如果密钥以与equals不一致的方式实现Comparable,那么Java 8的HashMap是否会出错?这是一个错误吗?

我知道,从Java 8开始,如果HashMap有足够的哈希冲突,并且密钥实现了Comparable ,它将使用平衡树而不是bin的链表 。 但是从我所看到的, Comparable接口并不要求 compareTo() “与equals()一致”(尽管强烈建议)。

我错过了什么? 似乎新的实现允许HashMap违反Map接口的要求,如果密钥恰好具有兼容但非推荐的Comparable实现。

以下JUnit测试在OpenJDK 8u72上公开此行为:

 import static org.junit.Assert.*; import java.util.HashSet; import java.util.Set; import org.junit.Test; class Foo implements Comparable // 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 return Integer.compare(baz, o.baz); } } public class FooTest { @Test public void test() { Set set = new HashSet(); for (int i = 0; i < 128; ++i) { set.add(new Foo(i, 0)); } // This fails if Foo implements Comparable assertTrue(set.contains(new Foo(64, 1))); } } 

它不是IMO中的任何错误 ,因为代码的行为与实现者的预期相同 – 但这是一个不寻常的Comparable实现的已知结果。 来自可Comparable文件 :

强烈建议(尽管不是必需的)自然排序与equals一致。 这是因为没有显式比较器的有序集(和有序映射)在与自然排序与equals不一致的元素(或键)一起使用时表现得“奇怪”。 特别地,这样的有序集(或有序映射)违反了集合(或映射)的一般契约,其根据equals方法来定义。

现在虽然这不是正常意义上的有序集或地图,但与问题有明确的关系。

我同意这是一个可能的问题,而且是一个非常微妙的问题,特别是因为它很难在简单的情况下重现。 我会更新你的文档,以便非常强烈地关注你的类以与equals不一致的方式实现Comparable的事实,并特别将其称为潜在问题。

首先,让我们回想一下equalscompareTo之间的一致性意味着:

Comparable

当且仅当e1.compareTo(e2) == 0 C类的每个e1e2具有与e1.equals(e2)相同的布尔值时, C类的自然排序被认为与equals一致

因此,自然排序与equals不一致的一种情况是e1.compareTo(e2)可能返回零的排序,尽管e1.equals(e2)返回false 。 一个例子是不区分大小写的排序与区分大小写的相等。 另一个例子是BigDecimal ,它有一个自然顺序,其中new BigDecimal("1.0").compareTo(BigDecimal.ONE)返回零但new BigDecimal("1.0").equals(BigDecimal.ONE)返回false

请注意,新的HashMap实现处理这些情况。 自然排序仅有助于找到候选者,但是如果equals方法返回true ,则候选者将仅被视为相等。 这意味着当你有很多具有相同哈希码的密钥并且根据它们的自然顺序而不是等于equals时,你最终会在这些密钥中进行线性搜索,就像在旧的实现中一样。

相比之下,你的例子完全不同。 您的compareTo实现可能会告诉两个对象不相等,尽管equals测试将返回true 。 我知道, BigDecimal或任何其他与equals不一致的自然顺序的实际例子都不会发生这种情况。

当前实现不支持您的情况,但正如您可能已经注意到的那样,如果对象也具有相同的哈希代码,则仍然只会中断。 我怀疑这种情况是否具有实际意义。 到目前为止我看到的所有示例都是了解了新的HashMap实现之后构建的。

不,它是一个文档化的实现约束,以及Comparable.实现中的错误Comparable.