@Documented @Retention(value=RUNTIME) @Target(value=TYPE) public @interface EqualsAndHashCode
equals()
and hashCode()
methods.
It allows you to write classes in this shortened form:
import groovy.transform.EqualsAndHashCode
@EqualsAndHashCode
class Person {
String first, last
int age
}
def p1 = new Person(first:'John', last:'Smith', age:21)
def p2 = new Person(first:'John', last:'Smith', age:21)
assert p1 == p2
def map = [:]
map[p1] = 45
assert map[p2] == 45
The @EqualsAndHashCode
annotation instructs the compiler to execute an
AST transformation which adds the necessary equals and hashCode methods to the class.
The hashCode()
method is calculated using Groovy's HashCodeHelper
class
which implements an algorithm similar to the one outlined in the book Effective Java.
The equals()
method compares the values of the individual properties (and optionally fields)
of the class. It can also optionally call equals on the super class. Two different equals method
implementations are supported both of which support the equals contract outlined in the javadoc
for java.lang.Object
To illustrate the 'canEqual' implementation style (see http://www.artima.com/lejava/articles/equality.html for further details), consider this class:
@EqualsAndHashCode
class IntPair {
int x, y
}
The generated equals
and canEqual
methods will be something like below:
public boolean equals(java.lang.Object other) if (other == null) return false if (this.is(other)) return true if (!(other instanceof IntPair)) return false if (!other.canEqual(this)) return false if (x != other.x) return false if (y != other.y) return false return true } public boolean canEqual(java.lang.Object other) { return other instanceof IntPair }If no further options are specified, this is the default style for
@Canonical
and
@EqualsAndHashCode
annotated classes. The advantage of this style is that it allows inheritance
to be used in limited cases where its purpose is for overriding implementation details rather than
creating a derived type with different behavior. This is useful when using JPA Proxies for example or
as shown in the following examples:
import groovy.transform.*Note that if you create any domain classes which don't have exactly the same contract as@Canonical
class IntPair { int x, y } def p1 = new IntPair(1, 2) // overridden getter but deemed an IntPair as far as domain is concerned def p2 = new IntPair(1, 1) { int getY() { 2 } } // additional helper method added through inheritance but // deemed an IntPair as far as our domain is concerned@InheritConstructors
class IntPairWithSum extends IntPair { def sum() { x + y } } def p3 = new IntPairWithSum(1, 2) assert p1 == p2 && p2 == p1 assert p1 == p3 && p3 == p1 assert p3 == p2 && p2 == p3
IntPair
then you should provide an appropriate
equals
and canEqual
method. The easiest way to
achieve this would be to use the @Canonical
or
@EqualsAndHashCode
annotations as shown below:
The alternative supported style regards any kind of inheritance as creation of a new type and is illustrated in the following example:@EqualsAndHashCode
@TupleConstructor(includeSuperProperties=true)
class IntTriple extends IntPair { int z } def t1 = new IntTriple(1, 2, 3) assert p1 != t1 && p2 != t1 && t1 != p3
@EqualsAndHashCode(useCanEqual=false)
class IntPair {
int x, y
}
The generated equals method will be something like below:
public boolean equals(java.lang.Object other) if (other == null) return false if (this.is(other)) return true if (IntPair != other.getClass()) return false if (x != other.x) return false if (y != other.y) return false return true }This style is appropriate for final classes (where inheritance is not allowed) which have only
java.lang.Object
as a super class.
Most @Immutable
classes fall in to this category. For such classes,
there is no need to introduce the canEqual()
method.
Note that if you explicitly set useCanEqual=false
for child nodes
in a class hierarchy but have it true
for parent nodes and you
also have callSuper=true
in the child, then your generated
equals methods will not strictly follow the equals contract.
Note that when used in the recommended fashion, the two implementations supported adhere
to the equals contract. You can provide your own equivalence relationships if you need,
e.g. for comparing instances of the IntPair
and IntTriple
classes
discussed earlier, you could provide the following method in IntPair
:
boolean hasEqualXY(other) { other.x == getX() && other.y == getY() }Then for the objects defined earlier, the following would be true:
assert p1.hasEqualXY(t1) && t1.hasEqualXY(p1) assert p2.hasEqualXY(t1) && t1.hasEqualXY(p2) assert p3.hasEqualXY(t1) && t1.hasEqualXY(p3)There is also support for including or excluding fields/properties by name when constructing the equals and hashCode methods as shown here:
import groovy.transform.*Note:@EqualsAndHashCode
(excludes="z")@TupleConstructor
public class Point2D { int x, y, z } assert new Point2D(1, 1, 1).equals(new Point2D(1, 1, 2)) assert !new Point2D(1, 1, 1).equals(new Point2D(2, 1, 1))@EqualsAndHashCode
(excludes=["y", "z"])@TupleConstructor
public class Point1D { int x, y, z } assert new Point1D(1, 1, 1).equals(new Point1D(1, 1, 2)) assert new Point1D(1, 1, 1).equals(new Point1D(1, 2, 1)) assert !new Point1D(1, 1, 1).equals(new Point1D(2, 1, 1))
@EqualsAndHashCode
is a transform to help reduce boilerplate
in the common cases. It provides a useful implementation of equals()
and hashCode()
but not necessarily the most appropriate or
efficient one for every use case. You should write custom versions if your
scenario demands it. In particular, if you have
mutually-referential classes, the implementations provided may not be suitable
and may recurse infinitely (leading to a StackOverflowError
). In such cases,
you need to remove the infinite loop from your data structures or write your own custom methods.
If your data structures are self-referencing, the code generated by this transform will try to avoid
infinite recursion but the algorithm used may not suit your scenario, so use with caution if
you have such structures.
A future version of this transform may better handle some additional recursive scenarios.
More examples:
import groovy.transform.EqualsAndHashCode @EqualsAndHashCode(includeFields=true) class User { String name boolean active List likes private int age = 37 } def user = new User(name: 'mrhaki', active: false, likes: ['Groovy', 'Java']) def mrhaki = new User(name: 'mrhaki', likes: ['Groovy', 'Java']) def hubert = new User(name: 'Hubert Klein Ikkink', likes: ['Groovy', 'Java']) assert user == mrhaki assert mrhaki != hubert Set users = new HashSet() users.add user users.add mrhaki users.add hubert assert users.size() == 2
HashCodeHelper
Modifier and Type | Optional Element and Description |
---|---|
boolean |
cache
Whether to cache hashCode calculations.
|
boolean |
callSuper
Whether to include super in equals and hashCode calculations.
|
String[] |
excludes
List of field and/or property names to exclude from the equals and hashCode calculations.
|
boolean |
includeFields
Include fields as well as properties in equals and hashCode calculations.
|
String[] |
includes
List of field and/or property names to include within the equals and hashCode calculations.
|
boolean |
useCanEqual
Generate a canEqual method to be used by equals.
|
public abstract String[] excludes
public abstract String[] includes
public abstract boolean cache
public abstract boolean callSuper
public abstract boolean includeFields
public abstract boolean useCanEqual