【发布时间】:2016-02-19 10:36:44
【问题描述】:
我经常推荐 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 测试做同样的事情:
导入静态 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 属性为空,所以它没有包含在输出中。
如果我尝试使用元组构造函数:
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 class not found 错误。
所以底线是,将@Immutable Groovy 类添加到现有 Java 系统的正确方法是什么?有什么方法可以及时生成构造函数,以便 Java 看到它们?
多年来,我一直在向 Java 开发人员演示 Groovy,并说您可以做到这一点,但现在却遇到了问题。请帮我以某种方式挽回面子。 :)
【问题讨论】:
-
IDE 在谈到 Groovy 时往往会给出假阴性,尤其是在 AST 转换方面。对于交叉编译问题,您可以尝试拥有两个独立的项目,一个 java 和一个 groovy。如果这仍然是一个未解决的问题,我会在早上试一试。这真的感觉它应该工作。
-
行为是否与
@CompileStatic相同? -
@CompileStatic不会改变任何东西 -
手动执行此操作(groovyc User、javac UserDemo、java -cp groovy.jar:.UserDemo)按预期工作。过去我的印象是,intellij 并不能真正完全掌握 gradle,所以您也可以尝试 a) 将您的 .java 文件放入 groovy 源集并在命令行上尝试(假设所有测试都在 intellij 中完成)。同时删除并定位。
-
试过了,我得到了和你一样的结果......我唯一能找到的与它有关的是 5 年前你的好自己的邮件列表:gradle.1045684.n5.nabble.com/… 曾经有过问题被抚养?