注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

北漂的小羊

Java编程,开发者,程序员,软件开发,编程,代码。新浪微博号:IT国子监

 
 
 

日志

 
 
关于我

在这里是面向程序员的高品质IT技术学习社区,是程序员学习成长的地方。让我们更好地用技术改变世界。请关注新浪微博号: IT国子监(http://weibo.com/itguozijian)

网易考拉推荐

实现重写equals方法时大家常常忽略的一个细节(类继承关系)  

2012-12-03 08:20:03|  分类: JAVA |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

什么时候需要重写equals()? 

  我们知道每一个java类都继承自Object类,equals()是Object类中提供的方法之一。那么,让我们先来看看Object#equals()在Java中的原代码: 

public boolean equals(Object obj) 

  return (this == obj); 

}    

可以看出,只有当一个实例等于它本身的时候,equals()才会返回true值。通俗地说,此时比较的是两个引用是否指向内存中的同一个对象,也可以称做是否实例相等。而我们在使用equals()来比较两个指向值对象的引用的时候,往往希望知道它们逻辑上是否相等,而不是它们是否指向同一个对象。在这样的情况下, 如果超类也没有重写equals()以实现期望的行为,这时我们就需要重写equals方法。


equal的具体步骤

书上说,我们要比较两个对象是否相等的时候需要定义equals()和hashCode()两个方法。equals方法的完整签名是:重写equals(Object o). 定义重写equals方法的过程无非以下几个步骤:

1. 比较传进来的对象是否为空,如果空则返回false。

2. 比较传进来的对象类型是否相同,不同则返回false.

3. 再根据定义的对象比较每个字段是否相等,不等则返回false.

 

为什么继承关系时需要注意重写equals()方法?

我们都知道,重写equals()方法意味着一种可传递和交换的对等关系,也就是说,假如A.equals(B)的结果为true的话,那么B.equals(A)的结果也是true.

      如果引入了类继承关系的时候,子类需要定义同样的方法时可以重用父类的方法,只针对自己的部分进行修改。但是在稍微不注意的情况下可能会打破前面的这种假定。

继承关系的一个实现

        在不考虑hashCode方法的情况下,下面是一个包含父类和子类的重写equals实现:

 

Java代码 
  1. class BaseClass  
  2. {  
  3.     private int x;  
  4.   
  5.     public BaseClass(int i)  
  6.     {  
  7.         x = i;  
  8.     }  
  9.   
  10.     public boolean equals(Object rhs)  
  11.     {  
  12.         if(!(rhs instanceof rhs))  
  13.             return false;  
  14.   
  15.         return x == ((BaseClass)rhs).x;  
  16.     }  
  17. }  
  18.   
  19. class DerivedClass extends BaseClass  
  20. {  
  21.     private int y;  
  22.   
  23.     public DerivedClass(int i, int j)  
  24.     {  
  25.         super(i);  
  26.         y = j;  
  27.     }  
  28.   
  29.     public boolean equals(Object rhs)  
  30.     {  
  31.         if(!(rhs instanceof DerivedClass))  
  32.             return false;  
  33.   
  34.         return super.equals(rhs) &&  
  35.             y == ((DerivedClass)rhs).y;  
  36.     }  
  37. }  
  38.   
  39. public class EqualsWithInheritance  
  40. {  
  41.     public static void main(String[] args)  
  42.     {  
  43.         BaseClass a = new BaseClass(5);  
  44.         DerivedClass b = new DerivedClass(58);  
  45.         DerivedClass c = new DerivedClass(58);  
  46.   
  47.         System.out.println("b.equals(c): " + b.equals(c));  
  48.         System.out.println("a.equals(b): " + a.equals(b));  
  49.         System.out.println("b.equals(a): " + b.equals(a));  
  50.     }  
  51. }  

 

 如果我们运行上面的代码,会发现一个比较奇怪的结果:

 

  1. b.equals(c): true  
  2. a.equals(b): true  
  3. b.equals(a): false  

 

 既然我们前面a.equals(b)为true了,为什么后面b.equals(a)的结果反而成为了false呢?这样的实现肯定有问题,很明显,它打破了重写equals()的对等性。

问题分析

        如果我们仔细去看前面的代码实现的话,会发现问题很可能出现在父类和子类定义的equals()方法中。在父类中定义的equals方法子类中也定义了。所以当我们a.equals(b)调用的时候,因为a是父类创建的对象,所以执行的是父类里的equals方法,而b是子类的实例化对象,b.equals(a)执行的是子类中的equals方法。

        另外,还有一个问题就是instanceof。在java里面,instanceof表示测试它左边的对象是否为它右边类的实例。由于类的继承关系,我们可以说一个子类也是父类的实例。比如说,在java中,所有对象都是继承自Object,那么我们任意定义的一个类的实例对象调用instanceof Object返回的结果都为true.所以问题的根源就在于这个instanceof。

        再看我们的这个问题,我们本身的逻辑要求是父类的实例和子类的实例是不一样的,所以a.equals(b)和b.equals(a)都应该返回false.那么,现在问题就是,我们该用什么方法来比较父类和子类是不同的类型呢?查阅过文档之后,我们可以使用getClass()方法。这个方法表示返回该对象的运行时class。我们都知道,在jvm虚拟机中,每个类的详细类型信息会被定义在方法区中,并被多个线程所共享。这里将包括每个类的详细不同,比如说b是类DerivedClass实例化的对象,这样就可以实现他们的区分。

下面是修改后的实现代码:

 

Java代码 
  1. class BaseClass  
  2. {  
  3.     private int x;  
  4.   
  5.     public BaseClass(int i)  
  6.     {  
  7.         x = i;  
  8.     }  
  9.   
  10.     public boolean equals(Object rhs)  
  11.     {  
  12.         if(rhs == null || getClass() != rhs.getClass())  
  13.             return false;  
  14.   
  15.         return x == ((BaseClass)rhs).x;  
  16.     }  
  17. }  
  18.   
  19. class DerivedClass extends BaseClass  
  20. {  
  21.     private int y;  
  22.   
  23.     public DerivedClass(int i, int j)  
  24.     {  
  25.         super(i);  
  26.         y = j;  
  27.     }  
  28.   
  29.     public boolean equals(Object rhs)  
  30.     {  
  31.         return super.equals(rhs) &&  
  32.             y == ((DerivedClass)rhs).y;  
  33.     }  
  34. }  
  35.   
  36. public class EqualsWithInheritance  
  37. {  
  38.     public static void main(String[] args)  
  39.     {  
  40.         BaseClass a = new BaseClass(5);  
  41.         DerivedClass b = new DerivedClass(58);  
  42.         DerivedClass c = new DerivedClass(58);  
  43.   
  44.         System.out.println("b.equals(c): " + b.equals(c));  
  45.         System.out.println("a.equals(b): " + a.equals(b));  
  46.         System.out.println("b.equals(a): " + b.equals(a));  
  47.     }  
  48. }  

运行结果如下:

  1. b.equals(c): true  
  2. a.equals(b): false  
  3. b.equals(a): false  
 

 总结:

        对象重写equals()方法在结合继承的关系时容易被搞混淆。重点就是根据我们逻辑定义,如果需要详细考虑父类和子类的差别的话,应该考虑object.getClass()而不是instanceof。

 

参考资料

Inside the java 2 virtual machine

Data structures & problem solving Using java

  评论这张
 
阅读(280)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2016