第8条:覆盖equals时请遵守通用约定

需要满足的条件: 
类的每个实例本质上都是唯一的。 
不关心类是否提供了“逻辑相等(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:
  1. // Broken - violates symmetry! - Pages 36-37  
  2.   
  3. public final class CaseInsensitiveString {  
  4.     private final String s;  
  5.   
  6.     public CaseInsensitiveString(String s) {  
  7.         if (s == null)  
  8.             throw new NullPointerException();  
  9.         this.s = s;  
  10.     }  
  11.   
  12.     // Broken - violates symmetry!  
  13.     @Override public boolean equals(Object o) {  
  14.         if (o instanceof CaseInsensitiveString)  
  15.             return s.equalsIgnoreCase(  
  16.                 ((CaseInsensitiveString) o).s);  
  17.         if (o instanceof String)  // One-way interoperability!  
  18.             return s.equalsIgnoreCase((String) o);  
  19.         return false;  
  20.     }  
  21.   
  22.     //This version is correct.  
  23. //   @Override public boolean equals(Object o) {  
  24. //       return o instanceof CaseInsensitiveString &&  
  25. //           ((CaseInsensitiveString) o).s.equalsIgnoreCase(s);  
  26. //   }  
  27.   
  28.     public static void main(String[] args) {  
  29.         CaseInsensitiveString cis = new CaseInsensitiveString("Polish");  
  30.         String s = "polish";  
  31.         System.out.println(cis.equals(s) + "  " + s.equals(cis));  
  32.     }  
  33. }  


传递性(transitive):对于任何非null的引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也必须返回true。 
子类增加的信息会影响到equals的比较结果。 
demo:
  1. // Simple immutable two-dimensional integer point class - Page 37  
  2.   
  3. import java.util.*;  
  4.   
  5. public class Point {  
  6.     private final int x;  
  7.     private final int y;  
  8.   
  9.     public Point(int x, int y) {  
  10.         this.x = x;  
  11.         this.y = y;  
  12.     }  
  13.   
  14.     @Override public boolean equals(Object o) {  
  15.         if (!(o instanceof Point))  
  16.             return false;  
  17.         Point p = (Point)o;  
  18.         return p.x == x && p.y == y;  
  19.     }  
  20.   
  21.     // Broken - violates Liskov substitution principle - Pages 39-40  
  22. //  @Override public boolean equals(Object o) {  
  23. //      if (o == null || o.getClass() != getClass())  
  24. //          return false;  
  25. //      Point p = (Point) o;  
  26. //      return p.x == x && p.y == y;  
  27. //  }  
  28.   
  29.     // See Item 9  
  30.     @Override public int hashCode()  {  
  31.         return 31 * x + y;  
  32.     }  
  33. }  
  34.   
  35. // Attempting to add a value component to Point - Pages 37 - 38  
  36.   
  37. public class ColorPoint extends Point {  
  38.     private final Color color;  
  39.   
  40.     public ColorPoint(int x, int y, Color color) {  
  41.         super(x, y);  
  42.         this.color = color;  
  43.     }  
  44.   
  45.     // Broken - violates symmetry!  
  46.     @Override public boolean equals(Object o) {  
  47.         if (!(o instanceof ColorPoint))  
  48.             return false;  
  49.         return super.equals(o) && ((ColorPoint) o).color == color;  
  50.     }  
  51.   
  52.     // Broken - violates transitivity!  
  53. //  @Override public boolean equals(Object o) {  
  54. //      if (!(o instanceof Point))  
  55. //          return false;  
  56. //  
  57. //      // If o is a normal Point, do a color-blind comparison  
  58. //      if (!(o instanceof ColorPoint))  
  59. //          return o.equals(this);  
  60. //  
  61. //      // o is a ColorPoint; do a full comparison  
  62. //      return super.equals(o) && ((ColorPoint)o).color == color;  
  63. //  }  
  64.       
  65.       
  66.   
  67.     public static void main(String[] args) {  
  68.         // First equals function violates symmetry  
  69.         Point p = new Point(12);  
  70.         ColorPoint cp = new ColorPoint(12, Color.RED);  
  71.         System.out.println(p.equals(cp) + " " + cp.equals(p));  
  72.   
  73.         // Second equals function violates transitivity  
  74.         ColorPoint p1 = new ColorPoint(12, Color.RED);  
  75.         Point p2 = new Point(12);  
  76.         ColorPoint p3 = new ColorPoint(12, Color.BLUE);  
  77.         System.out.printf("%s %s %s%n",  
  78.                           p1.equals(p2), p2.equals(p3), p1.equals(p3));  
  79.     }  
  80. }  


那么改成:
  1. @Override public boolean equals(Object o) {  
  2.     if (!(o instanceof Point))  
  3.         return false;  
  4.   
  5.     // If o is a normal Point, do a color-blind comparison  
  6.     if (!(o instanceof ColorPoint))  
  7.         return o.equals(this);  
  8.   
  9.     // o is a ColorPoint; do a full comparison  
  10.     return super.equals(o) && ((ColorPoint)o).color == color;  
  11. }  

可以解决错误的判断,但是却违反了传递性。 
因为: 
System.out.printf("%s %s %s%n", 
p1.equals(p2), p2.equals(p3), p1.equals(p3)); 
返回了:true、true、false 
里氏替换原则(Liskov substitution principle):一个类型的任何重要属性也将适用于他的子类型。 

不错的权宜之计(workaround):使用复合。
  1.                           
  2. // Adds a value component without violating the equals contract   
  3. public class ColorPoint {  
  4.     private final Point point;  
  5.     private final Color color;  
  6.   
  7.     public ColorPoint(int x, int y, Color color) {  
  8.         if (color == null)  
  9.             throw new NullPointerException();  
  10.         point = new Point(x, y);  
  11.         this.color = color;  
  12.     }  
  13.   
  14.     /** 
  15.      * Returns the point-view of this color point. 
  16.      */  
  17.     public Point asPoint() {  
  18.         return point;  
  19.     }  
  20.   
  21.     @Override public boolean equals(Object o) {  
  22.         if (!(o instanceof ColorPoint))  
  23.             return false;  
  24.         ColorPoint cp = (ColorPoint) o;  
  25.         return cp.point.equals(point) && cp.color.equals(color);  
  26.     }  
  27.   
  28.     @Override public int hashCode() {  
  29.         return point.hashCode() * 33 + color.hashCode();  
  30.     }  
  31. }  


一致性(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。 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值