sunny123456

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

【Java源码解析】如何严谨地重写 equals 方法、getClass 方法与 instanceof 关键词用法比较
https://blog.csdn.net/m0_46360532/article/details/123118780

如何严谨地重写 equals 方法

1 equals 方法概述

equals 方法我们都非常熟悉,equals 是 Object 基类中的模板方法 ,每个类中都有它的的存在,多数类或其抽象父类都以不同方式重写了 equals 方法。

例如,作为所有数值封装类的父类 Number 类,将 equals 比较方法的重写放到了各数值封装类中进行,因为不同数值类的相等判别依据不同,而 ArrayList 类和 LinkedList 类则将 equals 方法实现放到了其抽象父类 AbstractList 类中实现,因为集合框架得益于迭代器模式,因此可以方便地遍历同一类集合实现类,屏蔽其遍历细节,因此这两个集合类便可以在其抽象父类中实现相同的 equals 方法,等等这样的例子还有很多…
在这里插入图片描述

2 String 类中的 equals 方法

我们以 String 类中的 equals 方法为例,进行自定义 equals 方法的学习。

首先是一个引用是否一直的判断,之后便是根据 String 类型的特殊性,只要字符串一样就可以相等,不必硬性要求是否是同一个 String 对象,因此后续又进行了 instanceof 关键词的类别判断,首先需要判断参数类是否是字符串类,这才是后续可以获取其字符数组的前提,而每个 String 类,在 Java 中其实都是用一个 char[] value 去存储的,因此之后我们遍历当前字符串、目标字符串的 value 数组,用一次遍历,O(n) 的时间复杂度来进一步判断这是否是两个值相同的字符串。
在这里插入图片描述

3 自定义 equals 方法时出现的问题

我们用 Person 类和 Student 类,两个类来进行问题的阐释,同时我们仿照 JDK 源码中 String 类这个例子的 equals 方法的编写思想,重写我们自定义的这两个类中的 equals 方法:

public class Test {
    public static void main(String[] args) {
        Person person1 = new Person("001");
        Person person11 = new Person("001");
        System.out.println("person1.equals(person11) = " + person1.equals(person11));
        Student student1 = new Student("001", 100);
        Student student11 = new Student("001", 100);
        System.out.println("student1.equals(student11) = " + student1.equals(student11));
        System.out.println("person1.equals(student1) = " + person1.equals(student1));
        System.out.println("student1.equals(person1) = " + student1.equals(person1));
    }
}
class Person {
    private String idCard;
    public Person() {
    }
    public Person(String name) {
        this.idCard = name;
    }
    public String getIdCard() {
        return idCard;
    }
    public void setIdCard(String name) {
        this.idCard = name;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj instanceof Person) { //obj为null也可以判断,这里不用单独处理
            Person anotherPerson = (Person) obj;
            return this.getIdCard().equals(anotherPerson.getIdCard());
        }
        return false;
    }
}
class Student extends Person {
    private Integer score;
    public Student() {
    }
    public Student(String idCard, Integer score) {
        super(idCard);
        this.score = score;
    }
    public Integer getScore() {
        return score;
    }
    public void setScore(Integer score) {
        this.score = score;
    }
    @Override
    public boolean equals(Object obj) {
        if (!super.equals(obj)) {
            return false;
        }
        if (obj instanceof Student) {
            Student anotherStudent = (Student) obj;
            return this.getScore() == anotherStudent.getScore();
        }
        return false;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77

在这里插入图片描述
在上图输出结果中,前两行结果没有异议,但是后两行就可以看出问题了,发现上述实现的 equals 方法不满足对称性,也就是说 A.equals(B) != B.equals(A),而造成这种问题的原因就是,我们令 A 和 B 不再是同一种类型,而是父子类关系,而 instanceof 关键词在判断父子类时是很“认真”的,也就是说代码中的这一句是问题的关键——obj instanceof Student,由于 student1.equals(person1) 我们传入的参数是 Person 类型的,因此 obj instanceof Student 为 false,自然就判定为不相等了,更直观的验证请看下面的 二。

4 instanceof 关键词与 getClass 方法的比较

public class Test2 {
    public static void main(String[] args) {
        Person person = new Person();
        System.out.println(person instanceof Student);
        Student student = new Student();
        System.out.println(student instanceof Person);
    }
}
class Person {}
class Student extends Person {}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在下图的输出结果中,我们可以得出结论,父类实例 instanceof 子类类型会返回 false,而子类实例 instanceof 父类类型会返回 true,因此,这就更加印证了 一部分 中我们最后一行输出为什么是 false,我们的 equals 方法为什么不满足对称性。
在这里插入图片描述

那还有什么方法能判断类型呢,那我们自然也能想到是 getClass 方法了,之后,我们用 getClass 方法作测试,这也是基类 Object 类中的方法,我们可以获取当前类的运行时类型,我们接下来用这个方法进行上述 instanceof 不能解决的父子类对称性问题的检验

public class Test3 {
    public static void main(String[] args) {
        Person person = new Person();
        System.out.println(person.getClass());
        Student student = new Student();
        person = student;
        System.out.println(person.getClass());
    }
}
class Person {}
class Student extends Person {}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

在这里插入图片描述
根据上图结果,我们发现,getClass 方法获取的是实际的类型,不会因为上转型的问题而出现像是 instanceof 那样的对称性问题

5 正确编写 equals 方法

我们接下来便可以改进我们 Person、Student 类中的 equals 方法了(由于主要代码上文全部展示过,因此这里只展示我们改进的那部分代码):

Person 类

@Override
public boolean equals(Object obj) {
      if (this == obj) {
          return true;
      }
      if (getClass() == obj.getClass()) { //obj为null也可以判断,这里不用单独处理
          Person anotherPerson = (Person) obj;
          return this.getIdCard().equals(anotherPerson.getIdCard());
      }
      return false;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

Student 类

@Override
public boolean equals(Object obj) {
    if (!super.equals(obj)) {
        return false;
    }
    if (getClass() == obj.getClass()) {
        Student anotherStudent = (Student) obj;
        return this.getScore() == anotherStudent.getScore();
    }
    return false;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

在这里插入图片描述
再次运行,我们发现,equals 方法已经满足对称性,而又因为它也同时满足自反性,传递性、一致性(模拟代码便可证得),我们这样写的 equals 方法,才是正规的经得起实践的 equals 方法。因此,我们今后重写 equals 方法时,要根据实际情况判断是否用 instanceof 关键词判断数据类型,还是用 getClass 方法来判断数据类型,并进行自反性,对称性、传递性、一致性以及具体项目等情况的判断,才能编写出正确的 equals 方法。

posted on 2023-01-11 09:07  sunny123456  阅读(92)  评论(0编辑  收藏  举报