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. :)
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.
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.
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.
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.
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.
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.
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.
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”.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With