若是类具备本身特有的“逻辑相等”概念,并且父类尚未重写 equals 方法以实现指望的行为时,就须要重写 equals 方法。java
这样作还可使得这个类的实例能够被用做 Map 的 Key,或者 Set 的元素,使 Map 或 Set 表现出预期的行为来。ide
在重写 equals 方法的时候,若是知足了如下任何一个条件,就正是所指望的结果:测试
自反性(reflexive):对于任何非 null 的引用值 x,x.equals(x) 必须返回 true;flex
对称性(symmetric):对于任何非 null 的引用值 x and y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 必须返回 true;this
传递性(consistent):对于任何非 null 的引用值 x y z,若是 x.equals(y) 返回 true,而且 y.equals(z) 返回 true,那么 x.equals(z) 也必须返回true;spa
一致性(consistent):对于任何非 null 的引用值 x and y,只要equals的比较操做在对象中所用的信息没有被修改,那么屡次调用 x.equals(y) 都会一致地返回 true,或者一致地返回 false;.net
非空性(non nullity):对于任何非 null 的引用值 x,x.equals(null) 必须返回 false;code
通常不会违背这个原则,若是违背了,那么出现的现象会是将该类的实例添加到集合后,调用 contains 方法查找这个实例,会获得一个 false。对象
对称性简单说是,任何两个对象对于它们是否相等的问题都必须保持一致, x 等于 y 那么 y 也要等于 x。举个违反这个原则的例子:继承
package test.ch01; public class CaseInsensitiveString { private final String s; public CaseInsensitiveString(String s) { if (s == null) { throw new NullPointerException(); } this.s = s; } @Override public boolean equals(Object o) { if (o instanceof CaseInsensitiveString) { return s.equalsIgnoreCase(((CaseInsensitiveString) o).s); } if (o instanceof String) { return s.equalsIgnoreCase((String) o); } return false; } }
在这个类中,equals 要作到与普通的字符串比较时不区分大小写,其问题在于 String 类中的 equals 方法并不知道不区分大小写,所以反过来比较并不成立,违反了对称性。
package test.ch01; public class Test { public static void main(String[] args) { CaseInsensitiveString cis = new CaseInsensitiveString("Hello"); String s = "hello"; System.out.println(cis.equals(s)); // true System.out.println(s.equals(cis)); // false } }
解决这个问题,只须要把企图与 String 互操做的代码从 equals 方法中去掉就能够了:
package test.ch01; public class CaseInsensitiveString { private final String s; public CaseInsensitiveString(String s) { if (s == null) { throw new NullPointerException(); } this.s = s; } @Override public boolean equals(Object o) { return o instanceof CaseInsensitiveString && ((CaseInsensitiveString) o).s.equalsIgnoreCase(s); } }
package test.ch01; public class Test { public static void main(String[] args) { CaseInsensitiveString cis = new CaseInsensitiveString("Hello"); String s = "hello"; CaseInsensitiveString cis1 = new CaseInsensitiveString("hello"); System.out.println(cis.equals(s)); // false System.out.println(s.equals(cis)); // false System.out.println(cis.equals(cis1)); // true } }
传递性要求 x 等于 y,y 等于 z,那么 x 也要等于 z。可是,此处有很是重要的一点,面向对象语言关于等价关系的一个基本问题:
没法在扩展可实例化的类的同时,即增长新的值组件,同时又保留 equals 约定,除非愿意放弃面向对象的抽象所带来的优点。
考虑两个类,它们是继承关系:
package test.ch01; public class Point { private final int x; private final int y; public Point(int x, int y) { this.x = x; this.y = y; } @Override public boolean equals(Object o) { if (!(o instanceof Point)) { return false; } Point p = (Point) o; return p.x == x && p.y == y; } }
package test.ch01; public class ColorPoint extends Point { private final Color color; public ColorPoint(int x, int y, Color color) { super(x, y); this.color = color; } @Override public boolean equals(Object o) { if (!(o instanceof ColorPoint)) { return false; } return super.equals(o) && ((ColorPoint) o).color == color; } }
若是 ColorPoint 类,不重写 equals,而是直接从 Point 继承过来,那么 ColorPoint 与 Point 比较时会忽略掉颜色。
可是若是按照上面代码,重写 equals,那么会违反一致性:
Point p1 = new Point(1, 2); ColorPoint cp1 = new ColorPoint(1, 2, Color.RED); System.out.println(p1.equals(cp1)); // true System.out.println(cp1.equals(p1)); // false
总之面向对象语言的等价交换关系是一个问题。
里氏替换原则(Liskov substitution principle)认为,一个类型的任何重要属性也将适用于他的子类,所以为该类型编写的任何方法,在它的子类型上也应该一样运行的很好。
虽然没有一种使人满意的办法解决上面的问题,可是仍是有一个不错的权宜之计:
package test.ch01; public class ColorPoint { private final Point point; private final Color color; public ColorPoint(Point point, Color color) { this.point = point; this.color = color; } @Override public boolean equals(Object o) { if (!(o instanceof Color)) { return false; } ColorPoint cp = (ColorPoint) o; return cp.point.equals(point) && cp.color.equals(color); } }
使用复合方式代替继承,能够解决上面的问题。
可变对象在不一样的时候能够与不一样的对象相等,不可变的对象则不会这样。若是认为它应该是不可变的,就必须保证 equals 方法知足:相等的对象永远相等,不想等的对象永远不相等。
不管类是不是不可变的,都不要使 equals 方法依赖不可靠的资源。
全部对象都必须不等于 null。
1.使用 == 操做符检查“参数是否为这个对象的引用”,若是比较操做有可能很昂贵,就值得先这么作;
2.使用instanceof检查“参数类型是否正确”;
3.把参数转换成正确的类型,在instanceof后,因此会保证成功;
4.对每一个关键域,检查参数中的域是否与该对象中对应的域相匹配,float 和 double须要用 Float.compare 和 Double.compare 比较,集合用 Arrays.equals 比较;
5.对于引用域能够为 null 的状况,为了不致使 NPE,能够写成 :
(field == o.filed || (field != null && field.equals(o.field)))
6.最早比较最有可能不一致的域,或者是开销低的域,最好是同时知足这两个条件;
7.重写 equals 时总要重写 hashCode;
8.不要让 equals 方法过于智能;
9.不要将 equals 声明中的 Object 对象替换为其余类型;
10.最好用 @Override 注解描述 equals;