要保证自定义不可变类作为hashmap的key时hashcode绝对不变,核心前提是:对象一旦创建,其参与hashcode计算的所有字段值必须不可修改,且hashcode实现只依赖这些不可变字段,通常配合懒加载缓存避免重复计算。

要保证自定义不可变类作为 HashMap 的 Key 时 hashCode 绝对不变,核心前提是:对象一旦创建,其参与 hashCode 计算的所有字段值必须**不可修改**,且 hashCode 的实现必须**只依赖这些不可变字段**,并通常配合**懒加载缓存**避免重复计算。
确保所有参与 hash 计算的字段是 final 且真正不可变
不可变性不是靠“不写 setter”就能保证的。必须做到:
- 所有字段声明为
final(包括引用类型) - 若字段是可变引用类型(如
ArrayList、StringBuilder),需在构造时做防御性拷贝,并禁止对外暴露可变视图 - 类本身声明为
final,防止子类通过继承破坏不可变性
例如:
public final class Person {
private final String name;
private final int age;
private final List
public Person(String name, int age, List
this.name = Objects.requireNonNull(name);
this.age = age;
this.hobbies = Collections.unmodifiableList(new ArrayList(hobbies));
}
}
重写 hashCode 时只使用 final 字段,且逻辑稳定
hashCode 方法中不得调用任何可能返回不同结果的方法(如 System.currentTimeMillis()、随机数、外部状态),也不能依赖非 final 字段或可变对象的当前状态(如 list.size() 是安全的,但 list.hashCode() 仅在 list 真正不可变的前提下才安全)。
推荐使用 Objects.hash(...),它基于传入字段的值稳定计算:
@Override
public int hashCode() {
return Objects.hash(name, age, hobbies); // hobbies 是不可变列表,其 hashCode 也稳定
}
可选但强烈推荐:缓存 hashCode 值(懒加载 + final 保证)
虽然不强制,但能彻底杜绝因多次调用导致潜在不一致的风险(尤其当计算较重时),并进一步强化“生命周期内不变”的语义:
- 用
private final int hashCode;字段缓存 - 在构造器末尾一次性计算并赋值
- 避免在
hashCode()方法中做判断或同步(否则失去不可变优势)
private final int hashCode;
public Person(String name, int age, List
// ... 初始化其他 final 字段
this.hashCode = Objects.hash(name, age, hobbies);
}
@Override
public int hashCode() {
return hashCode; // 直接返回,零开销、绝对不变
}
配套:必须同时正确实现 equals
hashCode 不变只是基础;若 equals 实现错误(比如没比较所有关键字段、用了可变字段、或违反对称/传递性),仍会导致 HashMap 行为异常(如 get 找不到已存在的 key)。
要点:
-
equals参数判空、类型检查、字段逐一对等比较(同样只用final字段) - 与
hashCode使用完全相同的字段集合 - 推荐用
Objects.equals(a, b)安全比较引用字段
不复杂但容易忽略。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











