【Java源码解析】如何严谨地重写 equals 方法、getClass 方法与 instanceof 关键词用法比较

如何严谨地重写 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;
    }
}

在这里插入图片描述
在上图输出结果中,前两行结果没有异议,但是后两行就可以看出问题了,发现上述实现的 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 {}

在下图的输出结果中,我们可以得出结论,父类实例 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 {}

在这里插入图片描述
根据上图结果,我们发现,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;
}

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;
}

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

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

超周到的程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值