82. Useful Annotations
Groovy comes with a number of handy notations that let you easily customise a class without doing the hard work yourself.
This chapter will take a brief look at some of the handier annotations in the
groovy.tranform package.
You can even write your own annotations but that’s something for another book…
ToString
The toString() method is used to provide a “human readable” representation of the object. It’s available on all
objects and you can override it but you can also just use the ToString annotation to make this even easier:
@groovy.transform.ToString
class Person {
def name
def email
def mobile
Person(name) {
this.name = name
}
}
def astrid = new Person('Astrid Smithson')
println astrid
ToString takes a few options:
-
@ToString(includeNames=true): will add in the property name, prefixing the property value -
@ToString(includeFields=true): by default, properties are used but this adds in fields
You can use several options at once:
@groovy.transform.ToString(includeNames=true, includeFields=true)
class Person {
def name
private email
private mobile
Person(name) {
this.name = name
}
}
def astrid = new Person('Astrid Smithson')
println astrid
EqualsAndHashCode
Determining if two instances are equal is something you have to add in yourself. Groovy will agree that two variables are the same if they point to the same instance of a class:
def agentSmith1 = new Person(id: 411, name: 'Agent Smith')
def agentSmith2 = agentSmith1
assert agentSmith1 == agentSmith2
However, this isn’t Groovy being clever, it’s just seeing that agentSmith1 and agentSmith2 point to the same thing.
In the code below you’ll see that a Person instance with the same id and name as another Person instance doesn’t
automatically equate to them being equal:
class Person {
def id
def name
}
def agentSmith1 = new Person(id: 411, name: 'Agent Smith')
def agentSmith2 = agentSmith1
def agentSmith3 = new Person(id: 411, name: 'Agent Smith')
assert agentSmith1 == agentSmith2
//This will fail
assert agentSmith2 == agentSmith3
By using the EqualsAndHashCode annotation I can tell Groovy that two instances of Person with the same id are actually
equal:
@groovy.transform.EqualsAndHashCode(includes='id')
class Person {
def id
def name
}
def agentSmith = new Person(id: 414, name: 'Agent Smith')
def agentSmith2 = new Person(id: 414, name: 'Agent X')
assert agentSmith == agentSmith2
Equality is not always easy to determine and will depend on the context in which you’re developing - my example above
is likely to cause some debate as to if matching only on id is enough. The EqualsAndHashCode annotation has a few options that are worth digging into.
Canonical
The Canonical annotation brings together functionality from a suite of other annotations:
ToStringEqualsAndHashCodeTupleConstructor
We’ve looked at these previously so let’s jump to an example:
@groovy.transform.Canonical
class Person {
def name
def email
def mobile
}
Person phil = new Person('Phil', '041414141')
println phil
This saves you from needing to stack your annotations:
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
import groovy.transform.TupleConstructor
@ToString
@EqualsAndHashCode
@TupleConstructor
class Person {
def name
def email
def mobile
}
Person phil = new Person('Phil', '041414141')
println phil
Immutable
The Immutable annotation provides similar features as Canonical but locks down new instances so that they can’t be changed after creation.
Immutable objects can be very useful in systems using parallel processing or caching.
@groovy.transform.Immutable
class Person {
String name
String email
String mobile
}
def krusty = new Person(name: 'Krusty')
krusty.email = 'heyhey@example.com'
The code above will fail on krusty.email = 'heyhey@example.com' as the Immutable annotation marks the email field as final.