Does java.util.HashSet not Adhere to its Specification?

89 views Asked by At

As a relative Java noob, I was baffled to find out the following:

Point.java:

public class Point {
...
    public boolean equals(Point other) {
        return x == other.x && y == other.y;
    }
...
}

Edge.java:

public class Edge {
    public final Point a, b;
    ...
    public boolean equals(Edge other) {
        return a.equals(other.a) && b.equals(other.b);
    }
...
}

main snippet: private Set blockedEdges;

public Program(...) {
    ...
    blockedEdges = new HashSet<Edge>();

    for (int i = 0; ...) {
        for (int j = 0; ...) {

            Point p = new Point(i, j);              
            for (Point q : p.neighbours()) {

                Edge e = new Edge(p, q);
                Edge f = new Edge(p, q);

                blockedEdges.add(e);


                // output for each line is: 
                // contains e? true; e equals f? true; contains f? false

                System.out.println("blocked edge from "+p+"to " + q+
                      "; contains e? " + blockedEdges.contains(e)+
                      " e equals f? "+ f.equals(e) + 
                      "; contains f? " + blockedEdges.contains(f));
            }
        }
    }
}

Why is this surprising? Because I checked the documentation before I coded this to rely on equality and it says:

Returns true if this set contains the specified element. More formally, returns true if and only if this set contains an element e such that (o==null ? e==null : o.equals(e))

This sentence is very clear and it states that nothing more than equality is needed. f.equals(e) returns true as shown in the output. So clearly the set does indeed contain an element e such that o.equals(e), yet contains(o) returns false.

While it is certainly understandable that a hash set also depends on the hash values being the same, this fact is mentioned neither in the docs of HashSet itself, nor is any such possibility mentioned in the docs of Set.

Thus, HashSet doesn't adhere to its specification. This looks like a very serious bug to me. Am I completely on the wrong track here? Or how come behaviour like this is accepted?

1

There are 1 answers

2
aioobe On BEST ANSWER

You're not overriding equals (you're overloading it). equals need to accept an Object as argument.

Do something like

@Override
public boolean equals(Object o) {
    if (!(o instanceof Point))
        return false;
    Point other = (Point) o;
    return x == other.x && y == other.y;
}

(and same for Edge)

It's also important to always override hashCode when you're overriding equals. See for instance Why do I need to override the equals and hashCode methods in Java?

Note that this mistake would have been caught by the compile if you had used @Override. This is why it's good practice to always use it where possible.