Статьи

Groovy.compareTo (Groovy ++) — часть 1

Моя предыдущая статья Краткий обзор 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.