【问题标题】:Groovy @Immutable classes in JavaJava 中的 Groovy @Immutable 类
【发布时间】: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/… 曾经有过问题被抚养?

标签: java groovy


【解决方案1】:

我也尝试过您的场景,您有一个带有src/main/javasrc/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");
    }
}

这不是很理想,但它确实有效。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-15
    • 2012-02-10
    • 2014-02-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多