Bloch的精彩书籍“Effective Java”指出,如果equals
不对称,则集合contains
的行为是不确定的。
在他给出的例子中(下面稍作修改),布洛赫说他看到了“假”,但也可能看到了真或异。
如果标准没有指定contains(Object o)
是否对集合中的每个项目检查e.equals(o)
或o.equals(e)
,并且前者已实施,则可以看到“true”。然而,Collections Javadoc清楚地表明它必须是后者(这是我观察到的)。
所以我看到的唯一可能性是“假”或可能是异常(但String 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));
}
}
答案 0 :(得分:3)
现在是一大早,所以也许我错过了你问题的真实点,这段代码会失败:
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)返回时,x.equals(y)才应返回true 真。
所以,是的,本书中的示例可能与集合API的Javadoc相反,但原则是正确的。不应该创建一个行为奇怪或最终会出现问题的等于方法。
编辑2:
案文的重点是:
在Sun的当前实现中,它恰好返回false,但是 这只是一个实现工件。在另一个实现中,它 可以很容易地返回true或抛出运行时异常。一旦 你违反了平等合同,你根本就不知道其他如何 对象在面对你的对象时会表现出来。
然而,鉴于Javadoc说它看起来似乎行为被修复而不是实现工件。
如果它不在javadoc中,或者javadoc不是规范的一部分,那么它可能会在以后更改,代码将不再有效。
答案 1 :(得分:1)
在我现在看的那本书(第2版)的副本中,项目编号为8,关于对称要求的整个部分显示得非常差。
您提到的特殊问题似乎是由于使用代码与实现过于接近而造成的,这掩盖了作者试图提出的观点。我的意思是,我看list.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)
}
}
}
上面看起来很奇怪,但只要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)
可能引发异常时所想到的那样 - 再次,尽管经过了第一次equals
检查。