@AnnotationCollector(value: [ToString, TupleConstructor, EqualsAndHashCode], mode: AnnotationCollectorMode.PREFER_EXPLICIT_MERGED) @interface Canonical
The @Canonical meta-annotation combines the @EqualsAndHashCode,
@ToString and @TupleConstructor annotations. It is used to assist in
the creation of mutable classes. It instructs the compiler to execute AST transformations
which add positional constructors, equals, hashCode and a pretty print toString to your class.
You can write classes in this shortened form:
import groovy.transform.Canonical
@Canonical class Customer {
String first, last
int age
Date since
Collection favItems = ['Food']
def object
}
def d = new Date()
def anyObject = new Object()
def c1 = new Customer(first:'Tom', last:'Jones', age:21, since:d, favItems:['Books', 'Games'], object: anyObject)
def c2 = new Customer('Tom', 'Jones', 21, d, ['Books', 'Games'], anyObject)
assert c1 == c2
You don't need to provide all arguments in constructor calls. If using named parameters, any property names not
referenced will be given their default value (as per Java's default unless an explicit initialization constant is
provided when defining the property). If using a tuple constructor, parameters are supplied in the order in which
the properties are defined. Supplied parameters fill the tuple from the left. Any parameters missing on the right
are given their default value.
def c3 = new Customer(last: 'Jones', age: 21)
def c4 = new Customer('Tom', 'Jones')
assert null == c3.since
assert 0 == c4.age
assert c3.favItems == ['Food'] && c4.favItems == ['Food']
If you don't need all of the functionality of @Canonical, you can simply directly use one or more of the individual
annotations which @Canonical aggregates.
In addition, you can use @Canonical in combination with explicit use one or more of the individual annotations in
cases where you need to further customize the annotation attributes.
Any applicable annotation attributes from @Canonical missing from the explicit annotation will be merged
but any existing annotation attributes within the explicit annotation take precedence. So, for example in this case here:
The generated@Canonical(includeNames=true, excludes='c')@ToString(excludes='a,b') class MyClass { ... }
toString will include property names and exclude the a and b properties.
A class created using @Canonical has the following characteristics:
equals, hashCode and toString methods are provided based on the property values.
See the GroovyDoc for the individual annotations for more details.
If you want similar functionality to what this annotation provides but also require immutability, see the
@Immutable annotation.
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)'