@Documented @Retention(value=RUNTIME) @Target(value=TYPE) public @interface Immutable
It allows you to write classes in this shortened form:
@groovy.transform.Immutable
class Customer {
String first, last
int age
Date since
Collection favItems
}
def d = new Date()
def c1 = new Customer(first:'Tom', last:'Jones', age:21, since:d, favItems:['Books', 'Games'])
def c2 = new Customer('Tom', 'Jones', 21, d, ['Books', 'Games'])
assert c1 == c2
The @Immutable
annotation instructs the compiler to execute an
AST transformation which adds the necessary getters, constructors,
equals, hashCode and other helper methods that are typically written
when creating immutable classes with the defined properties.
A class created in this way has the following characteristics:
@Immutable
classes or known immutables (e.g. java.awt.Color, java.net.URI, java.util.UUID).
Also handled are Cloneable classes, collections, maps and arrays, and other "effectively immutable"
classes with special handling (e.g. java.util.Date).
ReadOnlyPropertyException
.
equals
, hashCode
and toString
methods are provided based on the property values.
Though not normally required, you may write your own implementations of these methods. For equals
and hashCode
,
if you do write your own method, it is up to you to obey the general contract for equals
methods and supply
a corresponding matching hashCode
method.
If you do provide one of these methods explicitly, the default implementation will be made available in a private
"underscore" variant which you can call. E.g., you could provide a (not very elegant) multi-line formatted
toString
method for Customer
above as follows:
String toString() { _toString().replaceAll(/\(/, '(\n\t').replaceAll(/\)/, '\n)').replaceAll(/, /, '\n\t') }If an "underscore" version of the respective method already exists, then no default implementation is provided.
Date
s, Cloneable
s and arrays are defensively copied on the way in (constructor) and out (getters).
Arrays and Cloneable
objects use the clone
method. For your own classes,
it is up to you to define this method and use deep cloning if appropriate.
Collection
s and Map
s are wrapped by immutable wrapper classes (but not deeply cloned!).
Attempts to update them will result in an UnsupportedOperationException
.
@Immutable
classes are allowed but for an
otherwise possible mutable property type, an error is thrown.
equals
or hashCode
methods.
Similarly, you may use static properties (though usually this is discouraged) and these too will be ignored
as far as significant state is concerned. If you do break standard conventions, you do so at your own risk and
your objects may no longer be immutable. It is up to you to ensure that your objects remain immutable at least
to the extent expected in other parts of your program!
@Canonical
.
Customising behaviour:
You can customise the toString() method provided for you by @Immutable
by also adding the @ToString
annotation to your class definition.
Limitations:
Cloneable
objects use the clone
method. For your own classes,
it is up to you to define this method and use deep cloning if appropriate.
Collection
s and Map
s are wrapped by immutable wrapper classes (but not deeply cloned!).
BigInteger
and BigDecimal
are deemed immutable but see:
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6348370
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. Use at your own risk.
java.util.Date
is treated as "effectively immutable" but is not final so it isn't strictly immutable.
Use at your own risk.
LinkedHashMap
or if there is a single Map, AbstractMap or HashMap property.
More examples:
--------------------------------------------------------------------------------import groovy.transform.* @Canonical class Building { String name int floors boolean officeSpace } // Constructors are added. def officeSpace = new Building('Initech office', 1, true) // toString() added. assert officeSpace.toString() == 'Building(Initech office, 1, true)' // Default values are used if constructor // arguments are not assigned. def theOffice = new Building('Wernham Hogg Paper Company') assert theOffice.floors == 0 theOffice.officeSpace = true def anotherOfficeSpace = new Building(name: 'Initech office', floors: 1, officeSpace: true) // equals() method is added. assert anotherOfficeSpace == officeSpace // equals() and hashCode() are added, so duplicate is not in Set. def offices = [officeSpace, anotherOfficeSpace, theOffice] as Set assert offices.size() == 2 assert offices.name.join(',') == 'Initech office,Wernham Hogg Paper Company' @Canonical @ToString(excludes='age') // Customize one of the transformations. class Person { String name int age } def mrhaki = new Person('mrhaki', 37) assert mrhaki.toString() == 'Person(mrhaki)'
Modifier and Type | Optional Element and Description |
---|---|
boolean |
copyWith
If
true , this adds a method copyWith which takes a Map of
new property values and returns a new instance of the Immutable class with
these values set. |
Class[] |
knownImmutableClasses
Allows you to provide
@Immutable with a list of classes which
are deemed immutable. |
String[] |
knownImmutables
Allows you to provide
@Immutable with a list of property names which
are deemed immutable. |
public abstract Class[] knownImmutableClasses
@Immutable
with a list of classes which
are deemed immutable. By supplying a class in this list, you are vouching
for its immutability and @Immutable
will do no further checks.
Example:
import groovy.transform.*@Immutable
(knownImmutableClasses = [Address]) class Person { String first, last Address address }@TupleConstructor
class Address { final String street }
public abstract String[] knownImmutables
@Immutable
with a list of property names which
are deemed immutable. By supplying a property's name in this list, you are vouching
for its immutability and @Immutable
will do no further checks.
Example:
@groovy.transform.Immutable
(knownImmutables = ['address'])
class Person {
String first, last
Address address
}
...
public abstract boolean copyWith
true
, this adds a method copyWith
which takes a Map of
new property values and returns a new instance of the Immutable class with
these values set.
Example:
@groovy.transform.Immutable
(copyWith = true)
class Person {
String first, last
}
def tim = new Person( 'tim', 'yates' )
def alice = tim.copyWith( first:'alice' )
assert tim.first == 'tim'
assert alice.first == 'alice'
Unknown keys in the map are ignored, and if the values would not change
the object, then the original object is returned.
If a method called copyWith
that takes a single parameter already
exists in the class, then this setting is ignored, and no method is generated.