hashCode() 方法
hashCode 是什么?
hashCode() 返回一个整数,用于哈希表(HashMap、HashSet)的快速查找。
简单说:哈希表先用 hashCode 定位「桶」,再用 equals 确认「是否真的相等」。
为什么需要 hashCode?
没有 hashCode,哈希表只能线性查找:
遍历所有元素 → O(n)有 hashCode,先算哈希值,定位到特定「桶」:
计算 hashCode → 定位桶 → 桶内查找 → O(1)hashCode 约定
如果
equals返回 true,两个对象的hashCode必须相同。
| 关系 | 必须满足 |
|---|---|
| equals true | → hashCode 必须相同 |
| hashCode 相同 | → equals 可能 true 也可能 false |
| hashCode 不同 | → equals 必定 false |
重写 hashCode
基本重写模式
java
class Person {
private String name;
private int age;
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}手动实现
java
class Person {
private String name;
private int age;
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
}为什么用 31?
- 奇数,避免乘积溢出时丢失信息
- 31 = 32 - 1,JVM 会优化为
result * 31 = result << 5 - result - 哈希分布均匀,不容易冲突
常见错误
错误一:hashCode 返回固定值
java
class Broken {
private String name;
@Override
public int hashCode() {
return 42; // ❌ 所有对象都在同一个桶,退化到 O(n)
}
}错误二:使用可变字段
java
class Broken {
private String name;
@Override
public int hashCode() {
return name.hashCode(); // ❌ name 改变后,hashCode 改变!
}
}
Set<Broken> set = new HashSet<>();
Broken b = new Broken("张三");
set.add(b);
b.setName("李四");
set.contains(b); // ❌ false!对象在 set 里「丢失」了原则:用于 hashCode 的字段必须是不可变的。
错误三:equals 和 hashCode 不一致
这是最严重的错误:
java
class Broken {
private int value;
@Override
public boolean equals(Object o) {
return o instanceof Broken b && value == b.value;
}
@Override
public int hashCode() {
return value * 2; // ❌ equals 相等但 hashCode 不同!
}
}
Broken a = new Broken(5);
Broken b = new Broken(5);
Map<Broken, String> map = new HashMap<>();
map.put(a, "hello");
map.get(b); // ❌ null!因为 hashCode 不同,定位到了错误的桶集合中的 hashCode
HashMap 和 HashSet 依赖 hashCode:
java
class Person {
private String name;
@Override
public boolean equals(Object o) {
return o instanceof Person p && name.equals(p.name);
}
@Override
public int hashCode() {
return name.hashCode();
}
}
Set<Person> set = new HashSet<>();
Person p1 = new Person("张三");
Person p2 = new Person("张三");
set.add(p1);
set.size(); // 1
set.contains(p2); // true,equals=true 且 hashCode 相同
set.add(p2);
set.size(); // 仍然是 1,因为是相等的元素String 的 hashCode
String 重写了 hashCode,相同内容的字符串 hashCode 一定相同:
java
String a = new String("hello");
String b = new String("hello");
a.hashCode() == b.hashCode(); // true
// 这也是为什么 String 常作为 HashMap 的 key
Map<String, Integer> map = new HashMap<>();
map.put("key", 100);
map.get("key"); // 100hashCode 常用技巧
1. 用 Objects.hash()
java
@Override
public int hashCode() {
return Objects.hash(field1, field2, field3);
}2. 缓存 hashCode
java
class Heavy {
private final String data; // 假设计算很耗时
private int hash; // 缓存
@Override
public int hashCode() {
if (hash == 0) {
hash = data.hashCode();
}
return hash;
}
}3. 排除不参与 equals 的字段
java
class User {
private String id; // 参与 equals
private String password; // 不参与 equals,但也不参与 hashCode
private int loginCount; // 不参与 equals,也不参与 hashCode
@Override
public boolean equals(Object o) {
return o instanceof User u && id.equals(u.id);
}
@Override
public int hashCode() {
return id.hashCode(); // 只用参与 equals 的字段
}
}总结
重写 hashCode 的原则:
| 原则 | 说明 |
|---|---|
| 一致性 | equals 为 true 时,hashCode 必须相同 |
| 参与字段 | 必须用参与 equals 的字段 |
| 不可变性 | 用于 hashCode 的字段最好不要变 |
| 分布均匀 | 哈希冲突越少越好 |
记住:equals 和 hashCode 必须同时重写,保持一致。
