一文读懂 Java 中的 ==、equals () 与 hashCode ()原理与避坑指南
作者:凛冬君主
在 Java 开发中,==
、equals()
和 hashCode()
是处理对象比较和哈希计算的核心元素,理解它们之间的区别与联系对编写高质量代码至关重要。
一、== 运算符
==
是 Java 中的比较运算符,用于比较两个值是否相等,其行为取决于比较的是基本类型还是引用类型:
1. 比较基本数据类型
对于 int
、double
、char
等基本类型,==
比较的是实际存储的值:
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,指向同一个对象
注意点
- 基本类型的包装类(如
Integer
、Double
)使用==
时,同样比较地址而非值(除非触发常量池缓存机制) - 基本数据类型不能与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 类型的哈希值,主要用于哈希表(如 HashMap
、HashSet
)中快速定位对象。
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,提高计算效率。
四、三者之间的关系总结
==
与equals()
:- 未重写
equals()
时,两者功能一致(比较地址) - 重写
equals()
后,==
仍比较地址,equals()
比较内容
- 未重写
equals()
与hashCode()
:- 核心约定:
equals()
为 true →hashCode()
必须相等 - 反之不成立:
hashCode()
相等 →equals()
不一定为 true(哈希冲突) - 实际应用:在哈希集合中,先通过
hashCode()
定位,再用equals()
确认
- 核心约定:
使用场景:
- 比较基本类型 → 用
==
- 比较对象地址 → 用
==
- 比较对象内容 → 用
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()
比较值。
- 答:Integer 对 -128~127 范围的值有缓存,因此
到此这篇关于一文读懂 Java 中的 ==、equals () 与 hashCode ()原理与避坑指南的文章就介绍到这了,更多相关java ==、equals () 与 hashCode ()内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!