繁体   English   中英

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

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

Bloch的精彩书籍“Effective Java”指出,如果equals不对称,那么Collections contains的行为是不确定的。

在他给出的例子中(通过下面的小修改再现),布洛赫说他看到了“假”,但也可以看到真实或异常。

如果标准没有指定contains(Object o)是否检查e.equals(o)中每个项目的e.equals(o)o.equals(e) ,并且实现前者,则可以看到“true”。 然而, Collections Javadoc明确指出它必须是后者(这是我观察到的)。

所以我看到的唯一可能是“假”或可能是异常(但是字符串Javadoc似乎排除了后者)。

我理解更广泛的观点,非对称equals可能会导致集合之外的代码出现问题,但我不会在他引用的例子中看到它。

我错过了什么吗?

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));
 }
}

这是一大早,所以也许我错过了你的问题的真正意义,这段代码将失败:

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));
    }
}

至少你的代码找到它并且我的代码没有找到它是奇怪的(从理智的角度来看,这显然不是你的代码是如何编写的:-)

编辑:

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));
 }
}

现在代码打印出来:

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

这仍然没有意义......而且非常脆弱。 如果两个对象像a.equals(b)那样相等,那么它们也必须等于b.equal(a),这与你的代码不同。

来自javadoc

它是对称的:对于任何非空引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)才应返回true。

所以,是的,本书中的示例可能与集合API的Javadoc相反,但原则是正确的。 不应该创建一个奇怪的行为或最终会出现问题的等于方法。

编辑2:

该文的关键点是:

在Sun目前的实现中,它恰好返回false,但这只是一个实现工件。 在另一个实现中,它可以很容易地返回true或抛出运行时异常。 一旦违反了equals合同,您就不知道其他对象在面对对象时的行为方式。

但是,鉴于Javadoc说它看起来似乎行为是固定的而不是实现工件。

如果它不在javadoc中,或者javadoc不是规范的一部分,那么它可能会在以后更改,代码将不再起作用。

在我现在看的书的副本(第2版)中,项目编号为8,关于对称性要求的整个部分显示得相当差。

您提到的特殊问题似乎是由于使用代码与实现过于接近而造成的,这掩盖了作者试图提出的观点。 我的意思是,我查看list.contains(s)并通过它看到ArrayList和String,关于返回true或抛出异常的所有推理对我来说list.contains(s)有意义,真的。

  • 我不得不将“使用代码”从实现中移开,以了解它的实现方式:

     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) } } } 

上面看起来很奇怪但只要list是我们的ArrayList而s是我们的String,测试通过。

现在,如果我们使用其他东西而不是String,会发生什么? 比如,如果我们传递new CIString("polish")作为第二个参数会发生什么?

看, 尽管通过第一次equals检查 ,断言在下一行失败 - 因为contains将为此对象返回true。


类似的推理适用于布洛赫提到异常的部分。 这次,我将第二个参数保留为String,但是对于第一个参数,想象一个除ArrayList之外的List实现(这是合法的不是它)。

  • 你看,List实现通常被允许从contains抛出ClassCastException,我们只需要得到一个完全相同的东西并将它用于我们的测试。 想到的一个可能是基于使用适当的比较器包裹在我们原始列表中的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 } }; } 

如果我们传递上面的列表和字符串“polish”来test什么? list.get(0).equals(s)仍将通过检查,但list.contains(s)将从TreeSet.contains()中抛出ClassCastException。

这似乎就像布洛赫在提到list.contains(s)可能会抛出异常时所list.contains(s)的情况一样 - 尽管经过了第一次equals检查

暂无
暂无

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

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