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. The Groovy documentation has a complete list of the rules applying to immutable objects. The Java code to make a class immutable is verbose, especially since the hashCode()
, equals()
and toString()
methods need to be overridden.
Groovy has the @Immutable
transformation to do all the work for us. We only have to define @Immutable
in our class definition and any object we create for this class is an immutable object. Groovy generates a class file following the rules for immutable objects. So all properties are readonly, constructors are created to set the properties, implementations for the hashCode()
, equals()
and toString()
methods are generated, and more.
@Immutable class User { String username, email Date created = new Date() Collection roles } def first = new User(username: 'mrhaki', email: 'email@host.com', roles: ['admin', 'user']) assert 'mrhaki' == first.username assert 'email@host.com' == first.email assert ['admin', 'user'] == first.roles assert new Date().after(first.created) try { // Properties are readonly. first.username = 'new username' } catch (ReadOnlyPropertyException e) { assert 'Cannot set readonly property: username for class: User' == e.message } try { // Collections are wrapped in immutable wrapper classes, so we cannot // change the contents of the collection. first.roles << 'new role' } catch (UnsupportedOperationException e) { assert true } def date = new Date(109, 8, 16) def second = new User('user', 'test@host.com', date, ['user']) assert 'user' == second.username assert 'test@host.com' == second.email assert ['user'] == second.roles assert '2009/08/16' == second.created.format('yyyy/MM/dd') assert date == second.created assert !date.is(second.created) // Date, Clonables and arrays are deep copied. // toString() implementation is created. assert 'User(user, test@host.com, Wed Sep 16 00:00:00 UTC 2009, [user])' == second.toString() def third = new User(username: 'user', email: 'test@host.com', created: date, roles: ['user']) // equals() method is also generated by the annotation and is based on the // property values. assert third == second