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

2025年 阅读约 8 分钟 面试指南 · Java面试

深入解析Java中==和equals()的区别,从基本概念到HashMap底层原理,分初级、中级、高级三个层次全面讲解。

一句话总结

== 比较栈中的值(基本类型比数值,引用类型比地址)→ equals() 默认比地址(Object 实现),但 String/Integer 等重写后比内容。重写 equals 必须同时重写 hashCode(HashMap 先用 hashCode 定位桶,再用 equals 判断相等)。Integer 有 -128~127 缓存,范围内 == 为 true。

初级理解

== 是关系运算符,比较的是两个变量的是否相等。对于基本数据类型(int、long、boolean等),比较的就是数值;对于引用类型,比较的是内存地址(即两个引用是否指向同一个对象)。

equals() 是 Object 类的方法,默认实现也是比较内存地址(和 == 一样),但大多数类(如 String、Integer)都重写了 equals() 方法,改为比较对象的内容是否相等。

// == 比较基本类型:比较数值 int a = 10; int b = 10; System.out.println(a == b); // true // == 比较引用类型:比较地址 String s1 = new String("hello"); String s2 = new String("hello"); System.out.println(s1 == s2); // false,两个不同对象 System.out.println(s1.equals(s2)); // true,内容相同 // 字符串常量池的影响 String s3 = "hello"; String s4 = "hello"; System.out.println(s3 == s4); // true,指向常量池同一个对象
一句话总结:== 比较的是栈中的值(基本类型比数值,引用类型比地址);equals() 默认比地址,但大多数类重写后比内容。

中级深入

重写 equals() 的规范:根据 Object.equals() 的 JavaDoc,重写 equals() 必须满足以下五个条件:

1. 自反性:x.equals(x) 必须返回 true

2. 对称性:x.equals(y)y.equals(x) 结果必须一致

3. 传递性:如果 x.equals(y)y.equals(z),则 x.equals(z)

4. 一致性:多次调用 equals() 结果不变(前提是对象信息没被修改)

5. 非空性:x.equals(null) 必须返回 false

重写 equals() 必须同时重写 hashCode():这是 Java 规范强制要求的。如果两个对象 equals() 返回 true,它们的 hashCode() 必须相等。反之不一定成立(哈希冲突)。

public class Person { private String name; private int age; @Override public boolean equals(Object o) { if (this == o) return true; // 自反性优化 if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return age == person.age && Objects.equals(name, person.name); } @Override public int hashCode() { return Objects.hash(name, age); // 必须同时重写 } }
注意:如果只重写 equals() 不重写 hashCode(),将导致对象在 HashSet、HashMap 等哈希容器中出现"找不到"的诡异问题——两个 equals 为 true 的对象,因为 hashCode 不同被放到了不同的桶里。

高级拓展

HashMap 中 equals() 和 hashCode() 的联动机制:

HashMap 的 put 流程中,先通过 hashCode() 定位桶的位置,再通过 equals() 判断桶内链表/红黑树中是否存在相同的 key:

// HashMap.putVal() 简化逻辑 Node<K,V>[] tab = table; int index = (n - 1) & hash; // 通过 hashCode 定位桶 // 在桶内遍历,通过 equals 判断 key 是否相同 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) // 找到了,替换 value

为什么重写 equals 必须重写 hashCode?因为 HashMap 先用 hashCode 定位桶。如果两个对象 equals 为 true 但 hashCode 不同,它们会被放到不同的桶里,HashMap 就无法正确识别它们是"同一个 key",导致重复存储。

Integer 的缓存陷阱:Integer 默认缓存了 -128 到 127 的对象(可通过 JVM 参数调整上限)。在这个范围内,== 比较会返回 true(因为指向缓存池中的同一个对象),超出范围则返回 false。

Integer a = 100; Integer b = 100; System.out.println(a == b); // true(在缓存范围内) Integer c = 200; Integer d = 200; System.out.println(c == d); // false(超出缓存范围) System.out.println(c.equals(d)); // true(始终比内容)

Lombok @Data 的坑:@Data 注解会自动生成 equals() 和 hashCode(),默认使用所有非静态字段。如果实体类中有懒加载的关联对象(如 JPA 的 @ManyToOne),可能导致意外的数据库查询或循环调用。建议使用 @EqualsAndHashCode(exclude = {"关联字段"}) 排除不需要的字段。

面试加分项:能说出 HashMap 中 hashCode 定位桶 + equals 判断相等的联动机制,说明你看过 JDK 源码,面试官会对你印象深刻。

实战场景

场景:Lombok @Data 的 equals/hashCode 坑

// 反例:@Data 默认使用所有字段,JPA 懒加载会触发额外查询 @Entity @Data public class Order { @Id private Long id; @ManyToOne(fetch = FetchType.LAZY) private User user; // equals/hashCode 会触发 user 的查询! } // 正例:排除懒加载字段 @Entity @Data @EqualsAndHashCode(exclude = "user") public class Order { @Id private Long id; @ManyToOne(fetch = FetchType.LAZY) private User user; }

场景:集合操作中的 equals 陷阱

// 问题:只重写 equals 不重写 hashCode Set<Person> set = new HashSet<>(); Person p1 = new Person("张三", 20); Person p2 = new Person("张三", 20); set.add(p1); System.out.println(set.contains(p2)); // false!因为 hashCode 不同 // 解决:同时重写 equals 和 hashCode // 或使用 IDE 生成 / Lombok @EqualsAndHashCode

面试模拟

Q:为什么重写 equals 必须重写 hashCode?

A:因为 HashMap/HashSet 等哈希容器先用 hashCode 定位桶,再用 equals 判断相等。如果两个对象 equals 为 true 但 hashCode 不同,它们会被放到不同桶中,导致 contains() 返回 false、重复存储等问题。这是 Java 规范强制要求的。

Q:Integer 用 == 比较有什么坑?

A:Integer 默认缓存 -128~127 的对象,范围内 == 比较返回 true(指向缓存池同一对象),超出范围返回 false(不同对象)。比较 Integer 值始终用 equals(),不要依赖 ==。