2015-11-17 2 views
6

Я часто рекомендую Groovy's @Immutable AST преобразование как простой способ сделать классы, ну, неизменные. Это всегда отлично работает с другими классами Groovy, но кто-то недавно спросил меня, могу ли я смешивать эти классы с Java-кодом. Я всегда считал, что ответ был «да», но я попадаю в ловушку.Groovy @Immutable классы в Java

Скажем, у меня есть непреложный User класс:

import groovy.transform.Immutable 

@Immutable 
class User { 
    int id 
    String name 
} 

Если я проверить это с помощью теста JUnit, написанный на Groovy, все работает, как ожидалось:

import org.junit.Test 

class UserGroovyTest { 

    @Test 
    void testMapConstructor() { 
     assert new User(name: 'name', id: 3) 
    } 

    @Test 
    void testTupleConstructor() { 
     assert new User(3, 'name') 
    } 

    @Test 
    void testDefaultConstructor() { 
     assert new User() 
    } 

    @Test(expected = ReadOnlyPropertyException) 
    void testImmutableName() { 
     User u = new User(id: 3, name: 'name') 
     u.name = 'other' 
    } 
} 

я могу сделать то же самое с Тест JUnit написан на Java:

import static org.junit.Assert. *;

import org.junit.Test; 

public class UserJavaTest { 

    @Test 
    public void testDefaultCtor() { 
     assertNotNull(new User()); 
    } 

    @Test 
    public void testTupleCtor() { 
     assertNotNull(new User(3, "name")); 
    } 

    @Test 
    public void testImmutableName() { 
     User u = new User(3, "name"); 
     // u.setName("other") // Method not found; doesn't compile 
    } 
} 

Это работает, хотя на горизонте есть проблемы. IntelliJ 15 не нравится вызов new User(), утверждая, что конструктор не найден. Это также означает, что среда IDE подчеркивает класс красным цветом, то есть имеет ошибку компиляции. Тест проходит в любом случае, что немного странно, но так оно и есть.

Если я попытаюсь использовать класс User в Java-коде напрямую, все начинает становиться странным.

public class UserDemo { 
    public static void main(String[] args) { 
     User user = new User(); 
     System.out.println(user); 
    } 
} 

Снова IntelliJ не устраивает, но компилируется и запускается. Выход из всех вещей:

User(0) 

Это странно, потому что, хотя @Immutable преобразование делает генерировать метод toString, я скорее ожидал, что это выход, чтобы показать оба свойства. Тем не менее, это может быть связано с тем, что свойство name равно null, поэтому оно не включено в вывод.

Если я пытаюсь использовать конструктор кортежа:

public class UserDemo { 
    public static void main(String[] args) { 
     User user = new User(3, "name"); 
     System.out.println(user); 
    } 
} 

Я получаю

User(0, name) 

как выход, по крайней мере, на этот раз (иногда не работает вообще).

Затем я добавил файл сборки Gradle. Если я ставлю Groovy классы под src\main\groovy и классы Java под src\main\java (то же самое для испытаний, но используя папку test вместо этого), я сразу получаю вопрос компиляции:

> gradle test 
error: cannot find symbol 
User user = new User(...) 
^ 

Я обычно исправить проблемы кросс-компиляции, как это пытаясь использовать компилятор Groovy для всего. Если я поместил оба класса под src\main\java, ничего не изменится, что не является большим сюрпризом. Но если я ставлю оба класса под src\main\groovy, то я получаю это во время compileGroovy фазы:

> gradle clean test 
error: constructor in class User cannot be applied to the given types; 
User user = new User(3, "name"); 

required: no arguments 
found: int,String 
reason: actual and formal arguments differ in length 

Ха. На этот раз он возражает против конструктора кортежей, потому что он считает, что он имеет только конструктор по умолчанию. Я знаю, что преобразование добавляет конструктор по умолчанию, основанный на карте и конструктор кортежей, но, возможно, они не генерируются вовремя, чтобы код Java их видел.

Кстати, если я снова отделить Java и Groovy классы, и добавьте следующие строки в моем Gradle построить:

sourceSets { 
    main { 
     java { srcDirs = []} 
     groovy { srcDir 'src/main/java' } 
    } 
} 

я получаю ту же ошибку. Если я не добавлю блок sourceSets, я получаю User класс, не найденный ранее.

Таким образом, нижняя строка - это правильный способ добавить класс @Immutable к существующей системе Java? Есть ли способ получить конструкторы, которые будут созданы вовремя для Java, чтобы увидеть их?

Я делаю презентации Groovy разработчикам Java уже много лет и говорю, что вы можете это сделать, только чтобы столкнуться с проблемами. Пожалуйста, помогите мне как-нибудь спастись. :)

+0

IDEs имеют тенденцию давать ложные отрицания, когда дело доходит до Groovy, особенно с трансформациями AST. Для проблемы кросс-компиляции вы можете попробовать два отдельных проекта: один java и один groovy. Если это еще нерешенный вопрос, я дам ему вихрь по утрам. Это действительно похоже, что он должен работать. – cjstehno

+0

Является ли поведение одинаковым с '@ CompileStatic'? – dmahapatro

+0

'@ CompileStatic' ничего не меняет – kousen

ответ

4

Я также пробовал ваш сценарий, где у вас есть один проект с src/main/java и src/main/groovy и в итоге были скомпилированы ошибки, похожие на то, что вы видели.

Я смог использовать неизменяемые объекты Groovy в Java, когда я помещал Groovy immutables в отдельный проект из кода Java. Я создал простой пример и нажал его на GitHub (https://github.com/cjstehno/immut).

В основном это мультипроект Gradle со всем кодом Groovy (неизменным объектом) в подпроекте immut-groovy и всем Java-кодом в проекте immut-java. Проект immut-java зависит от проекта immut-groovy и использует неизменный Something объект:

public class SomethingFactory { 

    Something createSomething(int id, String label){ 
     return new Something(id, label); 
    } 
} 

Я добавил модульное тестирование в проекте Java, который создает новый экземпляр неизменного Groovy класса и проверяет его содержимое.

public class SomethingFactoryTest { 
    @Test 
    public void createSomething(){ 
     Something something = new SomethingFactory().createSomething(42, "wonderful"); 

     assertEquals(something.getId(), 42); 
     assertEquals(something.getLabel(), "wonderful"); 
    } 
} 

Это не идеальный вариант, но он работает.

Смежные вопросы