原文
http://www.javapractices.com/topic/TopicAction.do;jsessionid=8D0BB2D9061334756DC17A10A4FB5F71?Id=28
Implementing hashCode :
* if a class overrides equals, it must override hashCode
* when they are both overridden, equals and hashCode must use the same set of fields
* if two objects are equal, then their hashCode values must be equal as well
* if the object is immutable, then hashCode is a candidate for caching and lazy initialization
It is a popular misconception that hashCode provides a unique identifier for an object. It does not.
Example
The following utility class allows simple construction of an effective hashCode method. It is based on the recommendations of Effective Java, by Joshua Bloch.
import java.lang.reflect.Array;
/**
* Collected methods which allow easy implementation of <code>hashCode</code>.
*
* Example use case:
* <pre>
* public int hashCode(){
* int result = HashCodeUtil.SEED;
* //collect the contributions of various fields
* result = HashCodeUtil.hash(result, fPrimitive);
* result = HashCodeUtil.hash(result, fObject);
* result = HashCodeUtil.hash(result, fArray);
* return result;
* }
* </pre>
*/
public final class HashCodeUtil {
/**
* An initial value for a <code>hashCode</code>, to which is added contributions
* from fields. Using a non-zero value decreases collisons of <code>hashCode</code>
* values.
*/
public static final int SEED = 23;
/**
* booleans.
*/
public static int hash( int aSeed, boolean aBoolean ) {
System.out.println("boolean...");
return firstTerm( aSeed ) + ( aBoolean ? 1 : 0 );
}
/**
* chars.
*/
public static int hash( int aSeed, char aChar ) {
System.out.println("char...");
return firstTerm( aSeed ) + (int)aChar;
}
/**
* ints.
*/
public static int hash( int aSeed , int aInt ) {
/*
* Implementation Note
* Note that byte and short are handled by this method, through
* implicit conversion.
*/
System.out.println("int...");
return firstTerm( aSeed ) + aInt;
}
/**
* longs.
*/
public static int hash( int aSeed , long aLong ) {
System.out.println("long...");
return firstTerm(aSeed) + (int)( aLong ^ (aLong >>> 32) );
}
/**
* floats.
*/
public static int hash( int aSeed , float aFloat ) {
return hash( aSeed, Float.floatToIntBits(aFloat) );
}
/**
* doubles.
*/
public static int hash( int aSeed , double aDouble ) {
return hash( aSeed, Double.doubleToLongBits(aDouble) );
}
/**
* <code>aObject</code> is a possibly-null object field, and possibly an array.
*
* If <code>aObject</code> is an array, then each element may be a primitive
* or a possibly-null object.
*/
public static int hash( int aSeed , Object aObject ) {
int result = aSeed;
if ( aObject == null) {
result = hash(result, 0);
}
else if ( ! isArray(aObject) ) {
result = hash(result, aObject.hashCode());
}
else {
int length = Array.getLength(aObject);
for ( int idx = 0; idx < length; ++idx ) {
Object item = Array.get(aObject, idx);
//recursive call!
result = hash(result, item);
}
}
return result;
}
/// PRIVATE ///
private static final int fODD_PRIME_NUMBER = 37;
private static int firstTerm( int aSeed ){
return fODD_PRIME_NUMBER * aSeed;
}
private static boolean isArray(Object aObject){
return aObject.getClass().isArray();
}
}
Here is an example of its use. When debugging statements are uncommented in HashCodeUtil, the reuse of the boolean, char, int and long versions of hash is demonstrated :
boolean...
char...
int...
long...
long...
int...
int...
int...
int...
int...
hashCode value: -608077094
import java.util.*;
public final class ApartmentBuilding {
public ApartmentBuilding (
boolean aIsDecrepit,
char aRating,
int aNumApartments,
long aNumTenants,
double aPowerUsage,
float aWaterUsage,
byte aNumFloors,
String aName,
List aOptions,
Date[] aMaintenanceChecks
){
fIsDecrepit = aIsDecrepit;
fRating = aRating;
fNumApartments = aNumApartments;
fNumTenants = aNumTenants;
fPowerUsage = aPowerUsage;
fWaterUsage = aWaterUsage;
fNumFloors = aNumFloors;
fName = aName;
fOptions = aOptions;
fMaintenanceChecks = aMaintenanceChecks;
}
@Override public boolean equals(Object that) {
if ( this == that ) return true;
if ( !(that instanceof ApartmentBuilding) ) return false;
ApartmentBuilding thatBuilding = (ApartmentBuilding)that;
return hasEqualState(thatBuilding);
}
@Override public int hashCode() {
//this style of lazy initialization is
//suitable only if the object is immutable
if ( fHashCode == 0) {
int result = HashCodeUtil.SEED;
result = HashCodeUtil.hash( result, fIsDecrepit );
result = HashCodeUtil.hash( result, fRating );
result = HashCodeUtil.hash( result, fNumApartments );
result = HashCodeUtil.hash( result, fNumTenants );
result = HashCodeUtil.hash( result, fPowerUsage );
result = HashCodeUtil.hash( result, fWaterUsage );
result = HashCodeUtil.hash( result, fNumFloors );
result = HashCodeUtil.hash( result, fName );
result = HashCodeUtil.hash( result, fOptions );
result = HashCodeUtil.hash( result, fMaintenanceChecks );
fHashCode = result;
}
return fHashCode;
}
//..other methods elided
// PRIVATE ////
/**
* The following fields are chosen to exercise most of the different
* cases.
*/
private boolean fIsDecrepit;
private char fRating;
private int fNumApartments;
private long fNumTenants;
private double fPowerUsage;
private float fWaterUsage;
private byte fNumFloors;
private String fName; //possibly null, say
private List fOptions; //never null
private Date[] fMaintenanceChecks; //never null
private int fHashCode;
/**
* Here, for two ApartmentBuildings to be equal, all fields must be equal.
*/
private boolean hasEqualState( ApartmentBuilding that ) {
//note the different treatment for possibly-null fields
return
( this.fName==null ? that.fName==null : this.fName.equals(that.fName) ) &&
( this.fIsDecrepit == that.fIsDecrepit )&&
( this.fRating == that.fRating )&&
( this.fNumApartments == that.fNumApartments ) &&
( this.fNumTenants == that.fNumTenants ) &&
( this.fPowerUsage == that.fPowerUsage ) &&
( this.fWaterUsage == that.fWaterUsage ) &&
( this.fNumFloors == that.fNumFloors ) &&
( this.fOptions.equals(that.fOptions) )&&
( Arrays.equals(this.fMaintenanceChecks, that.fMaintenanceChecks) );
}
/**
* Exercise hashcode.
*/
public static void main (String [] aArguments) {
List options = new ArrayList();
options.add("pool");
Date[] maintenanceDates = new Date[1];
maintenanceDates[0] = new Date();
byte numFloors = 8;
ApartmentBuilding building = new ApartmentBuilding (
false,
'B',
12,
396L,
5.2,
6.3f,
numFloors,
"Palisades",
options,
maintenanceDates
);
System.out.println("hashCode value: " + building.hashCode());
}
}