* Assists in implementing {@link Object#hashCode()} methods. *
* *
* This class enables a good hashCode
method to be built for any
* class. It follows the rules laid out in the book Effective Java
* by Joshua Bloch. Writing a good hashCode
method is actually
* quite difficult. This class aims to simplify the process.
*
* All relevant fields from the object should be included in the
* hashCode
method. Derived fields may be excluded. In general, any
* field used in the equals
method must be used in the
* hashCode
method.
*
* To use this class write code as follows: *
* ** public class Person { * String name; * int age; * boolean smoker; * ... * * public int hashCode() { * // you pick a hard-coded, randomly chosen, non-zero, odd number * // ideally different for each class * return new HashCodeBuilder(17, 37). * append(name). * append(age). * append(smoker). * toHashCode(); * } * } ** *
* If required, the superclass hashCode()
can be added using
* {@link #appendSuper}.
*
* Alternatively, there is a method that uses reflection to determine the fields
* to test. Because these fields are usually private, the method,
* reflectionHashCode
, 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.
*
* A typical invocation for this method would look like: *
* ** public int hashCode() { * return HashCodeBuilder.reflectionHashCode(this); * } ** * @author Stephen Colebourne * @author Gary Gregory * @author Pete Gieser * @since 1.0 * @version $Id: HashCodeBuilder.java 564070 2007-08-09 01:58:11Z bayard $ */ public class HashCodeAssist { /** *
* A registry of objects used by reflection methods to detect cyclical * object references and avoid infinite loops. *
* * @since 2.3 */ private static ThreadLocal
* Returns true
if the registry contains the given object. Used
* by the reflection methods to avoid infinite loops.
*
true
if the registry contains the given
* object.
* @since 2.3
*/
static boolean isRegistered(Object value) {
return registry.get().contains(toIdentityHashCodeInteger(value));
}
/**
*
* Appends the fields and values defined by the given object of the given
* Class
.
*
* This method uses reflection to build a valid hash code. *
* *
* 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 used, 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. *
* ** Two randomly chosen, non-zero, odd numbers must be passed in. Ideally * these should be different for each class, however this is not vital. * Prime numbers are preferred, especially for the multiplier. *
* * @param initialNonZeroOddNumber * a non-zero, odd number used as the initial value * @param multiplierNonZeroOddNumber * a non-zero, odd number used as the multiplier * @param object * the Object to create ahashCode
for
* @return int hash code
* @throws IllegalArgumentException
* if the Object is null
* @throws IllegalArgumentException
* if the number is zero or even
*/
public static int reflectionHashCode(int initialNonZeroOddNumber,
int multiplierNonZeroOddNumber, Object object) {
return reflectionHashCode(initialNonZeroOddNumber,
multiplierNonZeroOddNumber, object, false, null);
}
/**
* * This method uses reflection to build a valid hash code. *
* *
* 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. *
* ** Two randomly chosen, non-zero, odd numbers must be passed in. Ideally * these should be different for each class, however this is not vital. * Prime numbers are preferred, especially for the multiplier. *
* * @param initialNonZeroOddNumber * a non-zero, odd number used as the initial value * @param multiplierNonZeroOddNumber * a non-zero, odd number used as the multiplier * @param object * the Object to create ahashCode
for
* @param testTransients
* whether to include transient fields
* @return int hash code
* @throws IllegalArgumentException
* if the Object is null
* @throws IllegalArgumentException
* if the number is zero or even
*/
public static int reflectionHashCode(int initialNonZeroOddNumber,
int multiplierNonZeroOddNumber, Object object,
boolean testTransients) {
return reflectionHashCode(initialNonZeroOddNumber,
multiplierNonZeroOddNumber, object, testTransients, null);
}
/**
* Calls
* {@link #reflectionHashCode(int, int, Object, boolean, Class, String[])}
* with excludeFields set to null
.
*
* @param initialNonZeroOddNumber
* a non-zero, odd number used as the initial value
* @param multiplierNonZeroOddNumber
* a non-zero, odd number used as the multiplier
* @param object
* the Object to create a hashCode
for
* @param testTransients
* whether to include transient fields
* @param reflectUpToClass
* the superclass to reflect up to (inclusive), may be
* null
* @return int hash code
*/
public static int reflectionHashCode(int initialNonZeroOddNumber,
int multiplierNonZeroOddNumber, Object object,
boolean testTransients, Class> reflectUpToClass) {
return reflectionHashCode(initialNonZeroOddNumber,
multiplierNonZeroOddNumber, object, testTransients,
reflectUpToClass);
}
/**
* * This method uses reflection to build a valid hash code. *
* *
* 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 included up * to and including the specified superclass. A null superclass is treated * as java.lang.Object. *
* ** Two randomly chosen, non-zero, odd numbers must be passed in. Ideally * these should be different for each class, however this is not vital. * Prime numbers are preferred, especially for the multiplier. *
* * @param initialNonZeroOddNumber * a non-zero, odd number used as the initial value * @param multiplierNonZeroOddNumber * a non-zero, odd number used as the multiplier * @param object * the Object to create ahashCode
for
* @param testTransients
* whether to include transient fields
* @param reflectUpToClass
* the superclass to reflect up to (inclusive), may be
* null
* @param excludeFields
* array of field names to exclude from use in calculation of
* hash code
* @return int hash code
* @throws IllegalArgumentException
* if the Object is null
* @throws IllegalArgumentException
* if the number is zero or even
* @since 2.0
*/
public static int reflectionHashCode(int initialNonZeroOddNumber,
int multiplierNonZeroOddNumber, Object object,
boolean testTransients, Class> reflectUpToClass,
String... excludeFields) {
if (object == null) {
throw new IllegalArgumentException(
"The object to build a hash code for must not be null");
}
HashCodeAssist builder = new HashCodeAssist(initialNonZeroOddNumber,
multiplierNonZeroOddNumber);
Class> clazz = object.getClass();
reflectionAppend(object, clazz, builder, testTransients, excludeFields);
while (clazz.getSuperclass() != null && clazz != reflectUpToClass) {
clazz = clazz.getSuperclass();
reflectionAppend(object, clazz, builder, testTransients,
excludeFields);
}
return builder.toHashCode();
}
/**
* * This method uses reflection to build a valid hash code. *
* ** This constructor uses two hard coded choices for the constants needed to * build a hash code. *
* *
* 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 used, 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. *
* * @param object * the Object to create ahashCode
for
* @return int hash code
* @throws IllegalArgumentException
* if the object is null
*/
public static int reflectionHashCode(Object object) {
return reflectionHashCode(17, 37, object, false, null);
}
/**
* * This method uses reflection to build a valid hash code. *
* ** This constructor uses two hard coded choices for the constants needed to * build a hash code. *
* *
* 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. *
* * @param object * the Object to create ahashCode
for
* @param testTransients
* whether to include transient fields
* @return int hash code
* @throws IllegalArgumentException
* if the object is null
*/
public static int reflectionHashCode(Object object, boolean testTransients) {
return reflectionHashCode(17, 37, object, testTransients, null);
}
// -------------------------------------------------------------------------
/**
* * This method uses reflection to build a valid hash code. *
* ** This constructor uses two hard coded choices for the constants needed to * build a hash code. *
* *
* 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 used, 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. *
* * @param object * the Object to create ahashCode
for
* @param excludeFields
* array of field names to exclude from use in calculation of
* hash code
* @return int hash code
* @throws IllegalArgumentException
* if the object is null
*/
public static int reflectionHashCode(Object object, String... excludeFields) {
return reflectionHashCode(17, 37, object, false, null, excludeFields);
}
/**
* * Registers the given object. Used by the reflection methods to avoid * infinite loops. *
* * @param value * The object to register. */ static void register(Object value) { registry.get().add(toIdentityHashCodeInteger(value)); } /** * Returns an Integer for the given object's default hash code. * * @see System#identityHashCode(Object) * @param value * object for which the hashCode is to be calculated * @return Default int hash code */ private static Integer toIdentityHashCodeInteger(Object value) { return new Integer(System.identityHashCode(value)); } /** ** Unregisters the given object. *
* ** Used by the reflection methods to avoid infinite loops. * * @param value * The object to unregister. * @since 2.3 */ static void unregister(Object value) { registry.get().remove(toIdentityHashCodeInteger(value)); } /** * Constant to use in building the hashCode. */ private final int iConstant; /** * Running total of the hashCode. */ private int iTotal = 0; /** *
* Uses two hard coded choices for the constants needed to build a
* hashCode
.
*
* Two randomly chosen, non-zero, odd numbers must be passed in. Ideally * these should be different for each class, however this is not vital. *
* ** Prime numbers are preferred, especially for the multiplier. *
* * @param initialNonZeroOddNumber * a non-zero, odd number used as the initial value * @param multiplierNonZeroOddNumber * a non-zero, odd number used as the multiplier * @throws IllegalArgumentException * if the number is zero or even */ public HashCodeAssist(int initialNonZeroOddNumber, int multiplierNonZeroOddNumber) { if (initialNonZeroOddNumber == 0) { throw new IllegalArgumentException( "HashCodeBuilder requires a non zero initial value"); } if (initialNonZeroOddNumber % 2 == 0) { throw new IllegalArgumentException( "HashCodeBuilder requires an odd initial value"); } if (multiplierNonZeroOddNumber == 0) { throw new IllegalArgumentException( "HashCodeBuilder requires a non zero multiplier"); } if (multiplierNonZeroOddNumber % 2 == 0) { throw new IllegalArgumentException( "HashCodeBuilder requires an odd multiplier"); } iConstant = multiplierNonZeroOddNumber; iTotal = initialNonZeroOddNumber; } /** *
* Append a hashCode
for a boolean
.
*
* This adds iConstant * 1
to the hashCode
and not
* a 1231
or 1237
as done in java.lang.Boolean.
* This is in accordance with the Effective Java
design.
*
hashCode
* @return this
*/
public HashCodeAssist append(boolean value) {
iTotal = iTotal * iConstant + (value ? 0 : 1);
return this;
}
/**
*
* Append a hashCode
for a boolean
array.
*
hashCode
* @return this
*/
public HashCodeAssist append(boolean[] array) {
if (array == null) {
iTotal = iTotal * iConstant;
} else {
for (int i = 0; i < array.length; i++) {
append(array[i]);
}
}
return this;
}
// -------------------------------------------------------------------------
/**
*
* Append a hashCode
for a byte
.
*
hashCode
* @return this
*/
public HashCodeAssist append(byte value) {
iTotal = iTotal * iConstant + value;
return this;
}
// -------------------------------------------------------------------------
/**
*
* Append a hashCode
for a byte
array.
*
hashCode
* @return this
*/
public HashCodeAssist append(byte[] array) {
if (array == null) {
iTotal = iTotal * iConstant;
} else {
for (int i = 0; i < array.length; i++) {
append(array[i]);
}
}
return this;
}
/**
*
* Append a hashCode
for a char
.
*
hashCode
* @return this
*/
public HashCodeAssist append(char value) {
iTotal = iTotal * iConstant + value;
return this;
}
/**
*
* Append a hashCode
for a char
array.
*
hashCode
* @return this
*/
public HashCodeAssist append(char[] array) {
if (array == null) {
iTotal = iTotal * iConstant;
} else {
for (int i = 0; i < array.length; i++) {
append(array[i]);
}
}
return this;
}
/**
*
* Append a hashCode
for a double
.
*
hashCode
* @return this
*/
public HashCodeAssist append(double value) {
return append(Double.doubleToLongBits(value));
}
/**
*
* Append a hashCode
for a double
array.
*
hashCode
* @return this
*/
public HashCodeAssist append(double[] array) {
if (array == null) {
iTotal = iTotal * iConstant;
} else {
for (int i = 0; i < array.length; i++) {
append(array[i]);
}
}
return this;
}
/**
*
* Append a hashCode
for a float
.
*
hashCode
* @return this
*/
public HashCodeAssist append(float value) {
iTotal = iTotal * iConstant + Float.floatToIntBits(value);
return this;
}
/**
*
* Append a hashCode
for a float
array.
*
hashCode
* @return this
*/
public HashCodeAssist append(float[] array) {
if (array == null) {
iTotal = iTotal * iConstant;
} else {
for (int i = 0; i < array.length; i++) {
append(array[i]);
}
}
return this;
}
/**
*
* Append a hashCode
for an int
.
*
hashCode
* @return this
*/
public HashCodeAssist append(int value) {
iTotal = iTotal * iConstant + value;
return this;
}
/**
*
* Append a hashCode
for an int
array.
*
hashCode
* @return this
*/
public HashCodeAssist append(int[] array) {
if (array == null) {
iTotal = iTotal * iConstant;
} else {
for (int i = 0; i < array.length; i++) {
append(array[i]);
}
}
return this;
}
/**
*
* Append a hashCode
for a long
.
*
*
* @param value
* the long to add to the hashCode
* @return this
*/
// NOTE: This method uses >> and not >>> as Effective Java and
// Long.hashCode do. Ideally we should switch to >>> at
// some stage. There are backwards compat issues, so
// that will have to wait for the time being. cf LANG-342.
public HashCodeAssist append(long value) {
iTotal = iTotal * iConstant + ((int) (value ^ (value >> 32)));
return this;
}
/**
*
* Append a hashCode
for a long
array.
*
hashCode
* @return this
*/
public HashCodeAssist append(long[] array) {
if (array == null) {
iTotal = iTotal * iConstant;
} else {
for (int i = 0; i < array.length; i++) {
append(array[i]);
}
}
return this;
}
/**
*
* Append a hashCode
for an Object
.
*
hashCode
* @return this
*/
public HashCodeAssist append(Object object) {
if (object == null) {
iTotal = iTotal * iConstant;
} else {
// 'Switch' on type of array, to dispatch to the correct handler
// This handles multi dimensional arrays
if (object instanceof long[]) {
append((long[]) object);
} else if (object instanceof int[]) {
append((int[]) object);
} else if (object instanceof short[]) {
append((short[]) object);
} else if (object instanceof char[]) {
append((char[]) object);
} else if (object instanceof byte[]) {
append((byte[]) object);
} else if (object instanceof double[]) {
append((double[]) object);
} else if (object instanceof float[]) {
append((float[]) object);
} else if (object instanceof boolean[]) {
append((boolean[]) object);
} else if (object instanceof Object[]) {
// Not an array of primitives
append((Object[]) object);
} else {
iTotal = iTotal * iConstant + object.hashCode();
}
}
return this;
}
/**
*
* Append a hashCode
for an Object
array.
*
hashCode
* @return this
*/
public HashCodeAssist append(Object[] array) {
if (array == null) {
iTotal = iTotal * iConstant;
} else {
for (int i = 0; i < array.length; i++) {
append(array[i]);
}
}
return this;
}
/**
*
* Append a hashCode
for a short
.
*
hashCode
* @return this
*/
public HashCodeAssist append(short value) {
iTotal = iTotal * iConstant + value;
return this;
}
/**
*
* Append a hashCode
for a short
array.
*
hashCode
* @return this
*/
public HashCodeAssist append(short[] array) {
if (array == null) {
iTotal = iTotal * iConstant;
} else {
for (int i = 0; i < array.length; i++) {
append(array[i]);
}
}
return this;
}
/**
* * Adds the result of super.hashCode() to this builder. *
* * @param superHashCode * the result of callingsuper.hashCode()
* @return this HashCodeBuilder, used to chain calls.
* @since 2.0
*/
public HashCodeAssist appendSuper(int superHashCode) {
iTotal = iTotal * iConstant + superHashCode;
return this;
}
/**
*
* Return the computed hashCode
.
*
hashCode
based on the fields appended
*/
public int toHashCode() {
return iTotal;
}
}