equals() 和 hashCode() 在 Object 类中以本地方法的形式存在,Java 中所有的类都继承了 Object 类,因此所有的类中都包含了这两个方法。这两个方法在 Java 开发中使用及其频繁,熟系这两个方法的使用是掌握 Java 语言的必要条件。这里总结了重写这两个方法的两点原则,并提供了一套代码模板。

equals()

从 JDK 源码注释中可以看到:Java 中使用 equals() 来表示两个对象是否相等。a.equals(b) 返回 true 表示 ab 两个对象引用等价,所谓等价需要具备如下性质:

  • 自反性:对于任意的非空引用 xx.equals(x)返回 true
  • 对称性:对任意的非空引用 x, y,“x.equals(y) 返回 true” 的充分必要条件是 “y.equals(x) 返回 true”。
  • 传递性:对任意的非空引用 x, y, z,若“x.equals(y) 返回 truey.equals(z) 返回 true”,则 “x.equals(z) 返回 true”。
  • 一致性:如果用于比较两个对象的信息未发生改变,则无论 x.equals(y) 调用多少次,返回的值总是相同的(要么 true,要么 false)。
  • 对任意非空引用 xx.equals(null) 总是返回 false

equals() 和 == 有什么区别?

equals() 比较的结果与它的实现有关,即取决与 equals() 里面代码是如何写的。如果没有覆盖 equals(),则调用的是父类中的 equals()。

== 比较的为两个对象引用所引用对象的地址是否相等,也就是两个引用所引用的为同一个对象时返回 true

Object 类中的 equals() 方法的效果与 == 一致,也就意味着如果某个类的父类是 Object,而这个类没有覆盖 equals 方法,则该类的对象使用 equals 和 == 进行比较效果一样。

hashCode()

hashCode() 返回了对象的哈希值,用于支持基于哈希表的数据结构,如:HashMap。

关于 hashCode 的一般约定是:

  • 只要用于 equals() 比较的属性未发生改变,多次调用 hashCode() 应该返回相同的整型数。
  • 如果两个对象使用 equals() 方法比较相等,则这两个对象的哈希值必须相等。
  • 如果两个对象使用 equals() 方法比较不相等,不要求这两个对象的哈希值不相等。

Object 类中通常通过将对象地址转化为一个 32 位整数来作为对象的哈希值,在一定范围内不同对象的哈希值是不同的。之所以说“通常”,是因为 hashCode 是一个本地方法,跟 Java 虚拟机如何实现这个本地方法有关。

如何重写 equals() 和 hashCode()?

在 Java 开发当中,存储数据相关的类一般都需要重写 equals() 和 hashCode()。典型的例子就是 ORM 框架中的实体类。重写这两个方法的时候,需要符合上面所提到的性质和约定,这里总结了两条基本原则,并给出了一个重写 equals() 和 hashCode() 的模板。

原则一 重写了 equals() 必须重写 hashCode(),用于生成哈希值的属性是用于比较对象是否相等的属性的子集

这么做的原因是为了保证“equals()判断相等的两个对象生成的哈希值应该相等”。可以换一种更形象的方式去理解,hashCode() 用来将对象进行分组,相同哈希值的对象属于同一组,而 equals() 判断相等的对象才是真正相等。相等的两个对象一定属于同一组,而同一组的对象不一定相等。

假设重写了 equals() 而没有重写 hashCode(),equals() 使用了对象的部分属性进行判断,而 hashCode() 返回的仍然为地址所转换的值,则可能导致哈希表中存在两个相等的对象。如下代码,假设 User 重写了 equals(),而没有重写 hashCode():

import java.util.HashSet;
import java.lang.String;
import java.util.Objects; class User{
String id;
String name;
public User(String id, String name){
this.id = id;
this.name = name;
}
public boolean equals(Object obj){
if(this == obj) return true;
if(obj==null || getClass()!=obj.getClass()) return false;
User o = (User)obj;
return Objects.equals(id, o.id) && Objects.equals(name, o.name);
} public static void main(String[] args){
HashSet<User> set = new HashSet<>();
User a = new User("1", "Robothy"), b = new User("1", "Robothy");
System.out.println(a.equals(b)); // 输出 true
System.out.println(a.hashCode() == b.hashCode()); // 输出 false
set.add(a);
set.add(b);
System.out.println(set.size()); // 输出 2,期望值应该为 1。 程序出现 BUG。
}
}

类似地,如果重写 hashCode 方法时使用的属性不是在 equals() 中用到的属性的子集,则 equals() 判断相等的两个对象也会出现 hashCode() 不等的情况。

import java.lang.String;
import java.util.Objects; class User{
String id;
String name; public boolean equals(Object obj){
if(this == obj) return true;
if(obj == null || obj.getClass() != getClass()) return false;
User o = (User)obj;
return Objects.equals(id, o.id);
} public int hashCode(){
return Objects.hash(id, name);
} public User(String id, String name){
this.id = id;
this.name = name;
} public static void main(String[] args){
User a = new User("1", "Robothy");
User b = new User("1", "Luo");
System.out.println(a.equals(b)); // 输出 true
System.out.println(a.hashCode() == b.hashCode()); /// 输出 false,不符合 hashCode 第 2 点约定
}
}

原则二 equals() / hashCode() 中应该使用能够标识对象的属性

所谓标识属性,即能够用来判断对象相等的属性,这些属性应该不经常发生变化。例如实体类中,有些字段诸如 updateTime,createTime 这些经常变化,且判断两个对象是否相等不需要用到它们,则不应该在重写 equals() 和 hashCode() 时应用它们。如果应用了这些可变字段,则可能导致同一个元素在哈希表中存放多次。

如下示例代码,使用 id 和 name 是两个标识属性,visitedTime 是一个经常变化,不用于识别一个 User。代码输出 1 的根本原因是两次 add 的时候,计算出来的哈希值不同,导致对象引用放到了不同的哈希桶中,使得同一个元素在 Set 中存放了两次。

import java.util.HashSet;
import java.util.Date;
import java.lang.String;
import java.util.Objects; class User{
String id;
String name;
Date visitedTime; public boolean equals(Object obj){
if(this == obj) return true;
if(obj == null || obj.getClass() != getClass()) return false;
User o = (User)obj;
return Objects.equals(id, o.id) && Objects.equals(name, o.name) && Objects.equals(visitedTime, o.visitedTime);
} public int hashCode(){
return Objects.hash(id, name, visitedTime);
} public User(String id, String name){
this.id = id;
this.name = name;
} public static void main(String[] args){
HashSet<User> set = new HashSet<>();
User user = new User("1", "Robothy");
set.add(user);
user.visitedTime = new Date();
set.add(user);
System.out.println(set.size()); // 输出 2,期望 1,程序出现 BUG
}
}

小结

equals() 和 hashCode() 在 Object 类中是两个本地方法,其实现跟虚拟机有关。自定义类未覆盖这两个方法的情况下,只有当两个对象引用指向同一个对象时,使用 equals() 比较这两个引用才返回 true;hashCode() 方法是将对象的地址转化为 32 位的整数,一般情况下不同的对象 hashCode() 返回的值是不同的。

Java 中还说明了 equals() 和 hashCode() 的一些性质和约定,开发人员在覆盖这两个方法时一定要符合这些约定,否则在使用 JDK 中的一些数据结构时会出现 BUG,典型的数据结构是哈希表,例如:HashMap, HashSet。最后总结了两条简单原则,并提供了一个正确覆盖这两个方法的代码模板。

Java 中的 equals() 和 hashCode()的更多相关文章

  1. Java中的equals和hashCode方法

    本文转载自:Java中的equals和hashCode方法详解 Java中的equals方法和hashCode方法是Object中的,所以每个对象都是有这两个方法的,有时候我们需要实现特定需求,可能要 ...

  2. Java中==、equals、hashcode的区别与重写equals以及hashcode方法实例(转)

    Java中==.equals.hashcode的区别与重写equals以及hashcode方法实例  原文地址:http://www.cnblogs.com/luankun0214/p/4421770 ...

  3. Java中的equals和hashCode方法详解

    Java中的equals和hashCode方法详解  转自 https://www.cnblogs.com/crazylqy/category/655181.html 参考:http://blog.c ...

  4. 转:Java中的equals和hashCode方法详解

    转自:Java中的equals和hashCode方法详解 Java中的equals方法和hashCode方法是Object中的,所以每个对象都是有这两个方法的,有时候我们需要实现特定需求,可能要重写这 ...

  5. Java中的equals()和hashCode() - 超详细篇

    前言 大家好啊,我是汤圆,今天给大家带来的是<Java中的equals()和hashCode() - 详细篇>,希望对大家有帮助,谢谢 文章纯属原创,个人总结难免有差错,如果有,麻烦在评论 ...

  6. java集合(3)- Java中的equals和hashCode方法详解

    参考:http://blog.csdn.net/jiangwei0910410003/article/details/22739953 Java中的equals方法和hashCode方法是Object ...

  7. 【转】Java中==、equals、hashcode的区别与重写equals以及hashcode方法实例

    原文地址:http://www.cnblogs.com/luankun0214/p/4421770.html 感谢网友的分享,记录下来只为学习. 1.重写equals方法实例   部分代码参考http ...

  8. Java中==、equals、hashcode的区别与重写equals以及hashcode方法实例

    1.重写equals方法实例   部分代码参考http://blog.csdn.net/wangloveall/article/details/7899948 重写equals方法的目的是判断两个对象 ...

  9. java中==和equals和hashcode的区别详解

    一.相同点 都是用来进行值或对象的比较. 二.不同点 对于“==”而言,对于基本类型(char,byte,short,int,long,float,double,boolean),对比的是值,所以是相 ...

随机推荐

  1. Flutter开发实战笔记

    下载 https://flutter.cn/docs/get-started/install/macos#get-sdk 配置环境变量 export PATH="$PATH:[PATH_TO ...

  2. THE BUG 队第一次团队作业

    1.队名: THE BUG 队 2.队员学号: 杨梓琦 3118005115(队长) 温海源,3118005109 陈杰才,3118005089 李华,3118005097 钟明康,311800512 ...

  3. 第 3 篇 Scrum 冲刺博客

    每天举行会议 会议照片: 昨天已完成的工作与今天计划完成的工作及工作中遇到的困难: 成员姓名 昨天完成工作 今天计划完成的工作 工作中遇到的困难 蔡双浩 了解任务,并做相关学习和思考,创建基本的收藏夹 ...

  4. jq中$(function(){})和js中window.onload区别

    先看下执行代码: $(function(){   console.log("jq");}) $(function(){   console.log("jq1") ...

  5. 深入理解.NET/WPF内存泄漏

    众所周知,内存管理和如何避免内存泄漏(memory leak)一直是软件开发的难题.不要说C.C++等非托管(unmanaged)语言,即使是Java..NET等托管(managed)语言,尽管有着完 ...

  6. 「TJOI / HEOI2016」求和 的一个优秀线性做法

    我们把\(S(i, j)j!\)看成是把\(i\)个球每次选择一些球(不能为空)扔掉,选\(j\)次后把所有球都扔掉的情况数(顺序有关).因此\(S(i, j)j! = i![x^i](e^x - 1 ...

  7. 题解-MtOI2019 幽灵乐团

    题面 MtOI2019 幽灵乐团 给定 \(p\),\(Cnt\) 组测试数据,每次给 \(a,b,c\),求 \[\prod_{i=1}^a\prod_{j=1}^b\prod_{k=1}^c\le ...

  8. 谈谈MySQL bin log的写入机制、以及线上的参数是如何配置的

    目录 一.binlog 的高速缓存 二.刷盘机制 三.推荐的策略 推荐阅读 问个问题吧!为什么你需要了解binlog的落盘机制呢? 我来回答一下: ​ 上一篇文章提到了生产环境中你可以使用binlog ...

  9. JavaSE18-字节缓冲流&字符流

    1.字节缓冲流 1.1 字节缓冲流构造方法 字节缓冲流介绍 BufferOutputStream:该类实现缓冲输出流. 通过设置这样的输出流,应用程序可以向底层输出流写 入字节,而不必为写入的每个字节 ...

  10. MySQL数据库死锁分析

    背景说明: 公司内部一套自建分布式交易服务平台,在POC稳定性压力测试的时候出现了数据库死锁.(InnoDB引擎)由于保密性,假设是app_test表死锁了. 现象: 发生异常:Deadlock fo ...