需要满足的条件:
类的每个实例本质上都是唯一的。
不关心类是否提供了“逻辑相等(logical equality)”的测试功能。
超类已经覆盖了equals,从超类继承过来的行为对于子类也是合适的。
类是私有的或是包级私有的,可以确定他的equals方法永远不会被调用。
需要覆盖equals:如果类具有自己特有的“逻辑相等”概念,而且超类还没有覆盖equals以实现期望的行为,这时我们就需要覆盖equals方法。
不需要覆盖equals:用实例受控确保“每个值至多只存在一个对象”的类。枚举类型就属于这种类。对于这样的类而言,逻辑相同与对象等同是一回事。
equals方法实现等价关系通用约定(equivalence relation):
自反性(reflexive):对于任何非null的引用值x,x.equals(x)必须返回true。
如果违背,当你把该类的实例添加到集合(collection)中,然后该集合的contains会告诉你,没有包含你刚刚添加的实例。
对称性(symmetric):对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true。
一个实例对比不区分了大小写,但是,反过来对比是区分的,就导致了不对称:
demo:
传递性(transitive):对于任何非null的引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也必须返回true。
子类增加的信息会影响到equals的比较结果。
demo:
那么改成:
可以解决错误的判断,但是却违反了传递性。
因为:
System.out.printf("%s %s %s%n",
p1.equals(p2), p2.equals(p3), p1.equals(p3));
返回了:true、true、false
里氏替换原则(Liskov substitution principle):一个类型的任何重要属性也将适用于他的子类型。
不错的权宜之计(workaround):使用复合。
一致性(consistent):对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致的返回true,或者一致的返回false。
都不要是equals方法依赖于不可靠的资源。
非空性(Non-nullity):对于任何非null的引用值x,x.equals(null)必须返回false。
高质量equals方法:
1、使用==操作符检查“参数是否为这个对象的引用”。
2、使用instanceof操作符检查“参数是否为正确的类型”。
3、把参数转换成正确的类型。
4、对于该类的每个“关键(significant)”域,检查参数中的域是否与该对象中对应的域相匹配。
这点myeclipse自动生成的并不完全完美,myeclipse生成如下:
else if (!name.equals(other.name))
return false;
但是,这条是要求你改成:
else if (name != other.name && !name.equals(other.name))
return false;
5、当你编写完成了equals方法之后,应该会问自己三个问题:他是否的对称的、传递的、一致的。
(覆盖equals时总要覆盖hashcode;不要企图让equals方法过于智能;不要将equals声明中的Object对象替换为其他的类型。)
尽量不要省略@Override。
类的每个实例本质上都是唯一的。
不关心类是否提供了“逻辑相等(logical equality)”的测试功能。
超类已经覆盖了equals,从超类继承过来的行为对于子类也是合适的。
类是私有的或是包级私有的,可以确定他的equals方法永远不会被调用。
需要覆盖equals:如果类具有自己特有的“逻辑相等”概念,而且超类还没有覆盖equals以实现期望的行为,这时我们就需要覆盖equals方法。
不需要覆盖equals:用实例受控确保“每个值至多只存在一个对象”的类。枚举类型就属于这种类。对于这样的类而言,逻辑相同与对象等同是一回事。
equals方法实现等价关系通用约定(equivalence relation):
自反性(reflexive):对于任何非null的引用值x,x.equals(x)必须返回true。
如果违背,当你把该类的实例添加到集合(collection)中,然后该集合的contains会告诉你,没有包含你刚刚添加的实例。
对称性(symmetric):对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true。
一个实例对比不区分了大小写,但是,反过来对比是区分的,就导致了不对称:
demo:
- // Broken - violates symmetry! - Pages 36-37
- public final class CaseInsensitiveString {
- private final String s;
- public CaseInsensitiveString(String s) {
- if (s == null)
- throw new NullPointerException();
- this.s = s;
- }
- // Broken - violates symmetry!
- @Override public boolean equals(Object o) {
- if (o instanceof CaseInsensitiveString)
- return s.equalsIgnoreCase(
- ((CaseInsensitiveString) o).s);
- if (o instanceof String) // One-way interoperability!
- return s.equalsIgnoreCase((String) o);
- return false;
- }
- //This version is correct.
- // @Override public boolean equals(Object o) {
- // return o instanceof CaseInsensitiveString &&
- // ((CaseInsensitiveString) o).s.equalsIgnoreCase(s);
- // }
- public static void main(String[] args) {
- CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
- String s = "polish";
- System.out.println(cis.equals(s) + " " + s.equals(cis));
- }
- }
传递性(transitive):对于任何非null的引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也必须返回true。
子类增加的信息会影响到equals的比较结果。
demo:
- // Simple immutable two-dimensional integer point class - Page 37
- import java.util.*;
- 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;
- }
- // Broken - violates Liskov substitution principle - Pages 39-40
- // @Override public boolean equals(Object o) {
- // if (o == null || o.getClass() != getClass())
- // return false;
- // Point p = (Point) o;
- // return p.x == x && p.y == y;
- // }
- // See Item 9
- @Override public int hashCode() {
- return 31 * x + y;
- }
- }
- // Attempting to add a value component to Point - Pages 37 - 38
- public class ColorPoint extends Point {
- private final Color color;
- public ColorPoint(int x, int y, Color color) {
- super(x, y);
- this.color = color;
- }
- // Broken - violates symmetry!
- @Override public boolean equals(Object o) {
- if (!(o instanceof ColorPoint))
- return false;
- return super.equals(o) && ((ColorPoint) o).color == color;
- }
- // Broken - violates transitivity!
- // @Override public boolean equals(Object o) {
- // if (!(o instanceof Point))
- // return false;
- //
- // // If o is a normal Point, do a color-blind comparison
- // if (!(o instanceof ColorPoint))
- // return o.equals(this);
- //
- // // o is a ColorPoint; do a full comparison
- // return super.equals(o) && ((ColorPoint)o).color == color;
- // }
- public static void main(String[] args) {
- // First equals function violates symmetry
- Point p = new Point(1, 2);
- ColorPoint cp = new ColorPoint(1, 2, Color.RED);
- System.out.println(p.equals(cp) + " " + cp.equals(p));
- // Second equals function violates transitivity
- ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
- Point p2 = new Point(1, 2);
- ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
- System.out.printf("%s %s %s%n",
- p1.equals(p2), p2.equals(p3), p1.equals(p3));
- }
- }
那么改成:
- @Override public boolean equals(Object o) {
- if (!(o instanceof Point))
- return false;
- // If o is a normal Point, do a color-blind comparison
- if (!(o instanceof ColorPoint))
- return o.equals(this);
- // o is a ColorPoint; do a full comparison
- return super.equals(o) && ((ColorPoint)o).color == color;
- }
可以解决错误的判断,但是却违反了传递性。
因为:
System.out.printf("%s %s %s%n",
p1.equals(p2), p2.equals(p3), p1.equals(p3));
返回了:true、true、false
里氏替换原则(Liskov substitution principle):一个类型的任何重要属性也将适用于他的子类型。
不错的权宜之计(workaround):使用复合。
- // Adds a value component without violating the equals contract
- public class ColorPoint {
- private final Point point;
- private final Color color;
- public ColorPoint(int x, int y, Color color) {
- if (color == null)
- throw new NullPointerException();
- point = new Point(x, y);
- this.color = color;
- }
- /**
- * Returns the point-view of this color point.
- */
- public Point asPoint() {
- return point;
- }
- @Override public boolean equals(Object o) {
- if (!(o instanceof ColorPoint))
- return false;
- ColorPoint cp = (ColorPoint) o;
- return cp.point.equals(point) && cp.color.equals(color);
- }
- @Override public int hashCode() {
- return point.hashCode() * 33 + color.hashCode();
- }
- }
一致性(consistent):对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致的返回true,或者一致的返回false。
都不要是equals方法依赖于不可靠的资源。
非空性(Non-nullity):对于任何非null的引用值x,x.equals(null)必须返回false。
高质量equals方法:
1、使用==操作符检查“参数是否为这个对象的引用”。
2、使用instanceof操作符检查“参数是否为正确的类型”。
3、把参数转换成正确的类型。
4、对于该类的每个“关键(significant)”域,检查参数中的域是否与该对象中对应的域相匹配。
这点myeclipse自动生成的并不完全完美,myeclipse生成如下:
else if (!name.equals(other.name))
return false;
但是,这条是要求你改成:
else if (name != other.name && !name.equals(other.name))
return false;
5、当你编写完成了equals方法之后,应该会问自己三个问题:他是否的对称的、传递的、一致的。
(覆盖equals时总要覆盖hashcode;不要企图让equals方法过于智能;不要将equals声明中的Object对象替换为其他的类型。)
尽量不要省略@Override。