This class provides methods to build a good equals method for any
class. It follows rules laid out in
Effective Java
, by Joshua Bloch. In particular the rule for comparing doubles,
floats, and arrays can be tricky. Also, making sure that
equals() and hashCode() are consistent can be
difficult.
Two Objects that compare as equals must generate the same hash code,
but two Objects with the same hash code do not have to be equal.
All relevant fields should be included in the calculation of equals.
Derived fields may be ignored. In particular, any field used in
generating a hash code must be used in the equals method, and vice
versa.
Alternatively, there is a method that uses reflection to determine
the fields to test. Because these fields are usually private, the method,
reflectionEquals, uses AccessibleObject.setAccessible to
change the visibility of the fields. This will fail under a security
manager, unless the appropriate permissions are set up correctly. It is
also slower than testing explicitly.
| Method from org.apache.commons.lang.builder.EqualsBuilder Detail: |
public EqualsBuilder append(Object lhs,
Object rhs) {
if (isEquals == false) {
return this;
}
if (lhs == rhs) {
return this;
}
if (lhs == null || rhs == null) {
this.setEquals(false);
return this;
}
Class lhsClass = lhs.getClass();
if (!lhsClass.isArray()) {
if (lhs instanceof java.math.BigDecimal) {
isEquals = (((java.math.BigDecimal)lhs).compareTo(rhs) == 0);
} else {
// The simple case, not an array, just test the element
isEquals = lhs.equals(rhs);
}
} else if (lhs.getClass() != rhs.getClass()) {
// Here when we compare different dimensions, for example: a boolean[][] to a boolean[]
this.setEquals(false);
}
// 'Switch' on type of array, to dispatch to the correct handler
// This handles multi dimensional arrays of the same depth
else if (lhs instanceof long[]) {
append((long[]) lhs, (long[]) rhs);
} else if (lhs instanceof int[]) {
append((int[]) lhs, (int[]) rhs);
} else if (lhs instanceof short[]) {
append((short[]) lhs, (short[]) rhs);
} else if (lhs instanceof char[]) {
append((char[]) lhs, (char[]) rhs);
} else if (lhs instanceof byte[]) {
append((byte[]) lhs, (byte[]) rhs);
} else if (lhs instanceof double[]) {
append((double[]) lhs, (double[]) rhs);
} else if (lhs instanceof float[]) {
append((float[]) lhs, (float[]) rhs);
} else if (lhs instanceof boolean[]) {
append((boolean[]) lhs, (boolean[]) rhs);
} else {
// Not an array of primitives
append((Object[]) lhs, (Object[]) rhs);
}
return this;
}
|
public EqualsBuilder append(long lhs,
long rhs) {
if (isEquals == false) {
return this;
}
isEquals = (lhs == rhs);
return this;
}
|
public EqualsBuilder append(int lhs,
int rhs) {
if (isEquals == false) {
return this;
}
isEquals = (lhs == rhs);
return this;
}
|
public EqualsBuilder append(short lhs,
short rhs) {
if (isEquals == false) {
return this;
}
isEquals = (lhs == rhs);
return this;
}
|
public EqualsBuilder append(char lhs,
char rhs) {
if (isEquals == false) {
return this;
}
isEquals = (lhs == rhs);
return this;
}
|
public EqualsBuilder append(byte lhs,
byte rhs) {
if (isEquals == false) {
return this;
}
isEquals = (lhs == rhs);
return this;
}
|
public EqualsBuilder append(double lhs,
double rhs) {
if (isEquals == false) {
return this;
}
return append(Double.doubleToLongBits(lhs), Double.doubleToLongBits(rhs));
}
Test if two doubles are equal by testing that the
pattern of bits returned by doubleToLong are equal.
This handles NaNs, Infinities, and -0.0.
It is compatible with the hash code generated by
HashCodeBuilder.
|
public EqualsBuilder append(float lhs,
float rhs) {
if (isEquals == false) {
return this;
}
return append(Float.floatToIntBits(lhs), Float.floatToIntBits(rhs));
}
Test if two floats are equal byt testing that the
pattern of bits returned by doubleToLong are equal.
This handles NaNs, Infinities, and -0.0.
It is compatible with the hash code generated by
HashCodeBuilder.
|
public EqualsBuilder append(boolean lhs,
boolean rhs) {
if (isEquals == false) {
return this;
}
isEquals = (lhs == rhs);
return this;
}
|
public EqualsBuilder append(Object[] lhs,
Object[] rhs) {
if (isEquals == false) {
return this;
}
if (lhs == rhs) {
return this;
}
if (lhs == null || rhs == null) {
this.setEquals(false);
return this;
}
if (lhs.length != rhs.length) {
this.setEquals(false);
return this;
}
for (int i = 0; i < lhs.length && isEquals; ++i) {
append(lhs[i], rhs[i]);
}
return this;
}
Performs a deep comparison of two Object arrays.
This also will be called for the top level of
multi-dimensional, ragged, and multi-typed arrays.
|
public EqualsBuilder append(long[] lhs,
long[] rhs) {
if (isEquals == false) {
return this;
}
if (lhs == rhs) {
return this;
}
if (lhs == null || rhs == null) {
this.setEquals(false);
return this;
}
if (lhs.length != rhs.length) {
this.setEquals(false);
return this;
}
for (int i = 0; i < lhs.length && isEquals; ++i) {
append(lhs[i], rhs[i]);
}
return this;
}
Deep comparison of array of long. Length and all
values are compared.
The method #append(long, long) is used.
|
public EqualsBuilder append(int[] lhs,
int[] rhs) {
if (isEquals == false) {
return this;
}
if (lhs == rhs) {
return this;
}
if (lhs == null || rhs == null) {
this.setEquals(false);
return this;
}
if (lhs.length != rhs.length) {
this.setEquals(false);
return this;
}
for (int i = 0; i < lhs.length && isEquals; ++i) {
append(lhs[i], rhs[i]);
}
return this;
}
Deep comparison of array of int. Length and all
values are compared.
The method #append(int, int) is used.
|
public EqualsBuilder append(short[] lhs,
short[] rhs) {
if (isEquals == false) {
return this;
}
if (lhs == rhs) {
return this;
}
if (lhs == null || rhs == null) {
this.setEquals(false);
return this;
}
if (lhs.length != rhs.length) {
this.setEquals(false);
return this;
}
for (int i = 0; i < lhs.length && isEquals; ++i) {
append(lhs[i], rhs[i]);
}
return this;
}
Deep comparison of array of short. Length and all
values are compared.
The method #append(short, short) is used.
|
public EqualsBuilder append(char[] lhs,
char[] rhs) {
if (isEquals == false) {
return this;
}
if (lhs == rhs) {
return this;
}
if (lhs == null || rhs == null) {
this.setEquals(false);
return this;
}
if (lhs.length != rhs.length) {
this.setEquals(false);
return this;
}
for (int i = 0; i < lhs.length && isEquals; ++i) {
append(lhs[i], rhs[i]);
}
return this;
}
Deep comparison of array of char. Length and all
values are compared.
The method #append(char, char) is used.
|
public EqualsBuilder append(byte[] lhs,
byte[] rhs) {
if (isEquals == false) {
return this;
}
if (lhs == rhs) {
return this;
}
if (lhs == null || rhs == null) {
this.setEquals(false);
return this;
}
if (lhs.length != rhs.length) {
this.setEquals(false);
return this;
}
for (int i = 0; i < lhs.length && isEquals; ++i) {
append(lhs[i], rhs[i]);
}
return this;
}
Deep comparison of array of byte. Length and all
values are compared.
The method #append(byte, byte) is used.
|
public EqualsBuilder append(double[] lhs,
double[] rhs) {
if (isEquals == false) {
return this;
}
if (lhs == rhs) {
return this;
}
if (lhs == null || rhs == null) {
this.setEquals(false);
return this;
}
if (lhs.length != rhs.length) {
this.setEquals(false);
return this;
}
for (int i = 0; i < lhs.length && isEquals; ++i) {
append(lhs[i], rhs[i]);
}
return this;
}
Deep comparison of array of double. Length and all
values are compared.
The method #append(double, double) is used.
|
public EqualsBuilder append(float[] lhs,
float[] rhs) {
if (isEquals == false) {
return this;
}
if (lhs == rhs) {
return this;
}
if (lhs == null || rhs == null) {
this.setEquals(false);
return this;
}
if (lhs.length != rhs.length) {
this.setEquals(false);
return this;
}
for (int i = 0; i < lhs.length && isEquals; ++i) {
append(lhs[i], rhs[i]);
}
return this;
}
Deep comparison of array of float. Length and all
values are compared.
The method #append(float, float) is used.
|
public EqualsBuilder append(boolean[] lhs,
boolean[] rhs) {
if (isEquals == false) {
return this;
}
if (lhs == rhs) {
return this;
}
if (lhs == null || rhs == null) {
this.setEquals(false);
return this;
}
if (lhs.length != rhs.length) {
this.setEquals(false);
return this;
}
for (int i = 0; i < lhs.length && isEquals; ++i) {
append(lhs[i], rhs[i]);
}
return this;
}
|
public EqualsBuilder appendSuper(boolean superEquals) {
if (isEquals == false) {
return this;
}
isEquals = superEquals;
return this;
}
|
public boolean isEquals() {
return this.isEquals;
}
|
public static boolean reflectionEquals(Object lhs,
Object rhs) {
return reflectionEquals(lhs, rhs, false, null, null);
}
This method uses reflection to determine if the two Objects
are equal.
It uses AccessibleObject.setAccessible to gain access to private
fields. This means that it will throw a security exception if run under
a security manager, if the permissions are not set up correctly. It is also
not as efficient as testing explicitly.
Transient members will be not be tested, as they are likely derived
fields, and not part of the value of the Object.
Static fields will not be tested. Superclass fields will be included.
|
public static boolean reflectionEquals(Object lhs,
Object rhs,
Collection excludeFields) {
return reflectionEquals(lhs, rhs, ReflectionToStringBuilder.toNoNullStringArray(excludeFields));
}
This method uses reflection to determine if the two Objects
are equal.
It uses AccessibleObject.setAccessible to gain access to private
fields. This means that it will throw a security exception if run under
a security manager, if the permissions are not set up correctly. It is also
not as efficient as testing explicitly.
Transient members will be not be tested, as they are likely derived
fields, and not part of the value of the Object.
Static fields will not be tested. Superclass fields will be included.
|
public static boolean reflectionEquals(Object lhs,
Object rhs,
String[] excludeFields) {
return reflectionEquals(lhs, rhs, false, null, excludeFields);
}
This method uses reflection to determine if the two Objects
are equal.
It uses AccessibleObject.setAccessible to gain access to private
fields. This means that it will throw a security exception if run under
a security manager, if the permissions are not set up correctly. It is also
not as efficient as testing explicitly.
Transient members will be not be tested, as they are likely derived
fields, and not part of the value of the Object.
Static fields will not be tested. Superclass fields will be included.
|
public static boolean reflectionEquals(Object lhs,
Object rhs,
boolean testTransients) {
return reflectionEquals(lhs, rhs, testTransients, null, null);
}
This method uses reflection to determine if the two Objects
are equal.
It uses AccessibleObject.setAccessible to gain access to private
fields. This means that it will throw a security exception if run under
a security manager, if the permissions are not set up correctly. It is also
not as efficient as testing explicitly.
If the TestTransients parameter is set to true, transient
members will be tested, otherwise they are ignored, as they are likely
derived fields, and not part of the value of the Object.
Static fields will not be tested. Superclass fields will be included.
|
public static boolean reflectionEquals(Object lhs,
Object rhs,
boolean testTransients,
Class reflectUpToClass) {
return reflectionEquals(lhs, rhs, testTransients, reflectUpToClass, null);
}
This method uses reflection to determine if the two Objects
are equal.
It uses AccessibleObject.setAccessible to gain access to private
fields. This means that it will throw a security exception if run under
a security manager, if the permissions are not set up correctly. It is also
not as efficient as testing explicitly.
If the testTransients parameter is set to true, transient
members will be tested, otherwise they are ignored, as they are likely
derived fields, and not part of the value of the Object.
Static fields will not be included. Superclass fields will be appended
up to and including the specified superclass. A null superclass is treated
as java.lang.Object.
|
public static boolean reflectionEquals(Object lhs,
Object rhs,
boolean testTransients,
Class reflectUpToClass,
String[] excludeFields) {
if (lhs == rhs) {
return true;
}
if (lhs == null || rhs == null) {
return false;
}
// Find the leaf class since there may be transients in the leaf
// class or in classes between the leaf and root.
// If we are not testing transients or a subclass has no ivars,
// then a subclass can test equals to a superclass.
Class lhsClass = lhs.getClass();
Class rhsClass = rhs.getClass();
Class testClass;
if (lhsClass.isInstance(rhs)) {
testClass = lhsClass;
if (!rhsClass.isInstance(lhs)) {
// rhsClass is a subclass of lhsClass
testClass = rhsClass;
}
} else if (rhsClass.isInstance(lhs)) {
testClass = rhsClass;
if (!lhsClass.isInstance(rhs)) {
// lhsClass is a subclass of rhsClass
testClass = lhsClass;
}
} else {
// The two classes are not related.
return false;
}
EqualsBuilder equalsBuilder = new EqualsBuilder();
try {
reflectionAppend(lhs, rhs, testClass, equalsBuilder, testTransients, excludeFields);
while (testClass.getSuperclass() != null && testClass != reflectUpToClass) {
testClass = testClass.getSuperclass();
reflectionAppend(lhs, rhs, testClass, equalsBuilder, testTransients, excludeFields);
}
} catch (IllegalArgumentException e) {
// In this case, we tried to test a subclass vs. a superclass and
// the subclass has ivars or the ivars are transient and
// we are testing transients.
// If a subclass has ivars that we are trying to test them, we get an
// exception and we know that the objects are not equal.
return false;
}
return equalsBuilder.isEquals();
}
This method uses reflection to determine if the two Objects
are equal.
It uses AccessibleObject.setAccessible to gain access to private
fields. This means that it will throw a security exception if run under
a security manager, if the permissions are not set up correctly. It is also
not as efficient as testing explicitly.
If the testTransients parameter is set to true, transient
members will be tested, otherwise they are ignored, as they are likely
derived fields, and not part of the value of the Object.
Static fields will not be included. Superclass fields will be appended
up to and including the specified superclass. A null superclass is treated
as java.lang.Object.
|
protected void setEquals(boolean isEquals) {
this.isEquals = isEquals;
}
|