我经常推荐使用Groovy的@Immutable
AST转换作为一种简单的方法来创建类,这是不可变的。这总是适用于其他Groovy类,但有人最近问我是否可以将这些类混合到Java代码中。我一直认为答案是肯定的,但我遇到了麻烦。
说我有一个不可变的User
类:
import groovy.transform.Immutable
@Immutable
class User {
int id
String name
}
如果我使用Groovy编写的JUnit测试来测试它,一切都按预期工作:
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'
}
}
我可以用Java编写的JUnit测试做同样的事情:
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以红色突出显示该类,这意味着它有编译错误。无论如何,测试通过,这有点奇怪,但也是如此。
如果我尝试直接在Java代码中使用User
类,事情开始变得奇怪。
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
Groovy类添加到现有Java系统的正确方法是什么?是否有一些方法可以及时生成构造函数以供Java查看它们?
我多年来一直在向Java开发人员发表Groovy演示文稿,并说你可以做到这一点,但现在才遇到问题。不管怎样,请帮我救脸。 :)
答案 0 :(得分:4)
我也尝试了你的场景,你有一个带有src/main/java
和src/main/groovy
目录的项目,最终出现了类似于你看到的编译错误。
当我将Groovy不可变项放在与Java代码不同的项目中时,我能够在Java中使用Groovy不可变对象。我创建了一个简单的示例并将其推送到GitHub(https://github.com/cjstehno/immut)。
基本上它是一个Gradle多项目,包含immut-groovy
子项目中的所有Groovy代码(不可变对象)和immut-java
项目中的所有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");
}
}
这不是很理想,但它确实有效。