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