Java中的Groovy @Immutable类

时间:2015-11-17 21:32:31

标签: java groovy

我经常推荐使用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演示文稿,并说你可以做到这一点,但现在才遇到问题。不管怎样,请帮我救脸。 :)

1 个答案:

答案 0 :(得分:4)

我也尝试了你的场景,你有一个带有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");
    }
}

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