注册

别再混淆了!一文彻底搞懂System.identityHashCode与Object.hashCode的区别


在Java开发中,哈希码的使用无处不在,但许多开发者对System.identityHashCode()Object.hashCode()的区别仍然模糊不清。本文将深入剖析二者的核心区别,并通过实际代码演示它们在不同场景下的行为差异。



一、本质定义:两种哈希码的起源



  1. Object.hashCode()

    • 所有Java对象的默认方法(定义在Object类中)
    • 可被子类重写(通常基于对象内容计算)

    // 默认实现(未重写时)
    public native int hashCode();


  2. 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

五、底层机制揭秘



  1. 存储位置:身份哈希码存储在对象头
  2. 生成时机:首次调用hashCode()identityHashCode()时生成
  3. 计算规则:通常基于内存地址,但JVM会优化(非直接地址)
  4. 不变性:一旦生成,在对象生命周期内永不改变

六、总结与最佳实践


方法选用原则
Object.hashCode()需要基于对象内容的哈希逻辑时使用
System.identityHashCode()需要对象身份标识时使用

黄金准则



  • 当对象作为HashMap内容敏感容器的键时 → 重写hashCode()+equals()
  • 当需要对象身份标识(如调试、IdentityHashMap)时 → 使用identityHashCode()
  • 永远不要在重写的hashCode()中调用identityHashCode(),这违反哈希契约!


通过合理选择这两种哈希码,可以避免常见的HashMap逻辑错误和身份混淆问题。理解它们的差异,将使你的Java代码更加健壮高效!



作者:努力的小郑
来源:juejin.cn/post/7519797197925367818

0 个评论

要回复文章请先登录注册