Object.hashCode()
是 Java 中所有对象继承自 java.lang.Object
类的一个核心方法,用于返回对象的哈希码(Hash Code)。它是 Java 集合框架(尤其是基于哈希的集合如 HashMap
、HashSet
、Hashtable
)实现高效查找、插入和删除操作的基础。
一、核心概念
1. 什么是哈希码?
- 哈希码是一个 int 类型的整数,由对象的内容或内存地址通过某种算法计算得出。
- 它用于快速定位对象在哈希表中的“桶”(bucket)位置。
- 哈希码不是内存地址本身,但默认实现可能基于内存地址。
2. hashCode()
方法签名
public native int hashCode();
- 是一个 native 方法(由 JVM 底层实现,通常用 C/C++ 编写)。
- 返回值类型为
int
,范围:-2^31
到2^31 - 1
。
二、hashCode()
与 equals()
的契约(必须遵守!)
Java 规范中明确规定了 hashCode()
和 equals(Object obj)
方法之间的关系,如果重写了 equals()
,通常也必须重写 hashCode()
。
核心契约(三条):
条件 | 要求 |
---|---|
1. 一致性 | 在同一程序执行期间,只要对象用于 equals() 比较的字段没有改变,hashCode() 必须返回相同的整数。 |
2. 相等性传递 | 如果 x.equals(y) 返回 true ,那么 x.hashCode() 必须等于 y.hashCode() 。 |
3. 不相等对象 | 如果 x.equals(y) 返回 false ,x.hashCode() 和 y.hashCode() 不一定不同(可能发生哈希冲突)。 |
✅ 关键推论:
如果两个对象equals
为true
,它们的hashCode
必须相同。
如果hashCode
不同,则equals
一定为false
。
三、默认行为
1. Object
类的默认实现
- 默认的
hashCode()
通常基于对象的内存地址(通过某种算法转换为 int)。 - 不同对象(即使内容相同)的
hashCode()
通常不同。
public class Person {
private String name;
private int age;
// 未重写 equals 和 hashCode
}
Person p1 = new Person("Alice", 25);
Person p2 = new Person("Alice", 25);
System.out.println(p1.equals(p2)); // false(引用不同)
System.out.println(p1.hashCode()); // 例如:123456789
System.out.println(p2.hashCode()); // 例如:987654321(不同)
四、为什么要重写 hashCode()
?
场景:使用 HashMap
存储自定义对象
Map<Person, String> map = new HashMap<>();
map.put(new Person("Alice", 25), "Engineer");
map.put(new Person("Bob", 30), "Manager");
// 尝试获取
Person key = new Person("Alice", 25);
System.out.println(map.get(key)); // ❌ 输出 null!
原因分析:
HashMap
查找时,先通过key.hashCode()
定位桶。- 再通过
key.equals()
在桶内查找精确匹配。 - 由于
Person
未重写hashCode()
,两个内容相同的Person
对象哈希码不同,被放入不同桶中,导致查找不到。
解决方案:重写 hashCode()
和 equals()
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); // 使用 JDK 工具类
}
}
现在 map.get(key)
将正确返回 "Engineer"
。
五、如何正确重写 hashCode()
?
推荐方法:使用 Objects.hash()
@Override
public int hashCode() {
return Objects.hash(name, age, email); // 支持多个字段
}
手动实现(了解原理)
@Override
public int hashCode() {
int result = 17;
result = 31 * result + name.hashCode();
result = 31 * result + age;
result = 31 * result + (email != null ? email.hashCode() : 0);
return result;
}
为什么是 31?
- 31 是一个奇素数,乘法可优化为
(i << 5) - i
(位运算更快)。- 能有效减少哈希冲突。
六、常见错误与注意事项
错误 | 说明 | 正确做法 |
---|---|---|
只重写 equals() 不重写 hashCode() |
哈希集合中对象无法正确查找 | 两者必须同时重写 |
使用可变字段计算 hashCode() |
对象放入集合后修改字段,导致哈希码变化,无法再找到 | 使用不可变字段(如 ID),或确保放入集合后不修改 |
hashCode() 返回常量 |
所有对象哈希码相同,退化为链表查找,性能极差 | 应基于字段内容计算 |
浮点数处理不当 | float 和 double 需特殊处理 |
使用 Float.floatToIntBits() 或 Double.doubleToLongBits() |
七、使用技巧与最佳实践
使用 IDE 自动生成
IntelliJ IDEA / Eclipse 支持自动生成equals()
和hashCode()
,选择关键字段即可。使用 Lombok(推荐)
添加注解,自动实现:@Data // 包含 @EqualsAndHashCode public class Person { private String name; private int age; }
性能优化
- 优先使用不可变对象作为
HashMap
的 key。 - 避免在
hashCode()
中进行复杂计算(如数据库查询)。
- 优先使用不可变对象作为
调试技巧
打印哈希码帮助调试:System.out.println("Hash: " + obj.hashCode()); System.out.println("Hash in hex: " + Integer.toHexString(obj.hashCode()));
八、hashCode()
在集合中的应用
集合类 | 使用方式 |
---|---|
HashMap / LinkedHashMap |
基于 hashCode() 分桶,equals() 判断相等 |
HashSet |
底层是 HashMap ,同样依赖 hashCode() |
Hashtable |
与 HashMap 类似 |
ConcurrentHashMap |
线程安全的哈希表,同样依赖 |
九、总结
hashCode()
是 Java 对象的“指纹”之一,用于哈希集合的高效操作。- 必须遵守
equals()
和hashCode()
的契约。 - 自定义类作为
HashMap
的 key 或HashSet
的元素时,必须重写hashCode()
。 - 推荐使用
Objects.hash()
或 Lombok 自动生成。 - 避免使用可变字段影响
hashCode()
。
📌 黄金法则:
“如果两个对象逻辑上相等(equals
为true
),它们的hashCode
必须相同。”
✅ 掌握 hashCode()
,是写出高质量 Java 代码的关键一步!