Wednesday, 19 September 2012

Puzzle 58: Making a Hash of It


This puzzle attempts to learn from the mistakes of the previous one. Again the program consists of a Name class and a main method that puts a name into a hash set and checks whether the set contains the name. This time, however, the Name class does override the hashCode method. What does this program print?
import java.util.*;



public class Name {

    private final String first, last;



    public Name(String first, String last) {

        this.first = first; this.last = last;

    }



    public boolean equals(Name n) {

        return n.first.equals(first) && n.last.equals(last);

    }



    public int hashCode() {

        return 31 * first.hashCode() + last.hashCode();

    }



    public static void main(String[] args) {

        Set<Name> s = new HashSet<Name>();

        s.add(new Name("Donald", "Duck"));

        System.out.println(

            s.contains(new Name("Donald", "Duck")));

    }

}


Solution 58: Making a Hash of It

As in Puzzle 57, the main method of this program creates two Name instances, both representing the same name. This time it, happens to be Donald Duck rather than Mickey Mouse, but that shouldn't make much difference. Again, the main method puts the first instance into a hash set and then checks whether the set contains the second. This time, the hashCode method is clearly correct, so it looks as though the program ought to print TRue. Once again, appearances are deceiving: It always prints false. What's wrong this time?
The flaw in this program is similar to the one in Puzzle 57. In that puzzle, Name overrides the equals method but fails to override hashCode; in this puzzle, Name overrides the hashCode method but fails to override equals. That is not to say that Name doesn't declare an equals method; it does, but it's the wrong one. The Name class declares an equals method whose argument is of type Name rather than Object. The author of this class probably intended to override the equals method but mistakenly overloaded it [JLS 8.4.8.1, 8.4.9].
The HashSet class uses the equals(Object) method to test elements for equality; it is of no consequence to HashSet that Name declares an equals(Name) method. So where does Name get its equals(Object) method? It is inherited from Object. This method returns true only if its argument and the object on which it is invoked are one and the same. The main method of our program inserts one Name instance into the hash set and tests for the presence of another, so the test is guaranteed to return false. To us, both instances may represent the wonderful waterfowl, but to the hash map, they're just two unequal objects.
Fixing the program is as simple as replacing the overloaded equals method with the overriding one found in Puzzle 57. With this equals method, the program prints TRue as expected:
public boolean equals(Object o) {

    if (!(o instanceof Name))

        return false;

    Name n = (Name)o;

    return n.first.equals(first) && n.last.equals(last);

}


To make the program work, you merely have to add the overriding equals method. You don't have to eliminate the overloaded one, but you are better off without it. Overloadings represent opportunities for error and confusion [EJ Item 26]. If compatibility dictates that you must retain a "self-typed" equals method, implement the Object overloading in terms of the self-typed overloading to ensure identical behavior:
public boolean equals(Object o) {

    return o instanceof Name && equals((Name) o);

}


The lesson of this puzzle is: Don't overload a method when you want to override. To avoid unintentional overloading, mechanically copy the declaration of each superclass method that you want to override, or better yet, let your IDE do it for you. Besides protecting you against unintentional overloading, this protects you against misspelling method names. If you are using release 5.0 or a later release, apply the @Override annotation to each method declaration that is intended to override a superclass method:
@Override public boolean equals(Object o) { ... }


With this annotation, the program will not compile unless the annotated method overrides a superclass method. For language designers, it is worth considering a mandatory modifier on each method declaration that overrides a superclass method.

No comments:

Post a Comment

Your comments are welcome!