Моя предыдущая статья Краткий обзор Groovy ++ рассказывал о том, что такое Groovy ++, каковы преимущества и недостатки его использования, где он подходит по сравнению с Groovy и Java, а также о некоторых различиях между ними. Цель этой статьи состоит в том, чтобы пойти немного глубже и опробовать еще несколько базовых примеров и посмотреть, как вещи различаются в разных областях.
Идея здесь не в том, чтобы предлагать одно или другое, а просто в том, чтобы выявить различия в нашей информации и посмотреть, можно ли устранить эти различия.
Итак, начнем сразу с нескольких примеров.
1. Более строгие проверки времени компиляции
Если мы возьмем следующий кусок кода:
/* Leave it commented to run the dynamic Groovy version;
Uncomment to run Groovy++ version */
//@Typed
package test
def x = { List list ->
list.size()
}
x(1)
Не очень строгая проверка типов в Groovy позволяет успешно скомпилировать приведенный выше код. Он завершается с исключением во время выполнения, потому что мы вызываем x (Integer), где определен только x (List). Это зависит от вас, чтобы покрыть такие вещи в вашем тщательном модульном тестировании.
В Groovy ++ мы получаем ошибку времени компиляции — «Не удается найти метод {List -> …}. DoCall (int)», указывающий на то, что он не может вызываться с аргументами (int) для замыкания {List ->}
Обратная сторона проверок времени компиляции заключается в том, что мы проигрываем при утке.
Итак, если у нас есть:
/* Commented -> dynamic Groovy version;
Uncommented -> Groovy++ version */
//@Typed
package test
class Foo {
def greet() {println "Foo says hello"}
}
class Bar {
def greet() {println "Bar says hello"}
}
def c = {greeter -> greeter.greet()}
c(new Foo())
c(new Bar())
С Groovy мы получаем преимущества от утки. Хотя между типами Foo и Bar нет никакой связи, мы все равно можем вызывать методы, на которые они отвечают.
В Groovy ++ вместо этого мы получаем ошибку времени компиляции «Не удается найти метод Object.greet ()», поскольку он пытается перейти к статическому типу параметра закрытия.
2. Модификация типа «на лету» через ExpandoMetaClass
В следующем примере мы пытаемся добавить метод в класс String, используя функцию ExpandoMetaClass .
/* Commented -> dynamic Groovy version; Uncommented -> Groovy++ version */
//@Typed
package test
String.metaClass.foo = { -> println "foo called" }
"".foo() /* call my new method */
В Groovy код работает нормально и делает динамически добавляемый метод foo () доступным для класса java.lang.String.
Хотя Groovy ++ не поддерживает эту функцию в полностью статическом режиме, он предлагает «смешанный» режим для таких сценариев, где он пытается обеспечить лучшее из обоих миров.
Вот небольшой предварительный просмотр смешанного режима Groovy ++:
@Typed(TypePolicy.MIXED)
package test
String.metaClass.foo = { -> println "foo called" } // supported
"".foo()
class A {}
class B {}
A a = new A()
B b = a // this assignment produces a compile time error
Вышеприведенный код показывает, что при желании вы можете использовать смешанный режим, чтобы получить лучшее из обоих миров — динамических функций Groovy, а также статической проверки типов.
3. Замыкания — больше похоже на внутренние классы Java
В Java внутренние классы не могут ссылаться на неконечные члены своей внешней области видимости. Например, следующее не компилируется в Java:
void foo(){
String data = "";
class Inner {
void innerFoo() {
System.out.println(data);
}
}
}
Groovy рассматривает замыкания как внутренние классы, но не налагает таких ограничений на доступ к нефинальным элементам. Итак, следующий код прекрасно работает в Groovy:
void foo(){
String data = 'original';
def cl = {data = 'changed'} // access non-final data of its outer scope
cl()
assert data == 'changed'
}
foo()
Groovy ++ приближается к Java здесь. Он разрешает доступ только для чтения к неконечным элементам данных. Если вы попытаетесь внести изменения, вы получите ошибки компиляции. Поэтому следующий код завершается ошибкой с сообщением «Невозможно изменить итоговое поле test.Test $ foo $ 1.data»
@Typed
package test
void foo(){
String data = 'original';
def cl = {data = 'changed'}
}
foo()
Вот что говорит по этому поводу руководитель проекта Groovy ++ Алекс Ткачман : «Да, внешние переменные являются конечными, и это по замыслу — один из основных вариантов использования Groovy ++ — параллельное программирование, а не финальные переменные — самоубийство в этой ситуации».
Но что, если вы не выполняете параллельное программирование с Groovy ++, но, скажем, вы переносите некоторый код Groovy на Groovy ++, который изменяет данные из внешней области в своих замыканиях?
Есть ли решение Groovy ++ предлагает? — Да, вы можете явно использовать технику, которую Groovy использует за кулисами, и обернуть данные, которые вы хотите изменить, в объект groovy.lang.Reference, как показано в коде ниже:
@Typed
package test
void foo(){
Reference data = ['original']
def cl = {data = 'changed'} // now even Groovy++ supports modification of non-final outer scope data
cl()
assert data == 'changed'
}
foo()
4. Больше нет прямого доступа к частным пользователям
Groovy не ограничивает доступ к закрытым членам класса извне. Итак, следующий код проходит:
/* Commented -> dynamic Groovy version; Uncommented -> Groovy++ version */
//@Typed
package test
class SecretService {
String secret = "secret"
String getSecret() {
"covered-" + secret
}
private launchOperation() {
"launched"
}
}
def ss = new SecretService()
assert ss.@secret == "secret" // can access the private field directly
assert ss.launchOperation() == "launched" // can access the private method
Groovy ++ ограничивает доступ таких закрытых членов во время компиляции. В том же примере, что и выше в groovy ++, «ss. @ Secret» приводит к «Не удается получить доступ к полю test.SecretService.secret», а «ss.launchOperation ()» приводит к «Не удается получить доступ к методу SecretService.launchOperation ()»
5. Еще несколько незначительных отличий
а) Переменные привязки скрипта
/* Commented -> dynamic Groovy version; Uncommented -> Groovy++ version */
//@Typed
package test
foo = "foo"
assert foo == "foo"
Приведенный выше код отлично работает в Groovy — он определяет переменную «foo» в привязке скрипта. В Groovy ++ это не поддерживается. Если вы хотите использовать эту функцию в Groovy ++, используйте @Typed (TypePolicy.MIXED).
б) Свойство стиля доступа для карт
/* Commented -> dynamic Groovy version; Uncommented -> Groovy++ version */
//@Typed
package test
def map = [key1: "value1"]
assert map.key1 == "value1"
Groovy поддерживает стиль доступа к данным карты, как в примере выше. Groovy ++ не поддерживает его, если вы не используете режим MIXED.
Конец — Часть 1
Приведенные выше примеры охватывают несколько вещей, которые я смог выяснить относительно различий между Groovy и Groovy ++ до сих пор. Я намерен играть больше с Groovy ++ и попытаться раскрыть больше различий. Я призываю вас сделать то же самое и поделиться своими выводами.
Рекомендации:
1) Проект Groovy — http://groovy.codehaus.org/
2) Проект Groovy ++ — http://groups.google.com/group/groovyplusplus и http://code.google.com/p/groovypptest.