Java 實現 hashCode 方法

原文 [url]http://www.javapractices.com/topic/TopicAction.do;jsessionid=8D0BB2D9061334756DC17A10A4FB5F71?Id=28[/url]

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.

[b]Example [/b]

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());
}
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章