我经常推荐使用Groovy的@ImmutableAST转换作为一种简单的方法来创建类,这是不可变的.这总是适用于其他Groovy类,但有人最近问我是否可以将这些类混合到Java代码中.我一直认为答案是肯定的,但我遇到了麻烦.
说我有一个不可变的User类:
import groovy.transform.Immutable
@Immutable
class User {
int id
String name
}
Run Code Online (Sandbox Code Playgroud)
如果我使用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'
}
}
Run Code Online (Sandbox Code Playgroud)
我可以用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
}
}
Run Code Online (Sandbox Code Playgroud)
这是有效的,尽管有一些麻烦.IntelliJ 15不喜欢调用new User(),声称找不到构造函数.这也意味着IDE以红色突出显示该类,这意味着它有编译错误.无论如何,测试通过,这有点奇怪,但也是如此.
如果我尝试User直接在Java代码中使用该类,事情开始变得奇怪.
public class UserDemo {
public static void main(String[] args) {
User user = new User();
System.out.println(user);
}
}
Run Code Online (Sandbox Code Playgroud)
IntelliJ再次感到高兴,但编译和运行.在所有方面,输出是:
User(0)
Run Code Online (Sandbox Code Playgroud)
这很奇怪,因为虽然@Immutable变换确实生成了一个toString方法,但我更希望输出显示这两个属性.不过,这可能是因为该name属性为null,因此它不包含在输出中.
如果我尝试使用元组构造函数:
public class UserDemo {
public static void main(String[] args) {
User user = new User(3, "name");
System.out.println(user);
}
}
Run Code Online (Sandbox Code Playgroud)
我明白了
User(0, name)
Run Code Online (Sandbox Code Playgroud)
作为输出,至少这一次(有时根本不起作用).
然后我添加了一个Gradle构建文件.如果我把Groovy类src\main\groovy和Java类放在下面src\main\java(对于测试相同但是使用test文件夹),我立即得到一个编译问题:
> gradle test
error: cannot find symbol
User user = new User(...)
^
Run Code Online (Sandbox Code Playgroud)
我通常通过尝试将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
Run Code Online (Sandbox Code Playgroud)
呵呵.这次它反对元组构造函数,因为它认为它只有一个默认的构造函数.我知道转换添加了一个默认的,基于地图的和一个元组构造函数,但是它们可能没有及时生成,以便Java代码能够看到它们.
顺便说一句,如果我再次分离Java和Groovy类,并将以下内容添加到我的Gradle构建中:
sourceSets {
main {
java { srcDirs = []}
groovy { srcDir 'src/main/java' }
}
}
Run Code Online (Sandbox Code Playgroud)
我犯了同样的错误.如果我不添加sourceSets块,我User从前面得到class not found错误.
那么底线是,将@ImmutableGroovy类添加到现有Java系统的正确方法是什么?是否有某种方法可以及时生成构造函数以供Java查看它们?
多年来我一直在向Java开发人员发表Groovy演示文稿,并说你可以做到这一点,但现在只会遇到问题.不管怎样,请帮我救脸.:)
我也尝试过您的场景,其中您有一个带有src/main/java和src/main/groovy目录的项目,最终出现与您所看到的类似的编译错误。
当我将 Groovy 不可变对象放在与 Java 代码不同的项目中时,我能够在 Java 中使用 Groovy 不可变对象。我创建了一个简单的示例并将其推送到 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);
}
}
Run Code Online (Sandbox Code Playgroud)
我在 Java 项目中添加了一个单元测试,该测试创建不可变 Groovy 类的新实例并验证其内容。
public class SomethingFactoryTest {
@Test
public void createSomething(){
Something something = new SomethingFactory().createSomething(42, "wonderful");
assertEquals(something.getId(), 42);
assertEquals(something.getLabel(), "wonderful");
}
}
Run Code Online (Sandbox Code Playgroud)
这并不是很理想,但它确实有效。