1 Three Ways to Regard Equality
- Using an abstraction function: The function f is Absract Function. To use f as a definition for equality, we would say that a equals b if and only if f(a)=f(b).
- Using a relation: E is an quivalence relation, we would say that a equals b if and only if E(a,b).
- Using observation: Two objects are equal if and only if they cannot be distinguished by calling any operations of the abstract data type.
2 == vs. equals()
- The
==
operator compares references. Two references are == if they point to the same storage in memory. - The equals() operation compares object contents.
referential equality | object equality | |
---|---|---|
Java | == | equals() |
Objective C | == | isEqual: |
C# | == | Equals() |
Python | is | == |
Javasript | == | n/a |
Note that == unfortunately flips its meaning between Java and Python.
3 Equality of Immutable Types
It’s easy to make a mistake in the method signature, and** overload a method** when you meant to override it. This is such a common error that Java has a language feature, the annotation @Override
, which you should use whenever your intention is to override a method in your superclass.
3.1 instanceof
- Using instanceof is dynamic type checking, not the static type checking we vastly prefer. In general, using instanceof in object-oriented programming is a bad smell.
- This is another of our rules that holds true in most good Java programming — instanceof is disallowed anywhere except for implementing equals . This prohibition also includes** other ways of inspecting objects’ runtime types**. For example,
getClass
is also disallowed.
4 The Object Contract
equals
must define an equivalence relation – that is, a relation that is reflexive, symmetric, and transitive;equals
must be consistent: repeated calls to the method must yield the same result provided no information used in equals comparisons on the object is modified;- For a non-null reference x ,
x.equals(null)
should return false; hashCode
must produce the same result for two objects that are deemed equal by the equals method.
Here are some counter examples:
- Breaking the Equivalence Relation
private static final int CLOCK_SKEW = 5; // seconds
@Override
public boolean equals (Object thatObject) {
if (!(thatObject instanceof Duration)) return false;
Duration thatDuration = (Duration) thatObject;
//breaking transitive
return Math.abs(this.getLength() - thatDuration.getLength()) <= CLOCK_SKEW;
}
- Breaking Hash Tables
- Two very common collection implementations, HashSet and HashMap , use a hash table data structure, and depend on the
hashCode
method to be implemented correctly for the objects stored in the set and used as keys in the map. - How to implement
hashCode()
, see 《Effective Java》, Item 9.
- Two very common collection implementations, HashSet and HashMap , use a hash table data structure, and depend on the
Always override hashCode when you override equals.
5 Equality of Mutable Types
- when they cannot be distinguished by observation that doesn’t change the state of the objects , i.e., by calling only observer, producer, and creator methods. This is often strictly called observational equality , since it tests whether the two objects “look” the same, in the current state of the program.
- when they cannot be distinguished by any observation, even state changes. This interpretation allows calling any methods on the two objects, including mutators. This is often called** behavioral equality** , since it tests whether the two objects will “behave” the same, in this and all future states.
5.1 Solution
- Mutable objects should just inherit
equals()
andhashCode()
fromObject
. For clients that need a notion of observational equality (whether two mutable objects “look” the same in the current state), it’s better to define a new method, e.g.,similar()
. equals()
should compare references, just like == . Again, this is the same as saying equals() should provide behavioral equality.hashCode()
should map the reference into an integer.
6 Autoboxing and Equality
See code below:
Integer x = new Integer(3);
Integer y = new Integer(3);
x.equals(y) → true
and:
x == y // returns false
(int)x == (int)y // returns true
So you can’t really use Integer interchangeably with int . The fact that Java automatically converts between int and Integer (this is called autoboxing and autounboxing ) can lead to subtle bugs!
Reference
[1] 6.005 — Software Construction on MIT OpenCourseWare | OCW 6.005 Homepage at https://ocw.mit.edu/ans7870/6/6.005/s16/