简体   繁体   English

Java中的Groovy @Immutable类

[英]Groovy @Immutable classes in Java

I often recommend Groovy's @Immutable AST transformation as an easy way to make classes, well, immutable. 我经常建议使用Groovy的@Immutable AST转换作为使类良好@Immutable的简单方法。 This always works fine with other Groovy classes, but someone recently asked me if I could mix those classes into Java code. 这在其他Groovy类中总是可以正常工作,但是最近有人问我是否可以将这些类混合到Java代码中。 I always thought the answer was yes, but I'm hitting a snag. 我一直以为答案是肯定的,但我遇到了麻烦。

Say I have an immutable User class: 假设我有一个不变的User类:

import groovy.transform.Immutable

@Immutable
class User {
    int id
    String name
}

If I test this using a JUnit test written in Groovy, everything works as expected: 如果使用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'
    }
}

I can do the same with a JUnit test written in Java: 我可以使用用Java编写的JUnit测试执行相同的操作:

import static org.junit.Assert.*; 导入静态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
    }
}

This works, though there are troubles on the horizon. 尽管即将出现麻烦,但此方法可行。 IntelliJ 15 doesn't like the call to new User() , claiming that constructor is not found. IntelliJ 15不喜欢调用new User() ,声称未找到构造函数。 That also means the IDE underlines the class in red, meaning it has a compilation error. 这也意味着IDE用红色下划线标记该类,这意味着它具有编译错误。 The test passes anyway, which is a bit strange, but so be it. 无论如何,测试都通过了,这有点奇怪,但是事实如此。

If I try to use the User class in Java code directly, things start getting weird. 如果我尝试直接在Java代码中使用User类,那么事情开始变得很奇怪。

public class UserDemo {
    public static void main(String[] args) {
        User user = new User();
        System.out.println(user);
    }
}

Again IntelliJ isn't happy, but compiles and runs. IntelliJ再次感到不满意,但是会编译并运行。 The output is, of all things: 在所有方面的输出是:

User(0)

That's odd, because although the @Immutable transform does generate a toString method, I rather expected the output to show both properties. 这很奇怪,因为尽管@Immutable转换确实生成了toString方法,但我宁愿期望输出显示两个属性。 Still, that could be because the name property is null, so it's not included in the output. 仍然可能是因为name属性为null,所以它不包含在输出中。

If I try to use the tuple constructor: 如果我尝试使用元组构造函数:

public class UserDemo {
    public static void main(String[] args) {
        User user = new User(3, "name");
        System.out.println(user);
    }
}

I get 我懂了

User(0, name)

as the output, at least this time (sometimes it doesn't work at all). 作为输出,至少这次(有时根本不起作用)。

Then I added a Gradle build file. 然后我添加了Gradle构建文件。 If I put the Groovy classes under src\\main\\groovy and the Java classes under src\\main\\java (same for the tests but using the test folder instead), I immediately get a compilation issue: 如果将Groovy类放在src\\main\\groovy ,将Java类放在src\\main\\java (与测试相同,但改用test文件夹),我会立即遇到编译问题:

> gradle test
error: cannot find symbol
User user = new User(...)
^

I usually fix cross-compilation issues like this by trying to use the Groovy compiler for everything. 我通常会通过尝试对所有内容使用Groovy编译器来解决此类交叉编译问题。 If I put both classes under src\\main\\java , nothing changes, which isn't a big surprise. 如果我将这两个类都放在src\\main\\java ,则没有任何变化,这并不奇怪。 But if I put both classes under src\\main\\groovy , then I get this during the compileGroovy phase: 但是,如果我将两个类都放在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

Huh. This time it's objecting to the tuple constructor, because it thinks it only has a default constructor. 这次,它反对元组构造函数,因为它认为它只有默认构造函数。 I know the transform adds a default, a map-based, and a tuple constructor, but maybe they're not being generated in time for the Java code to see them. 我知道该转换会添加一个默认值,一个基于映射的值和一个元组构造函数,但是也许它们没有及时生成以使Java代码能够看到它们。

Incidentally, if I separate the Java and Groovy classes again, and add the following to my Gradle build: 顺便说一句,如果我再次分离Java和Groovy类,并将以下内容添加到我的Gradle构建中:

sourceSets {
    main {
        java { srcDirs = []}
        groovy { srcDir 'src/main/java' }
    }
}

I get the same error. 我犯了同样的错误。 If I don't add the sourceSets block, I get the User class not found error from earlier. 如果不添加sourceSets块, sourceSets收到来自较早版本的User class not found错误。

So the bottom line is, what's the correct way to add an @Immutable Groovy class to an existing Java system? 因此,最重要的是,将@Immutable Groovy类添加到现有Java系统的正确方法是什么? Is there some way to get the constructors to be generated in time for Java to see them? 有什么方法可以让Java及时看到构造函数吗?

I've been making Groovy presentations to Java developers for years and saying you can do this, only to now run into problems. 多年来,我一直在向Java开发人员进行Groovy演示,并说您可以做到这一点,直到现在遇到问题。 Please help me save face somehow. 请以某种方式帮助我节省面子。 :) :)

I did try your scenario as well, where you have a single project with a src/main/java and a src/main/groovy directory and ended up with compilation errors similar to what you saw. 我也尝试了您的方案,在该方案中,您有一个带有src/main/javasrc/main/groovy目录的项目,并且最终出现类似于您看到的编译错误。

I was able to use Groovy immutable objects in Java when I put the Groovy immutables in a separate project from the Java code. 将Groovy不可变对象放在与Java代码不同的项目中时,便能够在Java中使用Groovy不可变对象。 I have created a simple example and pushed it to GitHub ( https://github.com/cjstehno/immut ). 我创建了一个简单的示例并将其推送到GitHub( https://github.com/cjstehno/immut )。

Basically it's a Gradle multi-project with all the Groovy code (the immutable object) in the immut-groovy sub-project and all the Java code in the immut-java project. 基本上,这是一个Gradle多项目,在immut-groovy子项目中具有所有Groovy代码(不可变对象),在immut-java项目中具有所有Java代码。 The immut-java project depends on the immut-groovy project and uses the immutable Something object: immut-java项目依赖于immut-groovy项目,并使用不可变的Something对象:

public class SomethingFactory {

    Something createSomething(int id, String label){
        return new Something(id, label);
    }
}

I added a unit test in the Java project which creates a new instance of the immutable Groovy class and verifies its contents. 我在Java项目中添加了一个单元测试,该单元测试创​​建了不可变的Groovy类的新实例并验证其内容。

public class SomethingFactoryTest {
    @Test
    public void createSomething(){
        Something something = new SomethingFactory().createSomething(42, "wonderful");

        assertEquals(something.getId(), 42);
        assertEquals(something.getLabel(), "wonderful");
    }
}

This is not really ideal, but it works. 这不是很理想,但是可以。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM