Java 8';如果键以非';t与等于一致?

Java 8';如果键以非';t与等于一致?,java,hashmap,java-8,Java,Hashmap,Java 8,我知道,从Java8开始,如果一个HashMap有足够多的散列冲突,并且键实现了compariable,那么它就会。但是从我看到的情况来看,compareTo()的Comparable接口应该“与equals()一致”(尽管强烈推荐) 我错过什么了吗?新的实现似乎允许HashMap违反Map接口的要求,前提是这些键恰好有一个兼容但不推荐的可比较的实现 以下JUnit测试在OpenJDK 8u72上公开了此行为: import static org.junit.Assert.*; import

我知道,从Java8开始,如果一个
HashMap
有足够多的散列冲突,并且键实现了
compariable
,那么它就会。但是从我看到的情况来看,
compareTo()
Comparable
接口应该“与
equals()
一致”(尽管强烈推荐)

我错过什么了吗?新的实现似乎允许
HashMap
违反
Map
接口的要求,前提是这些键恰好有一个兼容但不推荐的
可比较的
实现

以下JUnit测试在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)));
    }
}
import static org.junit.Assert.*;
导入java.util.HashSet;
导入java.util.Set;
导入org.junit.Test;
福班
实现可比较//注释此项以修复测试用例
{
私人最终积分栏;
私人住宅区;
Foo(内部酒吧、内部酒吧){
这个.bar=bar;
this.baz=baz;
}
公共布尔等于(对象obj){
//请注意,这忽略了“baz”
返回对象实例Foo&&bar==((Foo)obj).bar;
}
公共int hashCode(){
返回0;
}
公共国际比较(Foo o){
//与equals()不一致,但似乎符合
//可比
返回整数。比较(baz,o.baz);
}
}
公务舱{
@试验
公开无效测试(){
Set=newhashset();
对于(int i=0;i<128;++i){
添加(新的Foo(i,0));
}
//如果Foo实现了可比较的
assertTrue(set.contains(新的Foo(64,1));
}
}

不,这是一个有文档记录的实现约束,是
可比实现中的一个bug。

在任何地方它都不是bug,因为代码的行为与实现者预期的一样-但这是一个不寻常的
可比
实现的已知结果。从:

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

现在,虽然这不是一个正常意义上的排序集或映射,但与这个问题有着明确的关系


我同意这是一个可能的问题,也是一个非常微妙的问题,特别是在简单的情况下很难重现。我将更新您的文档,以提请大家注意这样一个事实,即您的类以与
equals
不一致的方式实现了
compariable
,并特别将此称为一个潜在问题。

首先,让我们回顾一下
equals
comparieto
之间的一致性意味着什么:

()

C
的自然顺序称为与等于当且仅当
e1一致。compareTo(e2)==0
具有与
e1相同的布尔值。对于类
C
的每个
e1
e2
,等于(e2)

因此,自然排序与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
测试将返回
true
,但您的
compareTo
实现可能会告诉您两个对象并不相等。据我所知,
BigDecimal
或任何其他自然排序与equals不一致的实例都不会发生这种情况


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

我怀疑这不是一个bug,因为它是新实现的已知和预期行为。。。请注意,
TreeMap
总是使用顺序比较,而不是
equals
,所以这也有同样的问题。@jonsket True,但这就是我使用
HashMap
:)的原因。具体地说,我担心Java 7中故意使用
HashMap
(而不是
TreeMap
)的代码,因为它知道键有一个不推荐的
可比的
实现。升级到Java 8可能会引入一个极其微妙的错误。该类是一个与JDK中包含的equals不一致的示例。@jmehrens确实如此。不过,我无法用
BigDecimal
重现这个问题。我怀疑如果a.equals(b)
意味着a.compare(b)
(w