Friday, June 19, 2009

A rose by any other name...

Martin Odersky, Lex Spoon, and Bill Venners have collaborated to venture where everyone has gone before. Yes, I mean the age old story about right way to override the equals method in Java. I see you rolling your eyes with the "I know, I know I must override hashcode method as well" bored refrain.

Spare me a few more minutes and I will convince you that you must give the article a serious once over. I for one got a better appreciation of the complexity of the problem -- if you want it addressed fully.

The article warms up around the 3rd of the four pitfalls they identify
1. Defining equals with the wrong signature
2. Changing `equals` without also changing hashCode
3. Defining `equals` in terms of mutable fields
4. Failing to define `equals` as an equivalence relation
Pitfall #3 discusses what happens when the state of an object that has been placed in a collection is modified. Typical `equals`method implementation will result in it disappearing from the collection.
`  public class Point {    private int x, y;    public Point(int i, int j) { x = i; y = j; }    public int getX() { return x; }    public void setX(int i) { x = i; }    public int getY() { return y; }    public void setY(int i) { y = i; }    @Override public int hashCode() { return 41 * (41 + getX()) + getY(); }    @Override public boolean equals(Object other) {        boolean result = false;        if (other instanceof Point) {            Point that = (Point) other;            result = (this.getX() == that.getX() && this.getY() == that.getY());        }        return result;    }}..HashSet hashSet = new HashSet();Point p = new Point(1, 2);hashSet.add(p);p.setX(12);System.out.println(hashSet.contains(p)); // <-- Prints false!!`

Yep! A forehead smacking moment alright. The article suggests a way around this problem. Which leads us to the 4th pitfall -- that of equivalence. This means that for non-null values, the following is true about the equals method:
• Reflexivity: x.equals(x)
• Symmetry: x.equals(y) => y.equals(x)
• Transitivity:x.equals(y) and y.equals(z) => x.equals(z)
• Consistence: x.equals(y) always returns the same result
• x.equals(null) is always false
With clear and concise examples, the article explains the complications of these contracts and how to implement them.