有效Java中的第9项(等于合同):示例正确吗?

有效Java中的第9项(等于合同):示例正确吗?,java,collections,effective-java,Java,Collections,Effective Java,布洛赫的精彩著作《有效Java》指出,如果等于是不对称的,那么包含的集合的行为是不确定的 在他给出的例子中(在下面稍加修改后复制),布洛赫说他看到了“假”,但也可能看到了真或例外 如果标准未指定集合中每个项目的是否包含(对象o)检查e.equals(o)或o.equals(e),并且前者已实现,则可以看到“true”。然而,报告明确指出,必须是后者(这是我观察到的) 因此,我看到的唯一可能性是“错误”或可能是例外(但似乎排除了后者) 我理解更广泛的观点,不对称的equalsis很可能会导致集合之

布洛赫的精彩著作《有效Java》指出,如果
等于
是不对称的,那么
包含的集合的行为是不确定的

在他给出的例子中(在下面稍加修改后复制),布洛赫说他看到了“假”,但也可能看到了真或例外

如果标准未指定集合中每个项目的
是否包含(对象o)
检查
e.equals(o)
o.equals(e)
,并且前者已实现,则可以看到“true”。然而,报告明确指出,必须是后者(这是我观察到的)

因此,我看到的唯一可能性是“错误”或可能是例外(但似乎排除了后者)

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

我错过什么了吗

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));
 }
}
import java.util.List;
导入java.util.ArrayList;
类顺串{
私有最终字符串s;
公共CI字符串(字符串s){
这个.s=s;
}
@重写公共布尔等于(对象o){
System.out.println(“从“+this.s”调用CIString.equals);
if(i字符串的o实例)
返回s.equalsIgnoreCase(((CIString)o.s);
如果(字符串的o实例)
返回s.equalsIgnoreCase((字符串)o);
返回false;
}
//重写等于时始终重写哈希代码
//这是一个糟糕的哈希函数(一切都会发生冲突->性能糟糕!)
//但它在语义上是合理的,更多细节请参见有效Java中的第10项。
@重写公共int hashCode(){return 42;}
}
公共类CI{
公共静态void main(字符串[]args){
CIString a=新CIString(“波兰”);
字符串s=“波兰”;
列表=新的ArrayList();
列表.添加(a);
System.out.println(“列表包含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));
    }
}
这仍然没有意义。。。而且非常脆弱。如果两个对象像a.equals(b)一样相等,那么它们也必须像b.equal(a)一样相等,这不是代码的情况

从:

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

因此,是的,本书中的示例可能与collections API的Javadoc相反,但原理是正确的。我们不应该创建一个行为异常的equals方法,否则最终会出现问题

编辑2:

正文的要点是:

在Sun当前的实现中,它恰好返回false,但是 这只是一个实现工件。在另一个实现中,它 可以很容易地返回true或抛出运行时异常。一旦 你违反了平等合同,你根本不知道其他人 当面对对象时,对象将表现出行为

然而,考虑到Javadoc所说的,行为似乎是固定的,而不是实现工件


如果它不在javadoc中,或者javadoc不打算成为规范的一部分,那么它可能会在以后更改,代码将不再工作。

在我现在看的书(第二版)的副本中,项目编号是8,而关于对称性的整个章节都非常糟糕

您提到的特定问题似乎是由于使用代码过于接近实现而导致的,模糊了作者试图表达的观点。我的意思是,我看了一下
列表.contains(s)
,我看到了ArrayList和String,所有关于返回true或抛出异常的推理对我来说毫无意义,真的

  • 我不得不将“使用代码”从实现中进一步移开,以了解它可能是怎样的:

    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)
            }
        }
    }
    
如果我们将上面的列表和字符串“polish”传递给
测试,会发生什么
list.get(0).equals(s)
仍将通过检查,但
list.contains(s)
将从中抛出ClassCastException


这似乎就像布洛赫在提到
list.contains
可能会再次抛出异常时想到的情况一样,尽管首先通过了
equals
检查。

细微的挑剔:您的示例没有实现或暗示实现hashcode()方法,这意味着CIString没有实现契约,一些相等的对象将具有不相等的哈希码。我知道这不是你的问题,但为了以后在谷歌上搜索你的问题的读者,你能至少写一个关于这个问题的注释吗?我添加了一个哈希函数,并对它的必要性进行了注释。请参阅我的第二次编辑以获得澄清,你所指的方式是JDK 1.4-下面使用泛型的代码看起来有点奇怪。无论如何,javadoc文本仍然与ion JDK 1.5/1.6相同,所以这是一个次要的点,当您运行示例时,他的“
调用CIString.equals…
”消息是否出现在输出中?只是澄清一下,我在Bloch得到了相同的输出,
列表包含s:false
-如果将println消息写成
“list.contains(a)返回”+list.contains(a)
你的观点是正确的,但是我的问题的核心是布洛赫的主张是否成立,也就是说,你可能会将
true
(或例外)视为可能性(在我看来,答案仍然是否定的).是的,Sun当前实现中的单词也引起了我的注意。我花了几个小时研究JDK 1.6、1.5甚至1.4 javadocs,但它对我来说仍然毫无意义。我能想象的理解它的唯一方法是将Sun这个词视为一个拼写错误,并按照当前实现中的方式阅读——假设这是指当前的imple
list contains s:false
Calling CIString.equals from Polish
list contains a:true
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<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
        }
    };
}