Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Groovy @Immutable classes in Java

Tags:

java

groovy

I often recommend Groovy's @Immutable AST transformation as an easy way to make classes, well, immutable. This always works fine with other Groovy classes, but someone recently asked me if I could mix those classes into Java code. I always thought the answer was yes, but I'm hitting a snag.

Say I have an immutable User class:

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:

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:

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
    }
}

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. That also means the IDE underlines the class in red, meaning it has a compilation error. 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.

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. 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. Still, that could be because the name property is null, so it's not included in the output.

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. 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:

> 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. If I put both classes under src\main\java, nothing changes, which isn't a big surprise. But if I put both classes under src\main\groovy, then I get this during the compileGroovy phase:

> 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.

Incidentally, if I separate the Java and Groovy classes again, and add the following to my Gradle build:

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.

So the bottom line is, what's the correct way to add an @Immutable Groovy class to an existing Java system? Is there some way to get the constructors to be generated in time for Java to see them?

I've been making Groovy presentations to Java developers for years and saying you can do this, only to now run into problems. Please help me save face somehow. :)

like image 374
kousen Avatar asked Nov 17 '15 21:11

kousen


People also ask

What is immutable in groovy?

Immutable objects are created and cannot change after creation. This makes immutable objects very usable in concurrent and functional programming. To define a Java class as immutable we must define all properties as readonly and private. Only the constructor can set the values of the properties.

What is immutable class in Java?

Immutable class in java means that once an object is created, we cannot change its content. In Java, all the wrapper classes (like Integer, Boolean, Byte, Short) and String class is immutable. We can create our own immutable class as well.

Is Java Lang color immutable?

java. awt. Color is treated as "effectively immutable" but is not final so while not normally used with child classes, it isn't strictly immutable.

What is immutable object in Java?

An object is considered immutable if its state cannot change after it is constructed. Maximum reliance on immutable objects is widely accepted as a sound strategy for creating simple, reliable code. Immutable objects are particularly useful in concurrent applications.

What is immutable class in Java?

Immutable class in java means that once an object is created, we cannot change its content. In Java, all the wrapper classes (like Integer, Boolean, Byte, Short) and String class is immutable. We can create our own immutable class as well.

How to handle mutable objects in an immutable class?

If any mutable object is used as a field in an immutable class, then special handling has to be implemented so as to prevent its contents from being modified. A public constructor should be present. Getter methods for all the variables should be defined. Setter methods should not be present for any variables.

How to generate an immutable object from an abstract type?

The library generates immutable objects from abstract types: Interface, Class, Annotation. The key to achieving this is the proper use of @Value.Immutable annotation. It generates an immutable version of an annotated type and prefixes its name with the Immutable keyword.

How to generate an immutable version of an annotated type?

The key to achieving this is the proper use of @Value.Immutable annotation. It generates an immutable version of an annotated type and prefixes its name with the Immutable keyword. If we try to generate an immutable version of class named “ X “, it will generate a class named “ImmutableX”.


1 Answers

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.

I was able to use Groovy immutable objects in Java when I put the Groovy immutables in a separate project from the Java code. I have created a simple example and pushed it to 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. The immut-java project depends on the immut-groovy project and uses the immutable Something object:

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.

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.

like image 102
cjstehno Avatar answered Nov 04 '22 10:11

cjstehno