/* * Created on 12-11-20 * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. * * Copyright @2012 the original author or authors. */ package ch05object; import ch00model.Color; import ch00model.ColoredPoint; import ch00model.MutablePoint; import ch00model.ReadonlyPoint; import java.util.HashSet; import java.util.Iterator; import java.util.Set; /** * 演示神奇的equals方法重写 * * @author XiongNeng * @version 1.0 * @since 12-11-20 */ public class MagicEquals { /** * 陷阱1:定义错误equals方法签名(signature) */ public void trap1() { ReadonlyPoint p1 = new ReadonlyPoint(1, 2); ReadonlyPoint p2 = new ReadonlyPoint(1, 2); ReadonlyPoint q = new ReadonlyPoint(2, 3); System.out.println(p1.equals(p2)); // prints true System.out.println(p1.equals(q)); // prints false HashSet<ReadonlyPoint> coll = new HashSet<ReadonlyPoint>(); coll.add(p1); coll.add(p2); System.out.println(coll.contains(p1)); // prints true System.out.println(coll.contains(p2)); // prints true /** * 结果分析: * 在Java中重载被解析为静态的参数类型而非运行期的类型 * HasSet在加入新的元素的时候,会调用泛型方法equals, * 而此时的泛型方法早就被erase到Object参数类型了。 * 因此如果想要重写equals方法,参数必须是Object类型 */ } /** * 陷阱2:重载了equals的但没有同时重载hashCode的方法 */ public void trap2() { ReadonlyPoint p1 = new ReadonlyPoint(1, 2); ReadonlyPoint p2 = new ReadonlyPoint(1, 2); HashSet<ReadonlyPoint> coll = new HashSet<ReadonlyPoint>(); coll.add(p1); System.out.println(coll.contains(p2)); // prints false /** * 结果分析: * 虽然p1和p2是相等的equals返回true,但是没有重写hashcode的值, * 那么根据默认的内存地址的hashcode极有可能不等 */ } /** * 陷阱3:建立在会变化字段上的equals定义 */ public void trap3() { MutablePoint point = new MutablePoint(1, 2); HashSet<MutablePoint> set = new HashSet<MutablePoint>(); set.add(point); System.out.println(set.contains(point)); // prints true point.setX(8); System.out.println(set.contains(point)); // prints false // 但是通过迭代器来查找的话,是可以找到的,嘿嘿。 Iterator<MutablePoint> it = set.iterator(); boolean containedP = false; while (it.hasNext()) { MutablePoint nextP = it.next(); if (nextP.equals(point)) { containedP = true; break; } } System.out.println(containedP); // prints true /** * 结果分析: * HashSet的contains方法是通过hashcode去查找定位的,如果期间hashcode改变了,那么很可能就找不到了 * 而迭代器就是一个一个元素挨个循环,不会去查hashcode,那么是可以找到的 */ } /** * 陷阱4:不满足等价关系的equals错误定义 * Object中的equals的规范阐述了equals方法必须实现在非null对象上的等价关系: * 1. 自反原则:对于任何非null值X,表达式x.equals(x)总返回true。 * 2. 等价性:对于任何非空值x和y,那么当且仅当y.equals(x)返回真时,x.equals(y)返回真。 * 3. 传递性:对于任何非空值x,y,和z,如果x.equals(y)返回真,且y.equals(z)也返回真,那么x.equals(z)也应该返回真。 * 4. 一致性:对于非空x,y,多次调用x.equals(y)应该一致的返回真或假。提供给equals方法比较使用的信息不应该包含改过的信息。 * 5. 对于任何非空值x,x.equals(null)应该总返回false. */ public void trap4() { ReadonlyPoint p = new ReadonlyPoint(1, 2); ColoredPoint cp = new ColoredPoint(1, 2, Color.INDIGO); ReadonlyPoint pAnon = new ReadonlyPoint(1, 1) { @Override public int getY() { return 2; } }; Set<ReadonlyPoint> coll = new java.util.HashSet<ReadonlyPoint>(); coll.add(p); System.out.println(coll.contains(p)); // 打印 true System.out.println(coll.contains(cp)); // 打印 false System.out.println(coll.contains(pAnon)); // 打印 true } public static void main(String[] args) { MagicEquals magic = new MagicEquals(); magic.trap1(); magic.trap2(); magic.trap3(); magic.trap4(); } }