I know that since Java 8, if a HashMap
has enough hash collisions, and the keys implement Comparable
, it will use a balanced tree instead of a linked list for the bin. But from what I can see, the Comparable
interface does not require that compareTo()
be "consistent with equals()
" (though it is strongly recommended).
Did I miss something? It seems like the new implementation allows HashMap
to violate the requirements of the Map
interface if the keys happen to have a compliant, but non-recommended Comparable
implementation.
The following JUnit test exposes this behaviour on OpenJDK 8u72:
import static org.junit.Assert.*;
import java.util.HashSet;
import java.util.Set;
import org.junit.Test;
class Foo
implements Comparable<Foo> // Comment this out to fix the test case
{
private final int bar;
private final int baz;
Foo(int bar, int baz) {
this.bar = bar;
this.baz = baz;
}
public boolean equals(Object obj) {
// Note that this ignores 'baz'
return obj instanceof Foo && bar == ((Foo) obj).bar;
}
public int hashCode() {
return 0;
}
public int compareTo(Foo o) {
// Inconsistent with equals(), but seems to obey the requirements of
// Comparable<Foo>
return Integer.compare(baz, o.baz);
}
}
public class FooTest {
@Test
public void test() {
Set<Foo> set = new HashSet<>();
for (int i = 0; i < 128; ++i) {
set.add(new Foo(i, 0));
}
// This fails if Foo implements Comparable<Foo>
assertTrue(set.contains(new Foo(64, 1)));
}
}
It's not a bug anywhere IMO, in that the code is behaving as the implementor expected - but this is a known result of an unusual Comparable
implementation. From the Comparable
documentation:
It is strongly recommended (though not required) that natural orderings be consistent with
equals
. This is so because sorted sets (and sorted maps) without explicit comparators behave "strangely" when they are used with elements (or keys) whose natural ordering is inconsistent withequals
. In particular, such a sorted set (or sorted map) violates the general contract for set (or map), which is defined in terms of theequals
method.
Now while this isn't a sorted set or map in the normal sense, there's a clear relationship to the issue.
I agree that it's a possible issue though, and a really subtle one, particularly as it'll be hard to reproduce in simple situations. I would update your documentation to draw very strong attention to the fact that your class implements Comparable
in a way which is inconsistent with equals
, and specifically refer to this as a potential issue.
See more on this question at Stackoverflow