看起来非常的奇怪。p去那里去了?如果你通过集合的迭代器来检查p是否包含,你将会得到更奇怪的结果。
Iterator<Point> it = coll.iterator(); System.out.println(containedP); // 打印 true |
结果是,集合中不包含p,但是p在集合的元素中!到底发生了什么!当然,所有的这一切都是在x域的修改后才发生的,p最终的的hashCode是在集合coll错误的哈希桶中。即,原始哈希桶不再有其新值对应的哈希码。换句话说,p已经在集合coll的是视野范围之外,虽然他仍然属于coll的元素。
从这个例子所得到的教训是,当equals和hashCode依赖于会变化的状态时,那么就会给用户带来问题。如果这样的对象被放入到集合中,用户必须小心,不要修改这些这些对象所依赖的状态,这是一个小陷阱。如果你需要根据对象当前的状态进行比较的话,你应该不要再重定义equals,应该起其他的方法名字而不是equals。对于我们的Point类的最后的定义,我们最好省略掉hashCode的重载,并将比较的方法名命名为equalsContents,或其他不同于equals的名字。那么Point将会继承原来默认的equals和hashCode的实现,因此当我们修改了x域后p依然会呆在其原来在容器中应该在位置。
陷阱4:不满足等价关系的equals错误定义
Object中的equals的规范阐述了equals方法必须实现在非null对象上的等价关系:
● 自反原则:对于任何非null值X,表达式x.equals(x)总返回true。
● 等价性:对于任何非空值x和y,那么当且仅当y.equals(x)返回真时,x.equals(y)返回真。
● 传递性:对于任何非空值x,y,和z,如果x.equals(y)返回真,且y.equals(z)也返回真,那么x.equals(z)也应该返回真。
● 一致性:对于非空x,y,多次调用x.equals(y)应该一致的返回真或假。提供给equals方法比较使用的信息不应该包含改过的信息。
● 对于任何非空值x,x.equals(null)应该总返回false.
Point类的equals定义已经被开发成了足够满足equals规范的定义。然而,当考虑到继承的时候,事情就开始变得非常复杂起来。比如说有一个Point的子类ColoredPoint,它比Point多增加了一个类型是Color的color域。假设Color被定义为一个枚举类型:
public enum Color { RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET; } |
ColoredPoint重载了equals方法,并考虑到新加入color域,代码如下:
public class ColoredPoint extends Point { // Problem: equals not symmetric private final Color color; public ColoredPoint(int x, int y, Color color) { @Override public boolean equals(Object other) { |