`
frank-liu
  • 浏览: 1682613 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

类继承关系中实现equals方法的一个细节

    博客分类:
  • java
 
阅读更多

equals方法

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

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

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

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

 

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

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

继承关系的一个实现

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

 

class BaseClass
{
    private int x;

    public BaseClass(int i)
    {
        x = i;
    }

    public boolean equals(Object rhs)
    {
        if(!(rhs instanceof rhs))
            return false;

        return x == ((BaseClass)rhs).x;
    }
}

class DerivedClass extends BaseClass
{
    private int y;

    public DerivedClass(int i, int j)
    {
        super(i);
        y = j;
    }

    public boolean equals(Object rhs)
    {
        if(!(rhs instanceof DerivedClass))
            return false;

        return super.equals(rhs) &&
            y == ((DerivedClass)rhs).y;
    }
}

public class EqualsWithInheritance
{
    public static void main(String[] args)
    {
        BaseClass a = new BaseClass(5);
        DerivedClass b = new DerivedClass(5, 8);
        DerivedClass c = new DerivedClass(5, 8);

        System.out.println("b.equals(c): " + b.equals(c));
        System.out.println("a.equals(b): " + a.equals(b));
        System.out.println("b.equals(a): " + b.equals(a));
    }
}

 

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

 

b.equals(c): true
a.equals(b): true
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实例化的对象,这样就可以实现他们的区分。

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

 

class BaseClass
{
    private int x;

    public BaseClass(int i)
    {
        x = i;
    }

    public boolean equals(Object rhs)
    {
        if(rhs == null || getClass() != rhs.getClass())
            return false;

        return x == ((BaseClass)rhs).x;
    }
}

class DerivedClass extends BaseClass
{
    private int y;

    public DerivedClass(int i, int j)
    {
        super(i);
        y = j;
    }

    public boolean equals(Object rhs)
    {
        return super.equals(rhs) &&
            y == ((DerivedClass)rhs).y;
    }
}

public class EqualsWithInheritance
{
    public static void main(String[] args)
    {
        BaseClass a = new BaseClass(5);
        DerivedClass b = new DerivedClass(5, 8);
        DerivedClass c = new DerivedClass(5, 8);

        System.out.println("b.equals(c): " + b.equals(c));
        System.out.println("a.equals(b): " + a.equals(b));
        System.out.println("b.equals(a): " + b.equals(a));
    }
}

运行结果如下:

b.equals(c): true
a.equals(b): false
b.equals(a): false
 

 总结:

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

 

参考资料

Inside the java 2 virtual machine

Data structures & problem solving Using java

4
4
分享到:
评论
6 楼 商人shang 2012-11-09  
确实应该注意这些细节方面的东西,否则在测试的时候测不出来,将来正式运行以后那造成的损失可就难以估计了
5 楼 kjj 2012-11-09  
frank-liu 写道
zhukewen_java 写道
引用

1. 比较传进来的对象是否为空,如果空则返回false。
2. 比较传进来的对象类型是否相同,不同则返回false.
3. 再根据定义的对象比较每个字段是否相等,不等则返回false.

第三点的说法不准确.未必会比较每个字段,这是自定义的.

是的,因为是自己来定义,可以选择对于对象比较有意义的字段。


+1  equals 方法和hashcode实际上是为业务逻辑规定的对比方法,没必要死磕每个字段,你完全可以自定义任意规则!!
4 楼 frank-liu 2012-11-09  
zhukewen_java 写道
引用

1. 比较传进来的对象是否为空,如果空则返回false。
2. 比较传进来的对象类型是否相同,不同则返回false.
3. 再根据定义的对象比较每个字段是否相等,不等则返回false.

第三点的说法不准确.未必会比较每个字段,这是自定义的.

是的,因为是自己来定义,可以选择对于对象比较有意义的字段。
3 楼 zhukewen_java 2012-11-09  
引用

1. 比较传进来的对象是否为空,如果空则返回false。
2. 比较传进来的对象类型是否相同,不同则返回false.
3. 再根据定义的对象比较每个字段是否相等,不等则返回false.

第三点的说法不准确.未必会比较每个字段,这是自定义的.
2 楼 java赵云 2012-11-09  
用instanceof确实不可靠
1 楼 mybreeze77 2012-11-09  
大牛。。直接看不懂。。

相关推荐

    接口与Object类

    2. **实现要求**:一个类实现接口时,必须实现接口中的所有抽象方法;而继承抽象类时,并非必须实现抽象类中的所有抽象方法。 3. **多继承支持**:接口支持多继承,即一个类可以实现多个接口;而抽象类不支持多继承...

    封装、继承(csdn)————程序.pdf

    在Java中,一个类可以通过继承另一个类来获得其属性和方法。继承的主要目的是实现代码复用,减少重复代码。 1. 继承的概念 继承允许创建一个新的类(子类)基于已有的类(父类),子类自动继承父类的属性和方法,...

    Java类思维导图

    Java类思维导图是理解Java编程语言中类与类之间关系的重要工具,它通过图形化的方式,清晰地展示了Java各类的结构、继承关系以及方法的使用。这份"Java思维导图"很可能包含了从基础的面向对象概念到高级特性的全面...

    第4章(类的重用).ppt

    1. **类的继承**:继承是一种面向对象编程的基本特性,允许一个类(派生类)从另一个类(基类或父类)继承属性和方法。通过继承,子类可以获得父类的功能,并可以添加自己的特性和方法。Java中的每个类都默认继承自`...

    澄清Java(接口与继承)

    继承是指一个类(子类)可以直接使用另一个类(父类)的属性和方法,并且还可以添加新的属性或覆盖已有的方法。 ##### 2.2 继承的声明 使用`extends`关键字声明继承关系。例如: ```java public class B extends A ...

    java第05章.面向对象编程-2.pdf

    值得注意的是,Java中的继承关系是不可以传递的,也就是说,即使类A继承类B,B继承类C,并不意味着A和C之间有继承关系。 多态是面向对象编程的另一个重要特征,它指的是允许不同类的对象对同一消息做出响应。多态的...

    (完整版)2020年最新Java面试专题答案.doc

    - 继承关系:类使用extends继承抽象类,使用implements实现接口。 - 构造函数:抽象类可以有构造函数,接口则没有。 - 实现数量:一个类可以实现多个接口,但只能继承一个抽象类(Java单继承特性)。 - 访问修饰...

    corejava_面试题

    可以,匿名内部类既可以继承一个类,也可以实现一个或多个接口。 String是否是基本数据类型? 不是,String是final修饰的类,用于表示字符串的序列。字符串是不可变对象,一旦创建,其内容不能改变。 字符串操作的...

    java面试宝典2012版

    内部类是定义在一个类的内部的类,它可以让内部实现细节对外部隐藏,从而增强封装性。内部类可以访问其外部类的成员,包括私有成员。 27. 内部类引用外部类的成员限制? 内部类可以引用外部类的成员,但如果要...

    Java程序员面试宝典.pdf

    - **继承**:允许一个类(子类)继承另一个类(父类)的属性和方法,从而实现代码的重用。 - **多态**:同一接口可以被不同的类实现,表现出不同的行为。多态提高了代码的灵活性和扩展性。 #### 二、Java中的面向...

    java出学者必须掌握的

    使用关系是类间的依赖,聚合关系表示一个类包含另一个类的实例,继承关系表示子类继承父类的特性。 7. 构造器:构造器用于初始化新创建的对象。每个类可以有多个构造器,它们具有与类名相同的名字,没有返回值,并...

    Java问答.pdf

    在Java中,一个类可以实现多个接口,通过这种方式,类能够继承多个接口中定义的方法。虽然实现接口并不是真正的继承关系,但这种机制能够在一定程度上达到类似多重继承的效果。 在Java中,基本数据类型之间的转换...

    Java方向如何准备BAT技术面试答案(汇总版).pdf

    - 跨域不同:抽象类体现继承关系,接口体现实现关系。接口中的实现者不必是同一类型,只要实现了接口中定义的方法即可。 这份文档详尽地总结了Java开发者在技术面试中常见的问题及答案,对于有意加入BAT等一线...

    java作业类和对象.zip

    7. **继承**:继承允许一个类(子类)从另一个类(父类)继承属性和方法,减少了代码重复。Java中,使用`extends`关键字来实现继承。 8. **多态(Polymorphism)**:多态是指同一个接口,使用不同的实例而产生不同的...

    java基本概念 适用于初学者

    4. **继承**:继承允许一个类(子类)从另一个类(父类)继承属性和方法,从而实现代码的复用。所有的类最终都继承自 `Object` 类,它是所有类的根超类。 5. **对象的特性**:对象的三个主要特性是行为(behavior)...

    Java入门需掌握的30个基本概念

    其中继承关系最为常见,它表示一个类是从另一个类派生出来的。例如,如果A类继承了B类,那么A类不仅拥有了B类的所有方法,还可能有自己的方法。 #### 基本概念7:构造器 构造器是一种特殊的成员方法,用于创建并...

    学习Java的30个基本概念

    继承是一种类与类之间关系的表示方法,它允许一个类(子类)继承另一个类(父类)的属性和方法。继承的关键字是`extends`。 #### 12. 抽象类(Abstract Class) 抽象类是一种不能实例化的类,它主要用于定义一个...

    Java面试宝典2011版

    `Overload`指方法重载,允许在同一个类中声明多个同名方法,只要它们的参数列表不同即可。`Override`指方法重写,是在子类中重新定义父类的方法,要求方法名、参数列表和返回类型完全一致。 ### 19. 构造器的重写 ...

    Java入门需掌握的30个基本概念.pdf

    封装是指将数据和操作这些数据的方法包装在一个单一的单元——对象中,对外部隐藏对象的内部状态和实现细节。这不仅保护了数据的安全性,也简化了对象的使用方式。对象的实例字段存储着对象的状态信息。 #### 4. ...

    上海交大 第三学期期末 java复习题和模拟题(附答案).docx

    注意,Java中的类支持单一继承,即一个类只能直接继承一个父类,但可以通过实现多个接口来模拟多重继承。 ### 构造函数的特点 - **知识点**:在构造函数中使用`super`引用时,必须将其放在构造函数的第一条语句。 -...

Global site tag (gtag.js) - Google Analytics