Implementing hashCode

Implementing hashCode: It's a popular misconception that hashCode provides a unique identifier for an object. It does not.

Example 1

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 <tt>hashCode</tt>.
*
* 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 <tt>hashCode</tt>, to which is added contributions
  * from fields. Using a non-zero value decreases collisons of <tt>hashCode</tt>
  * values.
  */
  public static final int SEED = 23;

  /** booleans.  */
  public static int hash(int aSeed, boolean aBoolean) {
    log("boolean...");
    return firstTerm( aSeed ) + (aBoolean ? 1 : 0);
  }

  /*** chars.  */
  public static int hash(int aSeed, char aChar) {
    log("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.
    */
    log("int...");
    return firstTerm(aSeed) + aInt;
  }

  /** longs.  */
  public static int hash(int aSeed , long aLong) {
    log("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) );
  }

  /**
  * <tt>aObject</tt> is a possibly-null object field, and possibly an array.
  *
  * If <tt>aObject</tt> 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);
        //if an item in the array references the array itself, prevent infinite looping
        if(! (item == aObject))  
          //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();
  }
  
  private static void log(String aMessage){
    System.out.println(aMessage);
  }
} 


Here's an example of its use. When the logging 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<String> 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;
  }

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

Example 2

The WEB4J tool defines a utility class for implementing hashCode. Here is an example of a Model Object implemented with that utility.

Items to note:



package hirondelle.fish.main.discussion; 

import java.util.*;
import hirondelle.web4j.model.ModelCtorException;
import hirondelle.web4j.model.ModelUtil;
import hirondelle.web4j.model.Check;
import hirondelle.web4j.security.SafeText;
import static hirondelle.web4j.util.Consts.FAILS;

/** 
 Comment posted by a possibly-anonymous user.
*/
public final class Comment { 

  /**
   Constructor. 
     
   @param aUserName identifies the logged in user posting the comment. 
   @param aBody the comment, must have content.
   @param aDate date and time when the message was posted.
  */
  public Comment (
    SafeText aUserName, SafeText aBody, Date aDate
  ) throws ModelCtorException {
    fUserName = aUserName;
    fBody = aBody;
    fDate = aDate.getTime();
    validateState();
  }

  /** Return the logged in user name passed to the constructor. */
  public SafeText getUserName() {
    return fUserName;
  }

  /** Return the body of the message passed to the constructor.  */
  public SafeText getBody() {
    return fBody;
  }

  /**
   Return a <a href="http://www.javapractices.com/Topic15.cjp">defensive copy</a> 
   of the date passed to the constructor.
   
   <P>The caller may change the state of the returned value, without affecting 
   the internals of this <tt>Comment</tt>. Such copying is needed since 
   a {@link Date} is a mutable object.
  */
  public Date getDate() {
    // the returned object is independent of fDate
    return new Date(fDate);
  }

  /** Intended for debugging only. */
  @Override public String toString() {
    return ModelUtil.toStringFor(this);
  }

  @Override public boolean equals( Object aThat ) {
    Boolean result = ModelUtil.quickEquals(this, aThat);
    if ( result == null ){
      Comment that = (Comment) aThat;
      result = ModelUtil.equalsFor(
        this.getSignificantFields(), that.getSignificantFields()
      );
    }
    return result;    
  }

  @Override public int hashCode() {
    if ( fHashCode == 0 ) {
      fHashCode = ModelUtil.hashCodeFor(getSignificantFields());
    }
    return fHashCode;
  }

  // PRIVATE // 
  private final SafeText fUserName;
  private final SafeText fBody;
  /** Long is used here instead of Date in order to ensure immutability.*/
  private final long fDate;
  private int fHashCode;
  
  private Object[] getSignificantFields(){
    return new Object[] {fUserName, fBody, new Date(fDate)};
  }
  
  private void validateState() throws ModelCtorException {
    ModelCtorException ex = new ModelCtorException();
    if( FAILS ==  Check.required(fUserName) ) {
      ex.add("User name must have content.");
    }
    if ( FAILS == Check.required(fBody) ) {
      ex.add("Comment body must have content.");
    }
    if ( ! ex.isEmpty() ) throw ex;
  }
}
 



See Also :
Implementing equals
Immutable objects
Lazy initialization
Would you use this technique?
Yes   No   Undecided   
© 2014 Hirondelle Systems | Source Code | Contact | License | RSS
Individual code snippets can be used under this BSD license - Last updated on September 21, 2013.
Over 2,000,000 unique IPs last year - Built with WEB4J.
- In Memoriam : Bill Dirani -