hashCode() 是什么?
hashCode() 是 Java 中 Object 类的一个方法,这意味着所有 Java 对象都继承了这个方法。

它的核心作用是:返回一个整数值,这个整数值被称为“哈希码”(Hash Code)或“散列码”。
可以把它想象成对象的“数字指纹”或“身份证号码”,它本身并不唯一,但目的是为了在高效的数据结构(特别是 HashMap, HashSet, Hashtable)中快速地定位和存储对象。
为什么需要 hashCode()?(核心作用)
hashCode() 的主要应用场景是哈希表,HashMap,为了理解它的工作原理,我们来看一下 HashMap 是如何存储数据的:
- 计算哈希码:当你向
HashMap中添加一个键值对(map.put(key, value))时,HashMap首先会调用key对象的hashCode()方法,得到一个哈希码。 - 计算桶索引:
HashMap内部有一个数组,称为“桶”(Bucket),它会通过一个特定的算法(通常是hash & (n-1),n是桶的数量)将这个哈希码转换成一个数组的索引(位置)。 - 存储对象:
HashMap就会将这个键值对存放到计算出的索引位置的桶中。
这个过程的好处是:
通过 hashCode(),HashMap 可以直接计算出对象应该存储在哪个位置,而无需遍历整个数组,这使得查找、插入和删除操作的平均时间复杂度接近 O(1),效率极高,如果不用 hashCode(),每次查找都需要遍历所有元素,时间复杂度就是 O(n),效率会大大降低。

hashCode() 和 equals() 的“契约”(The Golden Rule)
这是理解 hashCode() 最关键的一点,Java 官方文档中定义了 hashCode() 和 equals() 之间必须遵守的“契约”:
-
一致性:在对象的
equals()方法没有被修改的情况下,多次调用同一个对象的hashCode()方法,必须返回相同的整数。- 注意:这个要求不适用于应用程序的一次执行到另一次执行期间,也不适用于不同 JVM 的执行,你可以在一次运行中得到
123,在重启程序后得到456,但在一次程序运行期间,只要对象内容没变,hashCode()必须稳定。
- 注意:这个要求不适用于应用程序的一次执行到另一次执行期间,也不适用于不同 JVM 的执行,你可以在一次运行中得到
-
核心规则:如果两个对象根据
equals()方法是相等的,那么这两个对象的hashCode()方法必须返回相同的整数。- 换句话说:
a.equals(b)为true,则a.hashCode()必须等于b.hashCode()。
- 换句话说:
-
非强制反向规则:如果两个对象的
hashCode()值相等,它们不一定是相等的(即a.equals(b)不一定为true),这被称为哈希冲突。
(图片来源网络,侵删)- 两个不同的对象可能计算出相同的哈希码,就像两个不同的人可能有相同的身份证号码一样,当发生哈希冲突时,
HashMap会通过链表或红黑树在同一个桶里存储这些对象,然后通过equals()方法来精确比较它们是否真的相等。
- 两个不同的对象可能计算出相同的哈希码,就像两个不同的人可能有相同的身份证号码一样,当发生哈希冲突时,
总结这个契约:
| 比较条件 | equals() 结果 |
hashCode() 结果 |
是否必须成立 |
|---|---|---|---|
| 两个对象相等 | true |
必须相等 | 是 (核心规则) |
| 两个对象不相等 | false |
可以相等或不相等 | 是 (允许冲突) |
hashCode() 相等 |
可以相等或不相等 | - | 是 (允许冲突) |
违反契约的后果:
如果你在自定义类中只重写了 equals() 而没有重写 hashCode(),或者两者逻辑不一致,那么当你把这个类的对象作为 HashMap 的 key 时,会出现严重问题:
场景示例:
class Person {
private String name;
public Person(String name) {
this.name = name;
}
// 重写了 equals,逻辑是 name 相等则对象相等
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return Objects.equals(name, person.name);
}
// 故意不重写 hashCode()
// @Override
// public int hashCode() { ... }
}
public class Main {
public static void main(String[] args) {
Map<Person, String> map = new HashMap<>();
Person p1 = new Person("Alice");
Person p2 = new Person("Alice");
// p1.equals(p2) 应该返回 true
System.out.println("p1.equals(p2): " + p1.equals(p2)); // true
map.put(p1, "Alice's Info");
// 因为 p1.equals(p2) 是 true,我们期望 get(p2) 能找到值
// 由于 p1 和 p2 的 hashCode() 不同(默认是对象的内存地址)
// HashMap 会在不同的桶里查找,导致 get(p2) 返回 null
System.out.println("map.get(p2): " + map.get(p2)); // 输出 null!
}
}
在上面的例子中,p1 和 p2 在逻辑上是相等的,但它们的默认 hashCode()(基于内存地址)不同。HashMap 认为它们是两个完全不同的 key,因此导致了错误的结果。
如何正确地重写 hashCode()?
当你在自定义类中重写了 equals() 方法后,必须也重写 hashCode() 方法。
推荐做法:
- 选择一个非零的常量作为哈希码的初始值,
result = 17,17 是一个质数,有助于减少哈希冲突。 - 为对象中参与
equals()比较的每一个关键属性(通常是final或private的字段)计算哈希码。 - 对于每个属性,使用其
hashCode()方法(如果是对象)或直接使用其值(如果是基本类型),然后将结果与result进行组合。 - 组合公式:
result = 31 * result + (attribute == null ? 0 : attribute.hashCode());- 为什么用
31?31 是一个质数,用31 * result可以有效地将不同的哈希码分散开。31 * i也可以被 JVM 优化为(i << 5) - i,性能很好。
- 为什么用
示例:
import java.util.Objects;
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 1. 重写 equals()
@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);
}
// 2. 重写 hashCode()
@Override
public int hashCode() {
// 使用 Objects.hash() 是最简单、最推荐的方式!
// 它内部就实现了上述的算法。
return Objects.hash(name, age);
/* 手动实现(理解原理):
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + age;
return result;
*/
}
}
强烈推荐使用 Objects.hash() 方法!
从 Java 7 开始,java.util.Objects 类提供了 hash(Object... values) 静态方法,它会自动帮你完成上述所有繁琐的计算,并且能很好地处理 null 值,这是现代 Java 编程的最佳实践。
hashCode() 的默认实现
如果你没有在自定义类中重写 hashCode(),那么它将使用 Object 类中的默认实现。
默认的 hashCode() 实现是基于对象的内存地址来生成一个整数值,这意味着,只有当两个对象是同一个对象(a == b 为 true)时,它们的默认哈希码才相等,对于任何通过 new 关键字创建的新对象,即使它们内容完全相同,它们的哈希码也不同。
| 特性 | 描述 |
|---|---|
| 定义 | Object 类的方法,返回一个整数值(哈希码)。 |
| 主要用途 | 为哈希表(如 HashMap, HashSet)提供快速查找和存储的依据。 |
| 核心契约 | a.equals(b) 为 true,则 a.hashCode() 必须等于 b.hashCode()。 |
| 重写规则 | 只要重写了 equals(),就必须重写 hashCode(),且逻辑要一致。 |
| 最佳实践 | 使用 Objects.hash(field1, field2, ...) 来轻松、正确地实现 hashCode()。 |
| 默认行为 | 基于对象的内存地址,不同对象的哈希码不同。 |
| 哈希冲突 | 两个不同的对象可能有相同的哈希码,这是允许的,HashMap 会通过 equals() 进一步处理。 |
理解并正确使用 hashCode() 是成为一名合格 Java 开发者的必备技能,尤其是在处理集合框架和性能优化方面。
