5 Апрель 2008 г.

Groovy Fields and Properties

После прочтения раздела Java and Groovy Integration книги Groovy Recipes, мне стало очень интересно, что же там скрывается за private и public свойствами в Groovy.

Suppressing Getter/Setter Generation


В Groovy есть два модификатора: private и public. Поэтому я могу написать

class Bla {
def a = 3
private b = 3
public c = 3
}

В первом случае модификатор не указан явно, в остальных – указан. В результате, если вывести
['a', 'b', 'c'].each {
println Bla.getDeclaredField(it)
}

то для этих трех случаев мы получим
> private java.lang.Object Bla.a
> private java.lang.Object Bla.b
> public java.lang.Object Bla.c

Действительно, если не указано явно, то поле будет иметь модификтор private.
Автор книги пишет, что делает явное объявление поля как private:
Explicitly flagging a field as private in Groovy suppresses the creation of the corresponding getter and setter methods.


Действительно:
class Bla {
private a = 3
}

println bla.invokeMethod('getA', null)
> Caught: groovy.lang.MissingMethodException: No signature of method: Bla.getA() is applicable for argument types: () values: {}
В то время как
class Bla {
def a = 3
}

println bla.invokeMethod('getA', null)
> 3
С другой стороны, следующий пример должен заставить задуматься:
class Bla {
public a = 3
}

println bla.invokeMethod('getA', null)
> Caught: groovy.lang.MissingMethodException: No signature of method: Bla.getA() is applicable for argument types: () values: {}
Т.е. не только private, но и public подавляет автогенерацию get и set методов. Хотя, конечно, ничто не мешает нам определить их самим. Мне непонятно, зачем же нужны два противоположных модификатора, которые, судя по всему, приводят к одинаковому результату. Возможно, это касается только интеграции с Java.

getProperties()


Этот метод возвращает список доступных свойств объекта. Судя по всему, этот список формируется по всем имеющимся get методам и значениям, которые они возвращают. Например:
class Bla {
public a = 3
def getA() {
return a + 10
}
def getB() {
return 33
}
}

println bla.getProperties()
> ["metaClass":groovy.lang.MetaClassImpl@1f78ef1[class Bla], "class":class Bla, "b":33, "a":13]

Как происходит обращение к свойству извне и изнутри


О том, как происходит обращение к свойству, довелось узнать не из книг по Groovy, а проведя несколько минут с дебаггером, переопределяя методы и ставя в них брейкпоинты. Вот что получилось, или "о том, что будет, если я сделаю вот так":
println bla.foo
или
bla.foo = 147
В первом случае происходит обращение к getProperty(String name), который пытается найти метод getFoo(String name). Этот метод должен присутствовать, он не должен быть синтезирован - invokeMethod(String name, args) и methodMissing(String name, args) не используются, и синтезированный метод не будет найден. Если метод найден, на его вызове все заканчивается. Если нет – будет произведена попытка найти поле с именем foo, и получить его значение (эквивалент bla.@foo). Если поле не найдено, вызывается метод propertyMissing(String name), который, в стандартной реализации, выбрасывает groovy.lang.MissingPropertyException.

Во втором случае – все аналогично, только сигнатуры методов выглядят как void setProperty(String name, args), void setFoo(String name, args), void propertyMissing(String name, value). setFoo(String name, value) тоже должен присутствовать или быть инъектированным. propertyMissing(String name, value) также по умолчанию выбрасывает исключение groovy.lang.MissingPropertyException. И еще – в этом случае все методы определены с типом void, а метод void propertyMissing(String name, value) получает значения, которое нужно присвоить.

Если обращение производится из самого объекта, например, a или this.a то сначала – попытка обратиться к полю напрямую (эквивалент this.@a), затем – вызов getProperty(String name) или void setProperty(String name, value), как уже было описано. Т.е. внутри объекта – обращение к полю более приоритетно, чем к свойству.
И еще немного юмора.
class Bla {
def A = 'A'
def getA() {
return 'small a'
}
}

println bla.getProperties()
> ["metaClass":groovy.lang.MetaClassImpl@2a4983[class Bla], "class":class Bla, "a":"small a"]