`
iloveflower
  • 浏览: 76768 次
社区版块
存档分类
最新评论
  • iloveflower: 呵呵。好好学习。。。。。。。。。。。。
    java 读书
  • Eric.Yan: 看了一点,不过是电子版的……你这一说到提醒我了,还要继续学习哈 ...
    java 读书

Implementing hashCode; Transaction.java

 
阅读更多
Below is the syntax highlighted version of Transaction.java from § Algorithms. 
/*************************************************************************
 *  Compilation:  javac Transaction.java
 *  Execution:    java Transaction
 *  
 *  Data type for commercial transactions.
 *
 *************************************************************************/

import java.util.Arrays;
import java.util.Comparator;


public class Transaction implements Comparable<Transaction> {
    private final String  who;      // customer
    private final Date    when;     // date
    private final double  amount;   // amount

    public Transaction(String who, Date when, double amount) {
        this.who    = who;
        this.when   = when;
        this.amount = amount;
    }

    // create new transaction by parsing string of the form: name,
    // date, real number, separated by whitespace
    public Transaction(String transaction) {
        String[] a = transaction.split("\\s+");
        who    = a[0];
        when   = new Date(a[1]);
        amount = Double.parseDouble(a[2]);
    }

    // accessor methods
    public String  who()    { return who;      }
    public Date    when()   { return when;     }
    public double  amount() { return amount;   }

    public String toString() {
        return String.format("%-10s %10s %8.2f", who, when, amount);
    }

    public int compareTo(Transaction that) {
        if      (this.amount < that.amount) return -1;
        else if (this.amount > that.amount) return +1;
        else                                return  0;
    }    

    // is this Transaction equal to x?
    public boolean equals(Object x) {
        if (x == this) return true;
        if (x == null) return false;
        if (x.getClass() != this.getClass()) return false;
        Transaction that = (Transaction) x;
        return (this.amount == that.amount) && (this.who.equals(that.who))
                                            && (this.when.equals(that.when));
    }




    public int hashCode() {
        int hash = 17;
        hash = 31*hash + who.hashCode();
        hash = 31*hash + when.hashCode();
        hash = 31*hash + ((Double) amount).hashCode();
        return hash;
    }

    // ascending order of account number
    public static class WhoOrder implements Comparator<Transaction> {
        public int compare(Transaction v, Transaction w) {
            return v.who.compareTo(w.who);
        }
    }

    // ascending order of time
    public static class WhenOrder implements Comparator<Transaction> {
        public int compare(Transaction v, Transaction w) {
            return v.when.compareTo(w.when);
        }
    }

    // ascending order of ammount
    public static class HowMuchOrder implements Comparator<Transaction> {
        public int compare(Transaction v, Transaction w) {
            if      (v.amount < w.amount) return -1;
            else if (v.amount > w.amount) return +1;
            else                          return  0;
        }
    }


    // test client
    public static void main(String[] args) {
        Transaction[] a = new Transaction[4];
        a[0] = new Transaction("Turing   6/17/1990  644.08");
        a[1] = new Transaction("Tarjan   3/26/2002  4121.85");
        a[2] = new Transaction("Knuth    6/14/1999  288.34");
        a[3] = new Transaction("Dijkstra 8/22/2007  2678.40");

        StdOut.println("Unsorted");
        for (int i = 0; i < a.length; i++)
            StdOut.println(a[i]);
        StdOut.println();
        
        StdOut.println("Sort by date");
        Arrays.sort(a, new Transaction.WhenOrder());
        for (int i = 0; i < a.length; i++)
            StdOut.println(a[i]);
        StdOut.println();

        StdOut.println("Sort by customer");
        Arrays.sort(a, new Transaction.WhoOrder());
        for (int i = 0; i < a.length; i++)
            StdOut.println(a[i]);
        StdOut.println();

        StdOut.println("Sort by amount");
        Arrays.sort(a, new Transaction.HowMuchOrder());
        for (int i = 0; i < a.length; i++)
            StdOut.println(a[i]);
        StdOut.println();
    }

}

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

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 : 
■the hashCode value is calculated only once, and only if it is needed. This is only possible since this is an immutable object.
 ■calling the getSignificantFields() method ensures hashCode and equals remain 'in sync'
 

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;
  }
}
 
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics