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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | @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 |