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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值