《Effective Java》中的第 9 项(等于合同):示例正确吗?
Bloch 的精彩著作《Effective Java》指出,如果 equals
不是对称的,那么 Collections 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));
}
}
Bloch's wonderful book "Effective Java" points out that if equals
is not symmetric then the behavior of Collections contains
is indeterminate.
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. However, the Collections Javadoc clearly states that it has to be the latter (and it's what I observed).
So the only possibilities I see are "false" or possibly an exception (but the String Javadoc seems to preclude the latter).
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.
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));
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
现在是凌晨,所以也许我错过了你问题的真正要点,这段代码将会失败:
至少你的代码找到它而我的代码没有找到它是很奇怪的(从理智的角度来看,并不是说这显然是您的代码的编写方式:-)
编辑:
现在代码打印出来:
这仍然没有意义......并且非常脆弱。如果两个对象像 a.equals(b) 一样相等,那么它们也必须像 b.equal(a) 一样相等,但您的代码并非如此。
来自 javadoc:
所以,是的,书中的示例可能与集合 API 的 Javadoc 相矛盾,但原理是正确的。人们不应该创建一个行为奇怪的 equals 方法,否则最终会出现问题。
编辑2:
文本的要点是:
然而,考虑到 Javadoc 所说的内容,该行为似乎是固定的,而不是实现工件。
如果它不在 javadoc 中,或者 javadoc 不属于规范的一部分,那么它可能会在以后发生更改,并且代码将不再工作。
It is early in the morning, so perhaps I am missing the true point of your question, this code will fail:
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:
Now the code prints out:
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.
From the javadoc:
So, yes, the example in the book may be contrary to the Javadoc of the collections API, but the principle is correct. One should not create an equals method that behaves oddly or eventually problems will arise.
Edit 2:
The key point of the text is:
However, given that the Javadoc says what it says it would seem that the behaviour is fixed not an implementation artifact.
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.
在我现在看的书的副本(第二版)中,项目编号是 8,并且关于对称性要求的整个部分呈现得相当糟糕。
您提到的特定问题似乎是由于使用代码过于接近实现而引起的,掩盖了作者试图提出的观点。我的意思是,我查看 list.contains(s) 并通过它看到 ArrayList 和 String,所有关于返回 true 或抛出异常的推理对我来说毫无意义,真的。
我必须将“使用代码”移离实现更远才能了解它可能是怎样的:
上面看起来很奇怪,但只要
list
是我们的 ArrayList 并且s
是我们的字符串,测试通过。现在,如果我们使用其他东西而不是字符串会发生什么?比如说,如果我们传递 new CIString("polish") 作为第二个参数,会发生什么?
看,尽管通过了第一个等于检查,但断言在下一行失败了 - 因为 contains 将为该对象返回 true。
类似的推理也适用于布洛赫提到例外的部分。这次,我将第二个参数保留为 String,但对于第一个参数,想象了 ArrayList 之外的 List 实现(这是合法的,不是吗)。
你看,List 实现通常允许从
contains
抛出 ClassCastException,我们只需要获得一个完全执行此操作的实现并将其用于我们的测试。我想到的可能是基于带有适当比较器的原始列表周围的 TreeSet。如果我们将上面的列表和字符串“polish”传递给
test
会发生什么?list.get(0).equals(s)
仍会通过检查,但list.contains(s)
会从 TreeSet.contains()。这似乎就像 Bloch 在提到
list.contains(s)
可能抛出异常时想到的情况 - 再次,尽管通过第一个equals
检查。。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.
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.I had to move the "usage code" further away from implementation to get the idea of how it may be:
Above looks weird but as long as
list
is our ArrayList ands
is our String, test passes.Now, what will happen if we use something else instead of String? say, what happens if we pass
new CIString("polish")
as the second argument?Look, despite passing through first
equals
check, assertion fails at the next line - because contains will return true for this object.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).
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. One that comes to mind could be based on TreeSet wrapped around our original list with appropriate comparator.What happens if we pass list like above and String "polish" to
test
?list.get(0).equals(s)
will still pass the check butlist.contains(s)
will throw ClassCastException from TreeSet.contains().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 firstequals
check.