一文读懂 Java 中的 ==、equals () 与 hashCode ()原理与避坑指南

 更新时间:2025年09月05日 10:49:28   作者:凛冬君主  
在 Java 开发中,==、equals() 和 hashCode() 是处理对象比较和哈希计算的核心元素,理解它们之间的区别与联系对编写高质量代码至关重要,本文给大家介绍Java 中的 ==、equals()与hashCode()原理,感兴趣的朋友一起看看吧

在 Java 开发中,==equals() 和 hashCode() 是处理对象比较和哈希计算的核心元素,理解它们之间的区别与联系对编写高质量代码至关重要。

一、== 运算符

== 是 Java 中的比较运算符,用于比较两个值是否相等,其行为取决于比较的是基本类型还是引用类型:

1. 比较基本数据类型

对于 intdoublechar 等基本类型,== 比较的是实际存储的值

int a = 10;
int b = 10;
System.out.println(a == b); // true,值相等
double c = 3.14;
double d = 3.14;
System.out.println(c == d); // true

2. 比较引用数据类型

对于对象(引用类型),== 比较的是对象在内存中的地址(即是否为同一个对象):

因为java是值传递,这里可能是地址的副本进行比较,根据这个副本也可以修改对象。

但是,这种比较不关心变量是否相同,只关心引用的对象是否相同。

String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // false,两个不同的对象,地址不同
String s3 = s1;
System.out.println(s1 == s3); // true,指向同一个对象

注意点

  • 基本类型的包装类(如 IntegerDouble)使用 == 时,同样比较地址而非值(除非触发常量池缓存机制)
  • 基本数据类型不能与null比较,因为基本数据类型就没有null这个值。
  • null == null 结果为 true,任何对象与 null 用 == 比较都为 false

(1)基本类型 vs 包装类(==比较)

  • 包装类会自动拆箱为基本类型,比较的是值。
    例:int a = 5; Integer b = 5; System.out.println(a == b); // true

(2)包装类 vs 包装类(==比较)

  • 比较的是对象的引用地址(是否为同一个对象),而非值。
  • 注意:Java 对Integer[-128, 127]范围内有缓存机制,超出此范围会创建新对象。
    例 1:Integer a = 100; Integer b = 100; System.out.println(a == b); // true(使用缓存)
    例 2:Integer a = 200; Integer b = 200; System.out.println(a == b); // false(新对象)
  • 当其中一个为new Integer(1)时,是强制显式创建一个对象,这里会开辟一个新的对象,即使缓存中有也不会使用。这时候比较就是false

二、equals () 方法

equals() 是 Object 类定义的实例方法,用于判断两个对象是否 "相等",默认行为与 == 一致,比较的是对象的内存地址(即是否为同一个对象)。

1. 默认实现(Object 类中)

public boolean equals(Object obj) {
    return (this == obj); // 本质就是用 == 比较地址
}

2. 重写后的常见实现

多数类会重写 equals() 方法,使其比较对象的内容而非地址,例如 String 类:

String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1.equals(s2)); // true,内容相同
List<Integer> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
System.out.println(list1.equals(list2)); // true,两个空列表内容相同

3. 重写 equals () 的规范

重写 equals() 时需遵循以下规则(来自《Effective Java》):

  • 自反性x.equals(x) 必须返回 true
  • 对称性:若 x.equals(y) 为 true,则 y.equals(x) 也必须为 true
  • 传递性:若 x.equals(y) 和 y.equals(z) 为 true,则 x.equals(z) 也必须为 true
  • 一致性:多次调用 x.equals(y) 应返回相同结果(前提是对象未被修改)
  • 非空性x.equals(null) 必须返回 false

4. 重写示例(自定义类)

public class User {
    private String id;
    private String name;
    // 构造方法、getter、setter 省略
    @Override
    public boolean equals(Object o) {
        // 1. 自身判断
        if (this == o) return true;
        // 2. 类型判断
        if (o == null || getClass() != o.getClass()) return false;
        // 3. 内容比较
        User user = (User) o;
        return Objects.equals(id, user.id) && 
               Objects.equals(name, user.name);
    }
}

注意点

  • 若调用者是null(如null.equals(obj)):必抛空指针异常。
  • 若参数是null(如obj.equals(null)):返回false(因为null不是任何对象的实例)。
  • 如果两个对象通过equals()比较返回true,那么它们的hashCode()必须返回相同的值;反之,hashCode()不同的对象,equals()必须返回false

三、hashCode () 方法

hashCode() 也是 Object 类的方法,返回一个 int 类型的哈希值,主要用于哈希表(如 HashMapHashSet)中快速定位对象。

1. 基本作用

  • 哈希值用于确定对象在哈希表中的存储位置
  • 提高哈希表的查找效率(理想情况下,不同对象应具有不同哈希值)

2. 默认实现

Object 类的 hashCode() 返回对象的内存地址转换后的整数(不同 JVM 实现可能不同)。

3. 重写原则

关键规则如果两个对象通过 equals() 比较相等,则它们的 hashCode() 必须返回相同的值。反之则不成立(不同对象也可能有相同哈希值,即哈希冲突)。

这是因为哈希表在判断对象是否存在时,会先通过哈希值定位,再用 equals() 精确比较。若违反此规则,会导致哈希表无法正常工作:

// 反例:equals相等但hashCode不同
class BadExample {
    private int value;
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        BadExample that = (BadExample) o;
        return value == that.value;
    }
    @Override
    public int hashCode() {
        return (int) (Math.random() * 1000); // 错误实现:相同对象可能返回不同哈希值
    }
}

4. 正确的重写实现

通常结合对象中参与 equals() 比较的字段来计算哈希值:

@Override
public int hashCode() {
    // 使用 Objects.hash() 简化实现
    return Objects.hash(id, name);
}
// 等价于手动计算
@Override
public int hashCode() {
    int result = id != null ? id.hashCode() : 0;
    result = 31 * result + (name != null ? name.hashCode() : 0);
    return result;
}

使用 31 是因为它是一个质数,能减少哈希冲突,且 31 * i 可以被优化为 (i << 5) - i,提高计算效率。

四、三者之间的关系总结

  1. == 与 equals()

    • 未重写 equals() 时,两者功能一致(比较地址)
    • 重写 equals() 后,== 仍比较地址,equals() 比较内容
  2. equals() 与 hashCode()

    • 核心约定:equals() 为 true → hashCode() 必须相等
    • 反之不成立:hashCode() 相等 → equals() 不一定为 true(哈希冲突)
    • 实际应用:在哈希集合中,先通过 hashCode() 定位,再用 equals() 确认
  3. 使用场景

    • 比较基本类型 → 用 ==
    • 比较对象地址 → 用 ==
    • 比较对象内容 → 用 equals()
    • 自定义类用于哈希表 → 必须同时重写 equals() 和 hashCode()

五、常见面试题解析

  • 为什么重写 equals () 必须重写 hashCode ()?
    • 答:为了保证希表(如 哈HashMap)的正确性。如果两个对象 equals 相等但 hashCode 不同,会导致它们在哈希表中被存储在不同位置,从而出现 "相同对象却被视为不同" 的情况。
  • String 类的 == 和 equals () 有什么区别?
    • 答:== 比较对象地址,equals() 比较字符串内容。由于字符串常量池的存在,"abc" == "abc" 为 true,但 new String("abc") == new String("abc") 为 false。
  • Integer 类型用 == 比较时要注意什么?
    • 答:Integer 对 -128~127 范围的值有缓存,因此 Integer a = 100; Integer b = 100; a == b 为 true,但超出此范围则为 false,应始终用 equals() 比较值。

到此这篇关于一文读懂 Java 中的 ==、equals () 与 hashCode ()原理与避坑指南的文章就介绍到这了,更多相关java ==、equals () 与 hashCode ()内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java实现深度搜索DFS算法详解

    Java实现深度搜索DFS算法详解

    深度优先搜索是一种在开发爬虫早期使用较多的方法。它的目的是要达到被搜索结构的叶结点。这篇文章主要介绍了基于Java实现深度优先搜索(DFS)算法,需要的朋友可以参考一下
    2021-12-12
  • SpringCloud服务接口调用OpenFeign及使用详解

    SpringCloud服务接口调用OpenFeign及使用详解

    这篇文章主要介绍了SpringCloud服务接口调用——OpenFeign,在学习Ribbon时,服务间调用使用的是RestTemplate+Ribbon实现,而Feign在此基础上继续进行了封装,使服务间调用变得更加方便,需要的朋友可以参考下
    2023-04-04
  • Java abstract class 与 interface对比

    Java abstract class 与 interface对比

    这篇文章主要介绍了 Java abstract class 与 interface对比的相关资料,需要的朋友可以参考下
    2016-12-12
  • 详解Springboot应用中设置Cookie的SameSite属性

    详解Springboot应用中设置Cookie的SameSite属性

    Chrome 51 开始,浏览器的 Cookie 新增加了一个SameSite属性,用来防止 CSRF 攻击和用户追踪。今天通过本文给大家介绍Springboot应用中设置Cookie的SameSite属性,感兴趣的朋友一起看看吧
    2022-01-01
  • spring-boot-maven-plugin插件爆红问题及解决方案

    spring-boot-maven-plugin插件爆红问题及解决方案

    这篇文章主要介绍了spring-boot-maven-plugin插件爆红问题及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-05-05
  • @Autowired注解以及失效的几个原因图文详解

    @Autowired注解以及失效的几个原因图文详解

    在微服务项目中,会遇到@Autowired注解失效的情况,下面这篇文章主要给大家介绍了关于@Autowired注解以及失效的几个原因的相关资料,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2023-03-03
  • Spring整合quartz做定时任务的示例代码

    Spring整合quartz做定时任务的示例代码

    这篇文章主要介绍了在spring项目使用quartz做定时任务,首先我这里的项目已经是一个可以跑起来的完整项目,web.xml里面的配置我就不贴出来了,具体实例代码跟随小编一起看看吧
    2022-01-01
  • mybatis中批量更新多个字段的2种实现方法

    mybatis中批量更新多个字段的2种实现方法

    当我们使用mybatis的时候,可能经常会碰到一批数据的批量更新问题,因为如果一条数据一更新,那每一条数据就需要涉及到一次数据库的操作,本文主要介绍了mybatis中批量更新多个字段的2种实现方法,感兴趣的可以了解一下
    2023-09-09
  • Java List<JSONObject>中的数据如何转换为List<T>

    Java List<JSONObject>中的数据如何转换为List<T>

    这篇文章主要介绍了Java List<JSONObject>中的数据如何转换为List<T>问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-05-05
  • Java Semaphore实现高并发场景下的流量控制

    Java Semaphore实现高并发场景下的流量控制

    在java开发的工作中是否会出现这样的场景,你需要实现一些异步运行的任务,该任务可能存在消耗大量内存的情况,所以需要对任务进行并发控制。本文将介绍通过Semaphore类优雅的实现并发控制,感兴趣的可以了解一下
    2021-12-12

最新评论