认识Object中的几个经常需要覆盖的方法——equals方法

      学习Java少不了对Object的认知,所有类都会继承它的属性,真正的超类。这一个系列,我会对Object中的几个方法,也就是我们自定义类的时候需要重写的几个方法做一个介绍。下面是这一个系列的主要内容:

本系列内容源于对《Effective Java》中文第二版第8条到第12条的学习记录。所有内容的准确性均以原书为准。

1,引言

      我想很多刚刚参加工作的java程序员在面试中都被问到过“==”和equals的区别,说实话,我以前不仅被问到过,而且还没有很好的答上来,现在如果被问到,我想下面的答案应该是可以满足基本要求的:

(1) 在不重写equals方法的时候,其和“==”功能是一样的;

(2)在重写了之后,它们的区别在于你是如何重写equals方法的,通常的做法是用于比较两个对象的值是否相同;我们重写equals方法需要遵循以下几条规则:

  • 自反性:对于任何非空引用值 xx.equals(x) 都应返回 true
  • 对称性:对于任何非空引用值 xy,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true
  • 传递性:对于任何非空引用值 xyz,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true
  • 一致性:对于任何非空引用值 xy,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改。
  • 对于任何非空引用值 xx.equals(null) 都应返回 false

(3)扩展:如果对象的hashCode值计算方法足够优秀,我们可以直接通过比较对象的hashCode值作为equals方法的比较结果)

2,分析

  上面简单的介绍了一下equals方法和“==”的区别,那我们就来看看每一条的分析:

(1)针对第一条,我们看一看Object中equals方法的源码:

public boolean equals(Object obj) {
        return (this == obj);
    }

我想也就不用再多说上面了,他们比较的也是两个非空对象的引用

(2)重写了equals方法之后,区别自然是你的equals方法是如何定义的,那下面就来仔细探讨一下equals方法该如何定义:

  • 自反性  对于任何非空引用值 xx.equals(x) 都应返回 true
  • 对称性 对于任何非空引用值 xy,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true

这里需要注意在出现继承关系时的处理,比如一个Person类

package hfut.edu;

/**
 * Date:2018年10月1日 上午11:05:45 Author:why
 */

public class Person {

    int age;
    String name;
    String sex;

    public Person(int age, String name, String sex) {
        super();
        this.age = age;
        this.name = name;
        this.sex = sex;
    }

    @Override
    public boolean equals(Object obj) {
        // TODO Auto-generated method stub

        if (!(obj instanceof Person))
            return false;

        Person p = (Person) obj;
        return this.age == p.age && this.name.equals(p.name) && this.sex.equals(p.sex);

    }

}

这里面我们定义了三个成员变量并且重写了equals方法,如果一个Student类继承Person类并且新增加了两个成员变量如下:

package hfut.edu;
/**
* Date:2018年10月1日 上午11:15:07
* Author:why
*/

public class Student extends Person {
    
    int studentID;
    String schoolName;

    public Student(int age, String name, String sex, int studentID,String SchoolName) {
        super(age, name, sex);
        this.studentID=studentID;
        this.schoolName=schoolName;
    }

}

现在,我想通过equals方法比较两个学生,如果直接只用继承Person类的,则新的成员变量比较不了,显然不合适,如果重写添加新的属性比如:

@Override
    public boolean equals(Object obj) {
        if(!(obj instanceof Student))
            return false;
        
        Student stu=(Student)obj;
        return super.equals(obj)&&stu.schoolName.equals(this.schoolName)&&stu.studentID==this.studentID;
        
    }

使用测试程序有:

public class TestClass {
    public static void main(String[] args) {

       Person p=new Person(26,"why","male");
       Student stu=new Student(26,"why","male",2020,"hfut");
       
       System.out.println("p.equals(stu)="+p.equals(stu));
       System.out.println("stu.equals(p)="+stu.equals(p));
    }

结果:

很显然是违背了对称性的,下面来看一下解决办法,把Student类中的equals方法改成:

@Override
    public boolean equals(Object obj) {
        if(!(obj instanceof Person))
            return false;    
        if(!(obj instanceof Student))
            return obj.equals(this);
        Student stu=(Student)obj;
        return super.equals(obj)&&stu.schoolName.equals(this.schoolName)&&stu.studentID==this.studentID;
        
    }

结果:

这样,对称性的问题算是解决了。那么,在Java的API里面有没有这样错误示例了,下面我们就来看一个:

所以,在Java平台类库中是有违背equals约定的示例的。参考:

Date中equals源码:

 public boolean equals(Object obj) {
        return obj instanceof Date && getTime() == ((Date) obj).getTime();
    }

TimeStamp中equals源码:

  public boolean equals(Timestamp ts) {
        if (super.equals(ts)) {
            if  (nanos == ts.nanos) {
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

注:它们都是package java.sql包下面的

  • 传递性 对于任何非空引用值 xyz,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true

就上面的例子,我们在测试一下这个特性是否满足,测试程序和结果如下图

很显然,在这里传递性失效了;所以有这么一句话:

我们无法在扩展可实例化类的同时,既增加新的值组件,同时又保留equals约定,除非愿意放弃面向对象的抽象所带来的优势。

对于上面的错误,我们可以使用复合的方式代替继承的方式,也就是说吧Person类作为Student类的一个成员来操作,具体的实现我就不多说了;也比较简单,主要是这种思想。

  • 一致性:对于任何非空引用值 xy,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改

建议:不要使equals方法依赖于不可靠的资源。

由此可见,想重写好Object中的equals方法还是不简单的,比我们平时认为的肯定要复杂一些,下面是在重写equals方法的时候的一些建议:

  • 使用“==”检查参数是否为这个对象的引用
  • 使用instanceof 检查参数是否为正确的类型
  • 把参数转化为正确的类型
  • 检查类中的每个关键域

对于float:使用Float.compare方法比较

对于double:使用Double.compare方法比较

对于其他基本类型:使用“”比较

对于引用类型域:使用equals方法(前提是引用指向的对象的类重写了)

  • 覆盖equals方法通常也需要覆盖hashCode方法
  • 不要在equals中做太多额外的逻辑业务判断,得不偿失
  • 不要将equals中的参数Object换成其他的类型

说到这里,其实关于Object中的equals方法的内容基本介绍完了,这里面很多内容都没有展开介绍。希望看到有模糊地方的朋友可以自己多展开一点。

猜你喜欢

转载自blog.csdn.net/hfut_why/article/details/82915167