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()

七、使用技巧与最佳实践

  1. 使用 IDE 自动生成
    IntelliJ IDEA / Eclipse 支持自动生成 equals() 和 hashCode(),选择关键字段即可。

  2. 使用 Lombok(推荐)
    添加注解,自动实现:

    @Data  // 包含 @EqualsAndHashCode
    public class Person {
        private String name;
        private int age;
    }
    
  3. 性能优化

    • 优先使用不可变对象作为 HashMap 的 key。
    • 避免在 hashCode() 中进行复杂计算(如数据库查询)。
  4. 调试技巧
    打印哈希码帮助调试:

    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 代码的关键一步!