比较和违反合同的

时间:2015-02-25 08:02:54

标签: java equals comparator contract

Comparator的JavaDoc声明

  

强烈建议(尽管不要求)自然排序与等于一致。

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

现在,有人能举例说明一个"奇怪的"案件(!a.equals(b) && c.compare(a,b) == 0)的行为?第二种情况应该比第一种情况更频繁发生,因为在实施equals时很容易忘记为比较类型实施Comparator。在不知道例如TreeSet的实现的情况下,很难想出一个例子。

(这个问题与我有关,这是一个较长的故事。而且这不是作业)

3 个答案:

答案 0 :(得分:1)

这是一个简单的演示。我们有一个名为Strange的类,它使用不区分大小写的字符串比较来实现equalshashCode,但实现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);
}

我们得到 - 正如您所料:

Set1: [Hello, Everyone]
Set2: [Everyone, Hello, everyone, hello]

了解如何将字符串放入TreeSet更改结果?这是因为TreeSet使用compareToHashSet使用equalshashCode。这可能会破坏许多不同(以及最重要的意外)方式,因为您不必知道幕后使用的是哪种Set

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

答案 1 :(得分:0)

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

奇怪&#34;他们的意思是&#34; undefined&#34; - 如果这些类与不遵守规则的类一起工作,则没有定义这些类的行为方式。它们可能完美地工作,但它们可能不会。但是如果您的元素类不遵守其javadoc中描述的行为,则不能依赖它们正常工作。

答案 2 :(得分:0)

假设以下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() {???}

我不知道Set的来源,只是我需要使用它。

假设我假设Set与HashSet类似,并以equals

的形式定义
  

他们还举了一个例子,一个&#34;奇怪的&#34; (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));

假设我打印true时会感到惊讶,因为它是带有比较器的TreeSet

(lhs, rhs) -> lhs == rhs ? 0 : 1
  

现在,有人能举例说明一个&#34;奇怪的&#34;行为(!a.equals(b) && c.compare(a,b) == 0)

假设我

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

假设我打印false时会感到惊讶,因为它是带有比较器的TreeSet

(lhs, rhs) -> Integer.compare(lhs.bar % 10, rhs.bar % 10)

现在,定义与equals不一致的排序不存在固有问题。关键是 TreeSet的行为可能与Set 的文档中指定的行为不同。

这是clearly documented

  

[...] Set接口是根据等于操作定义的,但TreeSet实例使用其compareTo(或compare执行所有元素比较)方法,因此从该方法的观点来看,通过该方法被认为相等的两个元素是相等的。 集合的行为定义良好,即使其排序与equals不一致;它只是不遵守Set界面的一般合同。

只要比较者不是hacky并且您知道它是具有特定排序的TreeSet,您就不会感到惊讶。 (如果它像(lhs, rhs) -> 1一样hacky,你可能会感到惊讶。)