Object.hashCode() 是 Java 中所有对象继承自 java.lang.Object 类的一个核心方法,用于返回对象的哈希码(Hash Code)。它是 Java 集合框架(尤其是基于哈希的集合如 HashMapHashSetHashtable)实现高效查找、插入和删除操作的基础。


一、核心概念

1. 什么是哈希码?

  • 哈希码是一个 int 类型的整数,由对象的内容或内存地址通过某种算法计算得出。
  • 它用于快速定位对象在哈希表中的“桶”(bucket)位置。
  • 哈希码不是内存地址本身,但默认实现可能基于内存地址。

2. hashCode() 方法签名

public native int hashCode();
  • 是一个 native 方法(由 JVM 底层实现,通常用 C/C++ 编写)。
  • 返回值类型为 int,范围:-2^312^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) 返回 falsex.hashCode()y.hashCode() 不一定不同(可能发生哈希冲突)。

关键推论
如果两个对象 equalstrue,它们的 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() 返回常量 所有对象哈希码相同,退化为链表查找,性能极差 应基于字段内容计算
浮点数处理不当 floatdouble 需特殊处理 使用 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()

📌 黄金法则
“如果两个对象逻辑上相等(equalstrue),它们的 hashCode 必须相同。”

✅ 掌握 hashCode(),是写出高质量 Java 代码的关键一步!