别再混淆了!一文彻底搞懂System.identityHashCode与Object.hashCode的区别
在Java开发中,哈希码的使用无处不在,但许多开发者对
System.identityHashCode()
和Object.hashCode()
的区别仍然模糊不清。本文将深入剖析二者的核心区别,并通过实际代码演示它们在不同场景下的行为差异。
一、本质定义:两种哈希码的起源
- Object.hashCode()
- 所有Java对象的默认方法(定义在
Object
类中) - 可被子类重写(通常基于对象内容计算)
// 默认实现(未重写时)
public native int hashCode(); - System.identityHashCode()
System
类提供的静态工具方法- 无视任何重写,始终返回JVM原始哈希码
public static native int identityHashCode(Object x);
二、核心区别对比(表格速查)
特性 | Object.hashCode() | System.identityHashCode() |
---|---|---|
是否可重写 | ✅ 子类可重写改变行为 | ❌ 行为固定不可变 |
对重写的敏感性 | 返回重写后的自定义值 | 永远返回JVM原始哈希码 |
null 处理 | 调用抛NullPointerException | 安全返回0 |
返回值一致性 | 内容改变时可能变化 | 对象生命周期内永不改变 |
典型用途 | HashMap /HashSet 等基于内容的集合 | IdentityHashMap 等身份敏感操作 |
三、关键差异深度解析
1. 重写行为对比(核心区别)
class CustomObject {
private int id;
// 重写hashCode(基于内容)
@Override
public int hashCode() {
return id * 31;
}
}
public static void main(String[] args) {
CustomObject obj = new CustomObject();
obj.id = 100;
System.out.println("hashCode: " + obj.hashCode()); // 3100
System.out.println("identityHashCode: " + System.identityHashCode(obj)); // 356573597
}
输出说明:
✅ hashCode()
返回重写后的计算值
✅ identityHashCode()
无视重写,返回JVM原始哈希
2. null安全性对比
Object obj = null;
// 抛出NullPointerException
try {
System.out.println(obj.hashCode());
} catch (NullPointerException e) {
System.out.println("调用hashCode()抛NPE");
}
// 安全返回0
System.out.println("identityHashCode(null): "
+ System.identityHashCode(obj));
3. 哈希码不变性验证
String str = "Hello";
int initialIdentity = System.identityHashCode(str);
str = str + " World!"; // 修改对象内容
// 身份哈希码保持不变
System.out.println(initialIdentity == System.identityHashCode(str)); // true
四、经典应用场景
1. 使用Object.hashCode()的场景
// 在HashMap中作为键(依赖内容哈希)
Map<Student, Grade> gradeMap = new HashMap<>();
Student s = new Student("2023001", "张三");
gradeMap.put(s, new Grade(90));
// 重写需遵守规范:内容相同则哈希码相同
class Student {
private String id;
private String name;
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}
2. 使用identityHashCode()的场景
场景1:实现身份敏感的容器
// IdentityHashMap基于身份哈希而非内容
IdentityHashMap<Object, String> identityMap = new IdentityHashMap<>();
String s1 = new String("ABC");
String s2 = new String("ABC");
identityMap.put(s1, "第一对象");
identityMap.put(s2, "第二对象"); // 不同对象,均可插入
System.out.println(identityMap.size()); // 2
场景2:检测hashCode是否被重写
boolean isHashCodeOverridden(Object obj) {
return obj.hashCode() != System.identityHashCode(obj);
}
// 测试
System.out.println(isHashCodeOverridden(new Object())); // false
System.out.println(isHashCodeOverridden(new String("Test"))); // true
场景3:调试对象内存关系
Object objA = new Object();
Object objB = objA; // 指向同一对象
// 身份哈希相同证明是同一对象
System.out.println(System.identityHashCode(objA)
== System.identityHashCode(objB)); // true
五、底层机制揭秘
- 存储位置:身份哈希码存储在对象头中
- 生成时机:首次调用
hashCode()
或identityHashCode()
时生成 - 计算规则:通常基于内存地址,但JVM会优化(非直接地址)
- 不变性:一旦生成,在对象生命周期内永不改变
六、总结与最佳实践
方法 | 选用原则 |
---|---|
Object.hashCode() | 需要基于对象内容的哈希逻辑时使用 |
System.identityHashCode() | 需要对象身份标识时使用 |
黄金准则:
- 当对象作为
HashMap
等内容敏感容器的键时 → 重写hashCode()
+equals()
- 当需要对象身份标识(如调试、
IdentityHashMap
)时 → 使用identityHashCode()
- 永远不要在重写的
hashCode()
中调用identityHashCode()
,这违反哈希契约!
通过合理选择这两种哈希码,可以避免常见的
HashMap
逻辑错误和身份混淆问题。理解它们的差异,将使你的Java代码更加健壮高效!
作者:努力的小郑
来源:juejin.cn/post/7519797197925367818
来源:juejin.cn/post/7519797197925367818