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

本文详细阐述了Java类中equals方法的正确实现方式,包括满足的条件、覆盖equals的方法场景、传递性、一致性等特性,以及如何避免常见的错误如违反对称性和传递性。同时,提供了一个案例来说明正确实现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. publicfinalclassCaseInsensitiveString {
  3. privatefinalString s;
  4. publicCaseInsensitiveString(String s) {
  5. if(s ==null)
  6. thrownewNullPointerException();
  7. this.s = s;
  8. }
  9. // Broken - violates symmetry!
  10. @Overridepublicbooleanequals(Object o) {
  11. if(oinstanceofCaseInsensitiveString)
  12. returns.equalsIgnoreCase(
  13. ((CaseInsensitiveString) o).s);
  14. if(oinstanceofString)// One-way interoperability!
  15. returns.equalsIgnoreCase((String) o);
  16. returnfalse;
  17. }
  18. //This version is correct.
  19. // @Override public boolean equals(Object o) {
  20. // return o instanceof CaseInsensitiveString &&
  21. // ((CaseInsensitiveString) o).s.equalsIgnoreCase(s);
  22. // }
  23. publicstaticvoidmain(String[] args) {
  24. CaseInsensitiveString cis =newCaseInsensitiveString("Polish");
  25. String s ="polish";
  26. System.out.println(cis.equals(s) +""+ s.equals(cis));
  27. }
  28. }


传递性(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. importjava.util.*;
  3. publicclassPoint {
  4. privatefinalintx;
  5. privatefinalinty;
  6. publicPoint(intx,inty) {
  7. this.x = x;
  8. this.y = y;
  9. }
  10. @Overridepublicbooleanequals(Object o) {
  11. if(!(oinstanceofPoint))
  12. returnfalse;
  13. Point p = (Point)o;
  14. returnp.x == x && p.y == y;
  15. }
  16. // Broken - violates Liskov substitution principle - Pages 39-40
  17. //@Override public boolean equals(Object o) {
  18. //if (o == null || o.getClass() != getClass())
  19. //return false;
  20. //Point p = (Point) o;
  21. //return p.x == x && p.y == y;
  22. //}
  23. // See Item 9
  24. @OverridepublicinthashCode(){
  25. return31* x + y;
  26. }
  27. }
  28. // Attempting to add a value component to Point - Pages 37 - 38
  29. publicclassColorPointextendsPoint {
  30. privatefinalColor color;
  31. publicColorPoint(intx,inty, Color color) {
  32. super(x, y);
  33. this.color = color;
  34. }
  35. // Broken - violates symmetry!
  36. @Overridepublicbooleanequals(Object o) {
  37. if(!(oinstanceofColorPoint))
  38. returnfalse;
  39. returnsuper.equals(o) && ((ColorPoint) o).color == color;
  40. }
  41. // Broken - violates transitivity!
  42. //@Override public boolean equals(Object o) {
  43. //if (!(o instanceof Point))
  44. //return false;
  45. //
  46. //// If o is a normal Point, do a color-blind comparison
  47. //if (!(o instanceof ColorPoint))
  48. //return o.equals(this);
  49. //
  50. //// o is a ColorPoint; do a full comparison
  51. //return super.equals(o) && ((ColorPoint)o).color == color;
  52. //}
  53. publicstaticvoidmain(String[] args) {
  54. // First equals function violates symmetry
  55. Point p =newPoint(1,2);
  56. ColorPoint cp =newColorPoint(1,2, Color.RED);
  57. System.out.println(p.equals(cp) +" "+ cp.equals(p));
  58. // Second equals function violates transitivity
  59. ColorPoint p1 =newColorPoint(1,2, Color.RED);
  60. Point p2 =newPoint(1,2);
  61. ColorPoint p3 =newColorPoint(1,2, Color.BLUE);
  62. System.out.printf("%s %s %s%n",
  63. p1.equals(p2), p2.equals(p3), p1.equals(p3));
  64. }
  65. }


那么改成:
  1. @Overridepublicbooleanequals(Object o) {
  2. if(!(oinstanceofPoint))
  3. returnfalse;
  4. // If o is a normal Point, do a color-blind comparison
  5. if(!(oinstanceofColorPoint))
  6. returno.equals(this);
  7. // o is a ColorPoint; do a full comparison
  8. returnsuper.equals(o) && ((ColorPoint)o).color == color;
  9. }

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

不错的权宜之计(workaround):使用复合。
  1. // Adds a value component without violating the equals contract
  2. publicclassColorPoint {
  3. privatefinalPoint point;
  4. privatefinalColor color;
  5. publicColorPoint(intx,inty, Color color) {
  6. if(color ==null)
  7. thrownewNullPointerException();
  8. point =newPoint(x, y);
  9. this.color = color;
  10. }
  11. /**
  12. * Returns the point-view of this color point.
  13. */
  14. publicPoint asPoint() {
  15. returnpoint;
  16. }
  17. @Overridepublicbooleanequals(Object o) {
  18. if(!(oinstanceofColorPoint))
  19. returnfalse;
  20. ColorPoint cp = (ColorPoint) o;
  21. returncp.point.equals(point) && cp.color.equals(color);
  22. }
  23. @OverridepublicinthashCode() {
  24. returnpoint.hashCode() *33+ color.hashCode();
  25. }
  26. }


一致性(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。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值