简体   繁体   English

来自Effective Java的第9项(等于合同):示例是否正确?

[英]Item 9 (equals contract) from Effective Java: is the example correct?

Bloch's wonderful book "Effective Java" points out that if equals is not symmetric then the behavior of Collections contains is indeterminate. Bloch的精彩书籍“Effective Java”指出,如果equals不对称,那么Collections contains的行为是不确定的。

In the example he gives (reproduced with small modifications below), Bloch says that he sees a "false", but could just as well have seen a true or an Exception. 在他给出的例子中(通过下面的小修改再现),布洛赫说他看到了“假”,但也可以看到真实或异常。

You could see a "true" if the standard does not specify whether contains(Object o) checks e.equals(o) or o.equals(e) for each item in the collection, and the former is implemented. 如果标准没有指定contains(Object o)是否检查e.equals(o)中每个项目的e.equals(o)o.equals(e) ,并且实现前者,则可以看到“true”。 However, the Collections Javadoc clearly states that it has to be the latter (and it's what I observed). 然而, Collections Javadoc明确指出它必须是后者(这是我观察到的)。

So the only possibilities I see are "false" or possibly an exception (but the String Javadoc seems to preclude the latter). 所以我看到的唯一可能是“假”或可能是异常(但是字符串Javadoc似乎排除了后者)。

I understand the broader point, it's likely that an asymmetric equals is will lead to problems in code outside of Collections, but I don't see it for the example he quotes. 我理解更广泛的观点,非对称equals可能会导致集合之外的代码出现问题,但我不会在他引用的例子中看到它。

Am I missing something? 我错过了什么吗?

import java.util.List;
import java.util.ArrayList;

class CIString {
  private final String s;

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

  @Override public boolean equals( Object o ) {
    System.out.println("Calling CIString.equals from " + this.s );
    if ( o instanceof CIString) 
      return s.equalsIgnoreCase( ( (CIString) o).s);
    if ( o instanceof String) 
      return s.equalsIgnoreCase( (String) o );
    return false;
  }
  // Always override hashCode when you override equals
  // This is an awful hash function (everything collides -> performance is terrible!)
  // but it is semantically sound.  See Item 10 from Effective Java for more details.
  @Override public int hashCode() { return 42; }
}

public class CIS {
  public static void main(String[] args) {
   CIString a = new CIString("Polish");
   String s = "polish";

   List<CIString> list = new ArrayList<CIString>();
   list.add(a);
   System.out.println("list contains s:" + list.contains(s));
 }
}

It is early in the morning, so perhaps I am missing the true point of your question, this code will fail: 这是一大早,所以也许我错过了你的问题的真正意义,这段代码将失败:

public class CIS 
{
    public static void main(String[] args) 
    {
        CIString a = new CIString("Polish");
        String s = "polish";

        List<String> list = new ArrayList<String>();
        list.add(s);
        System.out.println("list contains a:" + list.contains(a));
    }
}

At the very least it is odd that your code finds it and my code does not (from the point of view of sanity, not that that is clearly how your code is written :-) 至少你的代码找到它并且我的代码没有找到它是奇怪的(从理智的角度来看,这显然不是你的代码是如何编写的:-)

Edit: 编辑:

public class CIS {
  public static void main(String[] args) {
   CIString a = new CIString("Polish");
   String s = "polish";

   List<CIString> list = new ArrayList<CIString>();
   list.add(a);
   System.out.println("list contains s:" + list.contains(s));

   List<String> list2 = new ArrayList<String>();
   list2.add(s);
   System.out.println("list contains a:" + list2.contains(a));
 }
}

Now the code prints out: 现在代码打印出来:

list contains s:false
Calling CIString.equals from Polish
list contains a:true

Which still doesn't make sense... and is very fragile. 这仍然没有意义......而且非常脆弱。 If two objects are equal like a.equals(b) then they must also be equal like b.equal(a), that is not the case with your code. 如果两个对象像a.equals(b)那样相等,那么它们也必须等于b.equal(a),这与你的代码不同。

From the javadoc : 来自javadoc

It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true. 它是对称的:对于任何非空引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)才应返回true。

So, yes, the example in the book may be contrary to the Javadoc of the collections API, but the principle is correct. 所以,是的,本书中的示例可能与集合API的Javadoc相反,但原则是正确的。 One should not create an equals method that behaves oddly or eventually problems will arise. 不应该创建一个奇怪的行为或最终会出现问题的等于方法。

Edit 2: 编辑2:

The key point of the text is: 该文的关键点是:

In Sun's current implementation, it happens to return false, but that's just an implementation artifact. 在Sun目前的实现中,它恰好返回false,但这只是一个实现工件。 In another implementation, it could just as easily return true or throw a run-time exception. 在另一个实现中,它可以很容易地返回true或抛出运行时异常。 Once you've violated the equals contract, you simply don't know how other objects will behave when confronted with your object. 一旦违反了equals合同,您就不知道其他对象在面对对象时的行为方式。

However, given that the Javadoc says what it says it would seem that the behaviour is fixed not an implementation artifact. 但是,鉴于Javadoc说它看起来似乎行为是固定的而不是实现工件。

If it wasn't in the javadoc, or if the javadoc is not meant to be part of the specification, then it could change at a later date and the code would no longer work. 如果它不在javadoc中,或者javadoc不是规范的一部分,那么它可能会在以后更改,代码将不再起作用。

In the copy of the book I look at now (2nd Edition), item number is 8 and the whole section about symmetry requirement is presented pretty poorly. 在我现在看的书的副本(第2版)中,项目编号为8,关于对称性要求的整个部分显示得相当差。

Particular issue you mention seem to be caused by usage code being too close to implementation, obscuring the point author is trying to make. 您提到的特殊问题似乎是由于使用代码与实现过于接近而造成的,这掩盖了作者试图提出的观点。 I mean, I look at list.contains(s) and I see ArrayList and String through it and all the reasoning about returning true or throwing exception makes zero sense to me, really. 我的意思是,我查看list.contains(s)并通过它看到ArrayList和String,关于返回true或抛出异常的所有推理对我来说list.contains(s)有意义,真的。

  • I had to move the "usage code" further away from implementation to get the idea of how it may be: 我不得不将“使用代码”从实现中移开,以了解它的实现方式:

     void test(List<CIString> list, Object s) { if (list != null && list.size() > 0) { if (list.get(0).equals(s)) { // unsymmetric equality in CIString assert !list.contains(s); // "usage code": list.contain(s) } } } 

Above looks weird but as long as list is our ArrayList and s is our String, test passes. 上面看起来很奇怪但只要list是我们的ArrayList而s是我们的String,测试通过。

Now, what will happen if we use something else instead of String? 现在,如果我们使用其他东西而不是String,会发生什么? say, what happens if we pass new CIString("polish") as the second argument? 比如,如果我们传递new CIString("polish")作为第二个参数会发生什么?

Look, despite passing through first equals check , assertion fails at the next line - because contains will return true for this object. 看, 尽管通过第一次equals检查 ,断言在下一行失败 - 因为contains将为此对象返回true。


Similar reasoning applies for the part where Bloch mentions exception. 类似的推理适用于布洛赫提到异常的部分。 This time, I kept the second parameter as String, but for first one, imagined an List implementation other than ArrayList (that's legal isn't it). 这次,我将第二个参数保留为String,但是对于第一个参数,想象一个除ArrayList之外的List实现(这是合法的不是它)。

  • You see, List implementations are generally allowed to throw ClassCastException from contains , we just need to get one that does exactly that and use it for our test. 你看,List实现通常被允许从contains抛出ClassCastException,我们只需要得到一个完全相同的东西并将它用于我们的测试。 One that comes to mind could be based on TreeSet wrapped around our original list with appropriate comparator. 想到的一个可能是基于使用适当的比较器包裹在我们原始列表中的TreeSet。

     List<CIString> wrapperWithCce(List<CIString> original, Comparator<CIString> comparator) { final TreeSet<CIString> treeSet = new TreeSet<CIString>(comparator); treeSet.addAll(original); return new ArrayList<CIString>() { { addAll(treeSet); } @Override public boolean contains(Object o) { return treeSet.contains(o); // if o is String, will throw CCE } }; } 

What happens if we pass list like above and String "polish" to test ? 如果我们传递上面的列表和字符串“polish”来test什么? list.get(0).equals(s) will still pass the check but list.contains(s) will throw ClassCastException from TreeSet.contains() . list.get(0).equals(s)仍将通过检查,但list.contains(s)将从TreeSet.contains()中抛出ClassCastException。

This seem to be like the case Bloch had in mind when he mentioned that list.contains(s) may throw an exception - again, despite passing through first equals check . 这似乎就像布洛赫在提到list.contains(s)可能会抛出异常时所list.contains(s)的情况一样 - 尽管经过了第一次equals检查

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM