Skip to content

hashCode() 方法

hashCode 是什么?

hashCode() 返回一个整数,用于哈希表(HashMap、HashSet)的快速查找。

简单说:哈希表先用 hashCode 定位「桶」,再用 equals 确认「是否真的相等」。

为什么需要 hashCode?

没有 hashCode,哈希表只能线性查找:

遍历所有元素 → O(n)

有 hashCode,先算哈希值,定位到特定「桶」:

计算 hashCode → 定位桶 → 桶内查找 → O(1)

hashCode 约定

如果 equals 返回 true,两个对象的 hashCode 必须相同。

关系必须满足
equals true→ hashCode 必须相同
hashCode 相同→ equals 可能 true 也可能 false
hashCode 不同→ equals 必定 false

重写 hashCode

基本重写模式

java
class Person {
    private String name;
    private int age;

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

手动实现

java
class Person {
    private String name;
    private int age;

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }
}

为什么用 31?

  • 奇数,避免乘积溢出时丢失信息
  • 31 = 32 - 1,JVM 会优化为 result * 31 = result << 5 - result
  • 哈希分布均匀,不容易冲突

常见错误

错误一:hashCode 返回固定值

java
class Broken {
    private String name;

    @Override
    public int hashCode() {
        return 42; // ❌ 所有对象都在同一个桶,退化到 O(n)
    }
}

错误二:使用可变字段

java
class Broken {
    private String name;

    @Override
    public int hashCode() {
        return name.hashCode(); // ❌ name 改变后,hashCode 改变!
    }
}

Set<Broken> set = new HashSet<>();
Broken b = new Broken("张三");
set.add(b);
b.setName("李四");
set.contains(b); // ❌ false!对象在 set 里「丢失」了

原则:用于 hashCode 的字段必须是不可变的

错误三:equals 和 hashCode 不一致

这是最严重的错误:

java
class Broken {
    private int value;

    @Override
    public boolean equals(Object o) {
        return o instanceof Broken b && value == b.value;
    }

    @Override
    public int hashCode() {
        return value * 2; // ❌ equals 相等但 hashCode 不同!
    }
}

Broken a = new Broken(5);
Broken b = new Broken(5);

Map<Broken, String> map = new HashMap<>();
map.put(a, "hello");
map.get(b); // ❌ null!因为 hashCode 不同,定位到了错误的桶

集合中的 hashCode

HashMap 和 HashSet 依赖 hashCode:

java
class Person {
    private String name;

    @Override
    public boolean equals(Object o) {
        return o instanceof Person p && name.equals(p.name);
    }

    @Override
    public int hashCode() {
        return name.hashCode();
    }
}

Set<Person> set = new HashSet<>();
Person p1 = new Person("张三");
Person p2 = new Person("张三");

set.add(p1);
set.size(); // 1

set.contains(p2); // true,equals=true 且 hashCode 相同
set.add(p2);
set.size(); // 仍然是 1,因为是相等的元素

String 的 hashCode

String 重写了 hashCode,相同内容的字符串 hashCode 一定相同:

java
String a = new String("hello");
String b = new String("hello");

a.hashCode() == b.hashCode(); // true

// 这也是为什么 String 常作为 HashMap 的 key
Map<String, Integer> map = new HashMap<>();
map.put("key", 100);
map.get("key"); // 100

hashCode 常用技巧

1. 用 Objects.hash()

java
@Override
public int hashCode() {
    return Objects.hash(field1, field2, field3);
}

2. 缓存 hashCode

java
class Heavy {
    private final String data; // 假设计算很耗时
    private int hash; // 缓存

    @Override
    public int hashCode() {
        if (hash == 0) {
            hash = data.hashCode();
        }
        return hash;
    }
}

3. 排除不参与 equals 的字段

java
class User {
    private String id;       // 参与 equals
    private String password;  // 不参与 equals,但也不参与 hashCode
    private int loginCount;  // 不参与 equals,也不参与 hashCode

    @Override
    public boolean equals(Object o) {
        return o instanceof User u && id.equals(u.id);
    }

    @Override
    public int hashCode() {
        return id.hashCode(); // 只用参与 equals 的字段
    }
}

总结

重写 hashCode 的原则:

原则说明
一致性equals 为 true 时,hashCode 必须相同
参与字段必须用参与 equals 的字段
不可变性用于 hashCode 的字段最好不要变
分布均匀哈希冲突越少越好

记住:equals 和 hashCode 必须同时重写,保持一致。

基于 VitePress 构建