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