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

技术标签: Java  java  后端  开发语言  

如何严谨地重写 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 方法。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_46360532/article/details/123118780

智能推荐

使用subprocess出现WinError_winerror 14001,subprocess.popen()-程序员宅基地

解决,加上参数shell=True即可import subprocessprocess = subprocess.Popen(‘D:’,shell=True)_winerror 14001,subprocess.popen()

TCP长连接开发相关,调试工具SocketTool与框架GatewayWorker_tcp/udp socket调试工具-程序员宅基地

前言:将近一年未更新博客了,最近在做一个新项目,涉及到服务端与客户端之间的通信,使用到这款网络TCP/UDP通信调试工具。本人对TCP/UDP等通信相关知识不甚了解,正好以此为契机,在网上搜罗了相关资料以备不时之需。简单介绍下这个软件:SocketTool是一款网络TCP/UDP通信调试工具,免安装且免费使用。可以帮助网络编程人员、网络维护人员检查所开发的网络应用软硬件的通信情况。是一款非常好..._tcp/udp socket调试工具

.NET Framework 4和.NET Framework 4 Client Profile的区别与联系_net framework 4 full-程序员宅基地

最近接触了一些visual studio 2010的使用,发现出现了.NET Framework 4和.NET Framework 4 Client Profile。一开始总是弄不大明不二者之间有什么区别,因为和工作没啥直接关系,所以也就暂时压住了好奇,今天有空看了一下msdn里的介绍,给自己总结一下。 我先自己定一下简称,这样叫起来比较方便。.NET Framework 4_net framework 4 full

C语言简单入门之结构体和链表_结构体简单入门链表-程序员宅基地

可以,非常详细https://blog.csdn.net/maoye198602102339/article/details/82663130_结构体简单入门链表

Linux 命令行-基础篇_命令行 linux-程序员宅基地

一、什么是命令二、切换到命令行的两种方式三、命令行基础_命令行 linux

常用元器件使用方法21:红外接收器IRM-3638MF31的使用方法_irm3638电路-程序员宅基地

介绍:The IRM-36xxMF31 series devices are miniature type(微型) infrared receivers which have been developed and designed by using the latest IC technology, specially optimized to suppress interferences(排..._irm3638电路

随便推点

wordcount功能实现和单元测试-程序员宅基地

Github网址:https://gitee.com/ronanly/WordCount.git1.思路看到题目时,对于编写wordcount这个项目,首先想到它应该实现的功能有哪些,再来细化这些功能;我用的C语言,然后用gcc编译器来执行,在我的代码里,有以下功能实现函数:1.void countc(char *file) //返回文件的字符数2.void coun...

cadence SPB17.4 - allego - modify rename package pin number_cadence封装修改引脚编号_LostSpeed的博客-程序员宅基地

cadence SPB17.4 - allego - modify rename package pin number前言在做一个小板子的整体封装给底板用.焊盘都是随便放的, 没有编号.现在焊盘放完了, 想调整焊盘号码.先去官方本地文档, 官方论坛, 网上其他资料去查了一下, 讲的方法都是旧版的. 时间好早啊.并没有找到针对SPB17.4, 如何修改pin number的说明.笔记看旧版的方法都是改引脚的pin number TEXT, 试了一下, 可以改.在PCB Editor中新建或打_cadence封装修改引脚编号

Axure实战案例4——指针导航-程序员宅基地

今天主要讲解如何使用母版和函数来制作导航。下图是一个导航栏,“知识中心”、“流程中心”、“用户中心”分别对应三个页面page1 page2 page3。page1 page2 page3 思路:三个页面都有统一的导航栏(除了三角符号),所以应该将其做成一个母版,就是抽取出共性出来。那么怎么保证在点击任何一个选项的时候都能切换到正确的页面,并且那个页面也显示正确的三角形位置呢...

使用composer安装composer包报Your requirements could not be resolved to an installable set of packages-程序员宅基地

一:使用composer安装composer包时遇到Your requirements could not be resolved to an installable set of packages分析:这是由于doctrine/instantiator的1.1.0版本必须是PHP7.1,然而我的PHP环境是PHP7.0.12,但是我发现一个问题,我的composer.json文件内没...

整理收集的基于java、javaweb、jsp后台的2021年的几个微信小程序毕业设计毕设选题课题_黄菊华老师的博客-程序员宅基地

(1)Java微信小程序校园图书商城 大学生毕业设计教学视频https://ke.qq.com/course/3062230(2)大巴汽车票订票选座微信小程序(后台javaweb)源代码版https://ke.qq.com/course/3172321(3)电影院订票选座微信小程序(后台javaweb)源代码版https://ke.qq.com/course/3172320(4)微信小程序教室预约系统(后台javaweb)源代码版https://ke.qq.com/course/3172319

斐波那契数列通项的两种求法-程序员宅基地

本文将介绍斐波那契数列通项公式的两种求法,以及如何通过计算机来计算通项._斐波那契数列通项