重写equals方法

2023-11-09

重写equals方法

相信在每个人都有过重写过java的equals的方法的经历。这篇博文就从以下几个方面说明重写equals方法的原由,与君共进步。

在这里插入图片描述

一 为什么要重写equals方法

首先我们了解equals方法的作用是什么?

java的官方解释:

Indicates whether some other object is “equal to” this one.

The equals method implements an equivalence relation on non-null object references:

指示某个其他对象是否“等于”这个对象。
equals方法实现了非空对象引用的等价关系.

也就是说比较两个对象是否相等,在Object类中,这个方法是用来判断两个对象是否具有相同的引用。如果两个对象具有相同的引用,那么他们一定是相等的,看看下面这段代码的运行:

    public static void main(String[] args) {
    Object o1 = new Object();   
    Object o2 =o1;
    Object o3 = o1;
    System.out.println(o3.equals(o2));
}

结果是

输出是:  true  

这是因为o2和o3来两个对象都指向了相同的引用o1,所以是true。 但是很多时侯我们可能会有这样的需求。比较两个对象的状态(内容)是否相同。看看下面着段代码:

public class Student {

private String name;

public Student() {
}

public Student(String name) {
    this.name = name;
}

public String getName() {
    return name;
}
public void setName(String name) {
    this.name = name;
}

public static void main(String[] args) {
     Student stu1 = new Student("张三");
     Student stu2 = new Student("张三");
     System.out.println(stu1.equals(stu2));
     System.out.println(stu1==stu2);
}

}

结果是

false
false

分析: 为什么使用equals方法返回的不是true,而是和==相同的结果false呢。这是因为Student这个类并没有重写equals方法,当调用equals方法时,实际上调用的是Object类中的equals方法。看看equals方法的源码:

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

可以看到equals的底层代码使用 == 来实现的,也就是说在此时的equals方法也是利用 ==来比较的两个对象的内存地址是否相同。

所以上面的代码运行结果都是false。
所以要想实现两个对象的状态(内容)相同,就要重写equals方法

重写student类中的equals方法:

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof Student)) return false;
    Student student = (Student) o;
    return 
    Objects.equals(name, student.name);
}

再次运行:

 true
 false

可以看到,重写了equals方法实现了两个对象的状态比较。使用equals返回的是true。

重写eqauls方法能实现两个对象的状态(内容)比较。

二 重写equals方法的原则是什么

看官方文档给出的解释

  • It is reflexive: for any non-null reference value x, x.equals(x) should return true.
  • It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
  • It is transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
  • It is consistent: for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
  • For any non-null reference value x, x.equals(null) should return false.

也就是,重写equals方法时,必须满足下面原则

  • 自反性:对于任何非空参考值x,x.equals(x)应该返回true。
  • 对称性:对于任何非空参考值x和y,当且仅当y.equals(x)返回true时,x.equals(y)才应返回true。
  • 传递性:对于x,y和z的任何非空引用值,如果x.equals(y)返回true,而y.equals(z)返回true,则x.equals(z)应该返回true。
  • 一致性:对于任何非空引用值x和y,只要未修改对象的equals比较中使用的信息,对x.equals(y)的多次调用将始终返回true或始终返回false。
  • 对于任何非null参考值x,x.equals(null)应该返回false。

一般而言,对于一个类的两个对象,这五个原则都可以满足。看下面的代码:

     public static void main(String[] args) {
     Student stu = null;
     Student stu1 = new Student("张三");
     Student stu2 = new Student("张三");
     Student stu3 =  new Student("张三");
     System.out.println("自反性:"+stu1.equals(stu1));
     System.out.println("对称性");
     System.out.println(stu1.equals(stu2));
     System.out.println(stu2.equals(stu1));
     System.out.println("传递性");
     System.out.println(stu1.equals(stu2));
     System.out.println(stu2.equals(stu3));
     System.out.println(stu1.equals(stu3));
     System.out.println("一致性:");
    for(int i =0; i<5;i++){
        if(stu1.equals(stu2)!=stu1.equals(stu2)){
            System.out.println("没有遵守一致性");
            break;
        }
    }
    System.out.println("遵守了一致性");
    System.out.println("非空性");
    // 这里的stu对象为null,所以返回false
    System.out.println(stu1.equals(stu));
}

输出结果:

自反性:true
对称性
true
true
传递性
true
true
true
一致性:
遵守了一致性
非空性
false

测试这几个原则都能满足

如果一个类中含有继承关系,equals方法是否满足呢。我们让student类继承person类。看下面的代码

public class Person {
private int age;

public Person(){}

public Person(int age) {
    this.age = age;
}

public int getAge() {
    return age;
}

public void setAge(int age) {
    this.age = age;
}

// 实现父类equals的方法比较。
@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof Person)) return false;
    Person person = (Person) o;
    return age == person.age;
}

@Override
public int hashCode() {
    return Objects.hash(age);
}
 }

让student继承person,并且有自己的独有的属性name。

public class Student extends Person {
private String name;
public Student() {
}
public Student(String name){
    this.name = name;
}
public Student(int batch,String name) {
    super(batch);
    this.name = name;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}
@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof Student)) return false;
    if (!super.equals(o)) return false;
    Student student = (Student) o;
    return Objects.equals(name, student.name);
}

测试:

  public static void main(String[] args) {
   Person per1 = new Person(12);
  Student stu2 = new Student(12,"张三");
   System.out.println(per1.equals(stu2));
   System.out.println(stu2.equals(per1));
}

运行结果:

true 
false 

为什么出现了false呢,这是因为违背了对称性的原则了。分析一下也不难理解,因为student都是person,所以,per1.equals(stu2)是true,但是反过来就不成立,你不能说所有person都是student,所以stu2.equals(per1)是不成立的。这里也要明确一下父类和字类的equals方法的调用关系。在子类中定义equals方法时,首先调用父类的equals方法,如果检测失败,对象就不可能相等,如果超类中的域都相等,才需要比较子类的实例域。那么上述这个问题怎么解决呢,明白了equals方法的调用关系,那就只需在子类中的eqauls方法中添加一个判断。

 // 修改student类中的equals方法
@Override
public boolean equals(Object o) {
  if (this == o) return true;
  // 记得注释这里
  //if (!(o instanceof Student)) return false;
    if (!super.equals(o)) return false;
    if(o instanceof Student){
        Student student = (Student) o;
        return Objects.equals(name, student.name);
    }
    // 如果o不是student还需要再判断o 是不是person对象
    return super.equals(o);
}

再次运行上述代码:

true 
true 

就符合对称性了。

三 重写了equals方法为什么还要重写hashcode呢

看看hashcode的定义:
java官方档对hashcode()方法的解释:

Returns a hash code value for the object. This method is supported for the benefit of hash tables such as those provided by HashMap.

返回对象的哈希码值。支持此方法的好处是可以使用HashMap提供的散列表。

官方文档明确的说明了此方法的好处是可以使用HashMap提供的散列表。这是因为Map接口的类会使用到键对象的哈希码,当我们调用put方法时,就是根据对象的哈希码来计算存储位置的,因此必须提供对哈希码正确的保证。在java中,可以使用hashCode()这个方法来获取对象的哈希值。

public static void main(String[] args) {
    String s1 = "hello";
    String s2 = "world";
    System.out.println(s1.hashCode());
    System.out.println(s2.hashCode());
}

输出结果:

99162322
113318802

可以看到不同的对象的hashcode值是不同的。

看看javaAPI的对hashcode的几点说明:

  • 在Java应用程序的执行过程中,无论何时在同一个对象上多次调用它,hashCode方法都必须一致地返回相同的整数,前提是不对对象上的equals比较中使用的信息进行修改。此整数不需要在应用程序的一次执行与同一应用程序的另一次执行之间保持一致。

  • 如果根据equals(Object)方法,两个对象是相等的,那么在每个对象上调用hashCode方法必须产生相同的整数结果。

  • 如果两个对象根据equals(java.lang.Object)方法是不相等的,那么在每个对象上调用hashCode方法必须产生不同的整数结果,这是不需要的。但是,程序员应该意识到,为不相等的对象生成不同的整数结果可能会提高哈希表的性能。

    回到我们前面的问题,为什么重写equals方法,必须要重写hashcode方法。在Object类中,hashCode方法是通过Object对象的地址计算出来的,因为Object对象只与自身相等,所以同一个对象的地址总是相等的,计算取得的哈希码也必然相等。对于不同的对象,由于地址不同,所获取的哈希码自然也不会相等。所以如果一个类重写了equals方法,但没有重写hashCode方法,将会直接违法了第2条规定。还有一点就是重写hashcode()方法,可以方便用户将对象插入到散列表中。

四 如何写好一个equals方法

  1. 显式参数命名为otherObject,稍后需要将它转换成另一个叫做other的变量。
  2. 检测this与otherObject是否引用同一个对象:if(this=otherObject)returntrue;这条语句只是一个优化。实际上,这是一种经常采用的形式。因为计算这个等式要比一个一个地比较类中的域所付出的代价小得多。
  3. 检测otherObject是否为null,如果为null,返回false。这项检测是很必要的。if(otherObject=null)returnfalse;
  4. 比较this与otherObject是否属于同一个类。如果equals的语义在每个子类中有所改变,就使用getClass检测:if(getClass()!=otherObject.getCIassO)returnfalse;如果所有的子类都拥有统一的语义,就使用instanceof检测:if(!(otherObjectinstanceofClassName))returnfalse;
  5. 将otherObject转换为相应的类类型变量:ClassNameother=(ClassName)otherObject
  6. 现在开始对所有需要比较的域进行比较了。使用=比较基本类型域,使用equals比较对象域。如果所有的域都匹配,就返回true;否则返回false。returnfieldl==other.field&&Objects.equa1s(fie1d2,other.field2)
  7. 如果在子类中重新定义equals,就要在其中包含调用super.equals(other)。

如果你用的是eclipse或者是idea集成环境的话,可以使用快捷键自动生成符合以上标准的equals()方法,但是有时候需要自定义,所以知道原理很重要。

追本溯源,方能阔步前行

参考书籍:

1. Java核心技术 第一卷:基础知识

2. java官方文档

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

重写equals方法 的相关文章

  • SpringBoot + Websocket 实现实时聊天

    SpringBoot WebSocket 实现实时聊天 最近有点小时间 上个项目正好用到了websocket实现广播消息来着 现在来整理一下之前的一些代码 分享给大家 WebSocket协议是基于TCP的一种新的网络协议 它实现了浏览器与服

随机推荐

  • ctfshow-web10 with rollup 绕过

    0x00 前言 CTF 加解密合集 CTF Web合集 网络安全知识库 文中工具皆可关注 皓月当空w 公众号 发送关键字 工具 获取 0x01 题目 0x02 Write Up 基本方法 到处点一点 点到取消的时候 突然发现 可以下载一个文
  • C语言中signed和unsigned怎么理解?C语言unsigned long、long long 取值范围???

    C语言中signed和unsigned signed意思为有符号的 也就是第一个位代表正负 剩余的代表大小 例如 signed int 大小区间为 128 127 unsigned意思为无符号的 所有的位都为大小 没有负数 例如 unsig
  • java实现下载excel读取与生成超详细

    背景 没啥背景 就是要做这个功能 创建ExcelUtil工具类 具体导入导出方法如下 excel导入 param inputStream 导入的excel文件 return public static List
  • 酱香咖啡喝了没?用数据分析揭秘瑞幸咖啡的7500万用户增长策略

    瑞幸 X 茅台 这波联名赢麻了 不仅狂卖 542 万杯 甚至带动茅台市值飙升200亿 瑞幸这几年联名搞了不少 又是线条小狗的爱情故事 又是椰树 维密 周大福 足球的 下面老李就从数据分析角度 带大家来看一下近几年 瑞幸咖啡的用户增长策略 是
  • 数据结构笔记之链式栈的基本操作

    include stdio h include stdlib h include io h include math h include time h define OK 1 define ERROR 0 define TRUE 1 def
  • VMWare安装

    1 1 VMWare简介 VMWare是一个虚拟技术的合集 它提供了众多的相关软件 类似于Parallels VMWare是商业应用 而且价格非常的贵 所以 通常我们使用的是网上别人破解的版本 而不是使用官方的正版 VMWare官网 VMw
  • 【UnicodeDecodeError: ‘utf-8‘ codec can‘t decode byte 0xd3 in position 0: invalid continuation byte】

    UnicodeDecodeError utf 8 codec can t decode byte 0xd3 in position 0 invalid continuation byte F jupyter work dir MMLAB m
  • 有一个公网IP,在内网如何架设多台服务器?

    进行内网ip到外网ip的映射 也就是pat 这个工作现在多半由防火墙来完成 不过如果没有防火墙 用路由器也可以完成 只不过会在高峰时加重路由器的负担 思科2600路由可以独立完成各种nat pat但是因为这款产品本身属于低端产品 所以能够担
  • Java8学习记录(一)——Lambda表达式

    这两天看了 Java8实战 做一下记录 目录 一 行为参数化 1 什么是行为参数化 二 函数式接口 1 概念 三 Lambda表达式 四 方法引用 注意点 1 静态方法引用 2 实例方法引用 重点来了 任意类型的实例方法引用 现有对象的实例
  • 【深度学习】树莓派Zero w深度学习模型Python推理

    在机器学习开发过程中 当模型训练好后 接下来就要进行模型推理了 根据部署环境可分为三类场景 边缘计算 一般指手机 嵌入式设备 直接在数据生成的设备上进行推理 因为能避免将采集到的数据上传到云端 所以实时性非常好 端计算 介于云和边缘设备之间
  • Spring Boot 集成Mybatis实现多数据源

    总体来说多数据源配置有两种方式 一种是静态的 一种是动态的 静态的方式 我们以两套配置方式为例 在项目中有两套配置文件 两套mapper 两套SqlSessionFactory 各自处理各自的业务 这个两套mapper都可以进行增删改查的操
  • GPU版本安装Pytorch教程最新方法

    目录 步骤 第一步 安装 Anaconda 和 Pycharm 软件 第二步 下载安装CUDA11 3 1 首先查看自己电脑GPU版本 方式一 搜索框输入nvidia 打开nvidia控制面板 方式二 win R打开cmd 输入nvidia
  • ubuntu的FTP服务器搭建

    1 安装服务器 sudo apt get install vsftpd 如果有错误 先执行 apt update 2 创建用户 Linux下的用户 创建专用目录 mkdir home ftp 命令添加ftp用户 sudo useradd d
  • 基于tiny6410的led驱动程序

    今天弄了一个晚上了 终于弄懂了第一个简单的linux驱动感觉很多人编写的第一个驱动也是拿led开刀的吧 本文的led驱动是基于tiny6410的四个led驱动 用字符设备的驱动模块 而不是混杂设备驱动模块来编写 本人在使用混杂设备方法编写的
  • 5735. 雪糕的最大数量

    夏日炎炎 小男孩 Tony 想买一些雪糕消消暑 商店中新到 n 支雪糕 用长度为 n 的数组 costs 表示雪糕的定价 其中 costs i 表示第 i 支雪糕的现金价格 Tony 一共有 coins 现金可以用于消费 他想要买尽可能多的
  • pyspark指定schema

    通过StructType对象指定DataFrame的Schema 没有嵌套结构的json jsonString id 01001 city AGAWAM pop 15338 state MA id 01002 city CUSHMAN po
  • r语言 col_co,cob,col,con,cor,cog前缀其实都是com变化而已

    c开头前缀有co cob col com con cor cog等 哪它们有什么关联呢 下面我们来分析 come 英 k m 美 k m v 来 来到 到达 com前缀可以看成come变化而成 全部来到就是聚集到一起 表聚集 加强 强调的
  • select函数使用浅析

    一 函数原型及参数说明 int select int maxfdp fd set readfds fd set writefds fd set errorfds struct timeval timeout 返回值 负值 select错误
  • VIM-Plug安装插件时,频繁更新失败,或报端口443被拒绝

    安装vim插件 tabular时候一直报错 尝试了网上大部分办法 全部失败 方法 直接按照报错的内容自己手动操作 错误如下图 第一步 去github检查插件的网址有没有问题 直接复制网址浏览器搜索 若长时间加载不上 使用科学上网 第二步 若
  • 重写equals方法

    重写equals方法 相信在每个人都有过重写过java的equals的方法的经历 这篇博文就从以下几个方面说明重写equals方法的原由 与君共进步 一 为什么要重写equals方法 首先我们了解equals方法的作用是什么 java的官方