Implementing equals
Discussion:
All objects have both identity (the object's location in memory) and state (the object's data). The == operator always compares identity. The default implementation of equals compares identity as well. Sometimes the default implementation of equals has the desired behaviour (as in a type-safe enumeration, for example), but equals should usually compare state, not identity. This is particularly true for "data-centric" classes which map to database records.
hashCode and equals are closely related :
if you override equals, you must override hashCode hashCode must generate equal values for equal objects Objects placed in a List , Set, or Map (as either a key or value) should have an appropriate definition of equals. (See, for example, the javadoc for Collection.contains , Map.containsKey, and Map.containsValue .) If you extend a concrete class, and add a new field which contributes to equals, then it is not possible to write a perfectly correct equals method for the new class. Instead, you should use composition instead of inheritance.
Example
Here is an implementation of equals for a data-centric class. It demonstrates how different types of fields are treated:
object fields, including collections : use equals type-safe enumerations : use either equals or == (they amount to the same thing, in this case) possibly-null object fields : use both == and equals array fields : use Arrays.equals primitive fields other than float or double : use == float : convert to int using Float.floatToIntBits, then use == double : convert to long using Double.doubleToLongBits, then use == It is worth noting that if fields are implemented with wrapper classes (Integer, Boolean, and so on), then implementation of equals is simpler, since there is only one case : calling the equals method recursively. (The compareTo method is also simplified in this case.) The above policies can be collected in a utility class :
import java.util.Arrays;
/**
* Collected methods which allow easy implementation of <code>equals</code>.
*
* Example use case in a class called Car:
* <pre>
public boolean equals(Object that){
if ( this == that ) return true;
if ( !(that instanceof Car) ) return false;
Car thatCar = (Car)that;
return
EqualsUtil.areEqual(this.fName, that.fName) &&
EqualsUtil.areEqual(this.fNumDoors, that.fNumDoors) &&
EqualsUtil.areEqual(this.fGasMileage, that.fGasMileage) &&
EqualsUtil.areEqual(this.fColor, that.fColor) &&
Arrays.equals(this.fMaintenanceChecks, that.fMaintenanceChecks); //array!
}
* </pre>
*
* <em>Arrays are not handled by this class</em>.
* This is because the <code>Arrays.equals</code> methods should be used for
* array fields.
*/
public final class EqualsUtil {
static public boolean areEqual(boolean aThis, boolean aThat){
//System.out.println("boolean");
return aThis == aThat;
}
static public boolean areEqual(char aThis, char aThat){
//System.out.println("char");
return aThis == aThat;
}
static public boolean areEqual(long aThis, long aThat){
/*
* Implementation Note
* Note that byte, short, and int are handled by this method, through
* implicit conversion.
*/
//System.out.println("long");
return aThis == aThat;
}
static public boolean areEqual(float aThis, float aThat){
//System.out.println("float");
return Float.floatToIntBits(aThis) == Float.floatToIntBits(aThat);
}
static public boolean areEqual(double aThis, double aThat){
//System.out.println("double");
return Double.doubleToLongBits(aThis) == Double.doubleToLongBits(aThat);
}
/**
* Possibly-null object field.
*
* Includes type-safe enumerations and collections, but does not include
* arrays. See class comment.
*/
static public boolean areEqual(Object aThis, Object aThat){
//System.out.println("Object");
return aThis == null ? aThat == null : aThis.equals(aThat);
}
}
Car is a class which uses EqualsUtil to implement its equals method :
import java.util.*;
public final class Car {
public Car (
String aName,
int aNumDoors,
List<String> aOptions,
double aGasMileage,
String aColor,
Date[] aMaintenanceChecks
){
fName = aName;
fNumDoors = aNumDoors;
fOptions = new ArrayList<String>(aOptions);
fGasMileage = aGasMileage;
fColor = aColor;
fMaintenanceChecks = new Date[aMaintenanceChecks.length];
for (int idx=0; idx < aMaintenanceChecks.length; ++idx) {
fMaintenanceChecks[idx] = new Date( aMaintenanceChecks[idx].getTime() );
}
}
public boolean equals(Object aThat) {
//check for self-comparison
if ( this == aThat ) return true;
//use instanceof instead of getClass here for two reasons
//1. if need be, it can match any supertype, and not just one class;
//2. it renders an explict check for "that == null" redundant, since
//it does the check for null already - "null instanceof [type]" always
//returns false. (See Effective Java by Joshua Bloch.)
if ( !(aThat instanceof Car) ) return false;
//Alternative to the above line :
//if ( aThat == null || aThat.getClass() != this.getClass() ) return false;
//cast to native object is now safe
Car that = (Car)aThat;
//now a proper field-by-field evaluation can be made
return
EqualsUtil.areEqual(this.fName, that.fName) &&
EqualsUtil.areEqual(this.fNumDoors, that.fNumDoors) &&
EqualsUtil.areEqual(this.fOptions, that.fOptions) &&
EqualsUtil.areEqual(this.fGasMileage, that.fGasMileage) &&
EqualsUtil.areEqual(this.fColor, that.fColor) &&
Arrays.equals(this.fMaintenanceChecks, that.fMaintenanceChecks);
}
//..other methods elided
// PRIVATE ////
/**
* The following fields are chosen to exercise most of the different
* cases.
*/
private String fName;
private int fNumDoors;
private List<String> fOptions;
private double fGasMileage;
private String fColor; //treat as possibly-null
private Date[] fMaintenanceChecks;
/**
* Exercise the equals method.
*/
public static void main (String[] aArguments) {
List<String> options = new ArrayList<String>();
options.add("sunroof");
Date[] dates = new Date[1];
dates[0] = new Date();
//Create a bunch of Cars; only one and two should be equal
Car one = new Car("Nissan", 2, options, 46.3, "Green", dates);
//two is equal to one
Car two = new Car("Nissan", 2, options, 46.3, "Green", dates);
//three has a differs in fName only
Car three = new Car("Pontiac", 2, options, 46.3, "Green", dates);
//four differs in fNumDoors only
Car four = new Car("Nissan", 4, options, 46.3, "Green", dates);
//five differs in fOptions only
List<String> optionsTwo = new ArrayList<String>();
optionsTwo.add("air conditioning");
Car five = new Car("Nissan", 2, optionsTwo, 46.3, "Green", dates);
//six differs in fGasMileage only
Car six = new Car("Nissan", 2, options, 22.1, "Green", dates);
//seven differs in fColor only
Car seven = new Car("Nissan", 2, options, 46.3, "Fuchsia", dates);
//eight differs in fMaintenanceChecks only
Date[] datesTwo = new Date[1];
datesTwo[0] = new Date(1000000);
Car eight = new Car("Nissan", 2, options, 46.3, "Green", datesTwo);
System.out.println( "one = one: " + one.equals(one) );
System.out.println( "one = two: " + one.equals(two) );
System.out.println( "two = one: " + two.equals(one) );
System.out.println( "one = three: " + one.equals(three) );
System.out.println( "one = four: " + one.equals(four) );
System.out.println( "one = five: " + one.equals(five) );
System.out.println( "one = six: " + one.equals(six) );
System.out.println( "one = seven: " + one.equals(seven) );
System.out.println( "one = eight: " + one.equals(eight) );
System.out.println( "one = null: " + one.equals(null) );
}
}
An example run of this class demonstrates that only objects one and two are equal :
one = one: true
one = two: true
two = one: true
one = three: false
one = four: false
one = five: false
one = six: false
one = seven: false
one = eight: false
one = null: false