Java 比较器与违反set合同

Java 比较器与违反set合同,java,equals,comparator,contract,Java,Equals,Comparator,Contract,Comparator的JavaDoc声明 强烈建议(尽管不是必需的)自然顺序与equals一致 他们还举了一个当(a.equals(b)&&c.compare(a,b)!=0)时的“奇怪”行为的例子 现在,有人能给我举一个例子说明在case(!a.equals(b)和&c.compare(a,b)==0)中的“奇怪”行为吗?第二种情况应该比第一种情况更频繁,因为在实现比较器时,很容易忘记对比较类型实现等于。在不知道的情况下很难给出一个示例,例如,TreeSet (为什么这个问题与我有关,这是一个

Comparator的JavaDoc声明

强烈建议(尽管不是必需的)自然顺序与equals一致

他们还举了一个当
(a.equals(b)&&c.compare(a,b)!=0)
时的“奇怪”行为的例子

现在,有人能给我举一个例子说明在case
(!a.equals(b)和&c.compare(a,b)==0)
中的“奇怪”行为吗?第二种情况应该比第一种情况更频繁,因为在实现
比较器
时,很容易忘记对比较类型实现
等于
。在不知道的情况下很难给出一个示例,例如,
TreeSet


(为什么这个问题与我有关,这是一个较长的故事。这不是一个家庭作业)

集合的JDK实现依赖于这样的行为关系。另一个很好的例子是HashSet,它依赖于
equals()
hashCode()
同意


“奇怪”的意思是“未定义”——没有定义这些类在使用不遵循规则的类时的行为。它们可以完美地工作,也可以不完美。但是,如果您的元素类不遵循javadoc中描述的行为,您就不能依靠它们正常工作。

假设以下API:

final class Foo {
    int bar;
    Foo(int bar) { this.bar = bar; }
    public int hashCode() {
        return bar;
    }
    public boolean equals(Object o) {
        return o instanceof Foo && ((Foo)o).bar == bar;
    }
}

static Set<Foo> getSetOpaquely() {???}
假设这是一个带有比较器的树集,当它打印出
true
时我感到惊讶

(lhs, rhs) -> lhs == rhs ? 0 : 1
(lhs, rhs) -> Integer.compare(lhs.bar % 10, rhs.bar % 10)
现在,有人能给我举一个例子说明在case
(!a.equals(b)和&c.compare(a,b)==0)
中的“奇怪”行为吗

我想是的

Set<Foo> mySet = getSetOpaquely();
mySet.add(new Foo(1));
System.out.println(mySet.add(new Foo(1));
Set<Foo> mySet = getSetOpaquely();
mySet.add(new Foo(102));
System.out.println(mySet.add(new Foo(12));

现在,定义与
equals
不一致的顺序并不存在固有的问题。关键是树集的行为可能与集合的文档中指定的行为不同

这是:

[…]根据equals操作定义了
Set
接口,但是
TreeSet
实例使用其
compareTo
(或
compare
)方法执行所有元素比较,因此从集合的角度来看,此方法认为相等的两个元素是相等的集的行为是定义良好的,即使其顺序与equals不一致;它只是没有遵守
集合
接口的总合同。


只要比较器不是黑客,并且您知道它是一个具有特定顺序的树集,您就不会感到惊讶。(如果它像
(lhs,rhs)->1,你可能会感到惊讶。)

这里是一个简单的演示。我们有一个名为
Strange
的类,它使用不区分大小写的字符串比较来实现
equals
hashCode
,但实现了区分大小写的
compareTo

class Strange implements Comparable<Strange> {

    final String s;

    public Strange(String s) {
        this.s = s;
    }

    @Override
    public boolean equals(Object o) {
        // Kind of equals - case insensitive.
        return (o instanceof Strange) && ((Strange) o).s.equalsIgnoreCase(s);
    }

    @Override
    public int hashCode() {
        // Consistent with equals.
        return s.toUpperCase().hashCode();
    }

    @Override
    public int compareTo(Strange o) {
        // Exact ordering including case - inconsistent with equals.
        return s.compareTo(o.s);
    }

    @Override
    public String toString() {
        return s;
    }

}

public void test() {
    Set<Strange> set1 = new HashSet<>();
    Set<Strange> set2 = new TreeSet<>();
    for (String s : new String[]{"Hello", "hello", "Everyone", "everyone"}) {
        Strange strange = new Strange(s);
        set1.add(strange);
        set2.add(strange);
    }
    System.out.println("Set1: " + set1);
    System.out.println("Set2: " + set2);
}
请参见如何将字符串放入
树集
更改结果?这是因为
TreeSet
使用
compareTo
,而
HashSet
使用
equals
hashCode
。这可能会以许多不同的方式(最重要的是出乎意料的)破坏事物,因为您不必知道幕后正在使用什么类型的
集合


这表明
(a.equals(b)&&a.compareTo(b)!=0)
给出了奇怪的结果。很容易看出相反的问题
(!a.equals(b)&&a.compareTo(b)==0)
也显示出奇怪的结果。

如果你阅读
集合的文档,它会说如果你有两个相等的对象,那么集合中只存储一个(或两个都不存储)。如果树集使用的比较器与等号不一致,则这是不正确的。请参见我的回答:谢谢,斯图尔特·马克斯。你能把你的评论作为常规的回答,这样我就可以接受了吗?当然,这种行为是没有定义的。但通常情况下,存在一种确定性行为,这取决于具体的实现(有时还取决于其他因素,如虚拟机版本、操作系统等),我希望在给定的合同违约情况下有一个具体的非设置行为示例。
Set1: [Hello, Everyone]
Set2: [Everyone, Hello, everyone, hello]