真实面经题目 · 原创解析

自定义对象的 hashCode 应该如何计算?

自定义对象的 hashCode 应该基于 equals 使用的关键字段来计算,核心目标是满足“相等对象必须有相同哈希值”,同时尽量让不同对象分布均匀、计算稳定,并避免使用会频繁变化的字段。

出现于:字节跳动 · 后端开发

60 秒回答模板

回答时可以先讲原则:hashCode 不是随便返回一个整数,而是对象参与哈希容器定位的依据。自定义对象通常应该选择 equals 中参与比较的字段,按固定顺序组合计算;如果两个对象 equals 为 true,它们的 hashCode 必须相同。常见做法是使用一个非零初始值,再用质数乘子组合各字段,也可以使用 Objects.hash,但要知道它可能有装箱和可变参数数组的开销。最后补充注意点:不要用随机数、内存地址、可变业务字段作为核心哈希依据;数组字段要用 Arrays.hashCode 或 Arrays.deepHashCode;hashCode 不要求唯一,冲突允许存在,但要尽量降低冲突率。

考点 计算入口
难度 真实面经高频题
回答目标 讲清机制、边界和追问

深入解析

01

先看契约,而不是公式

自定义对象计算 hashCode 的第一原则是遵守 equals 与 hashCode 的通用约定:只要两个对象通过 equals 判断相等,它们的 hashCode 就必须相等。这个要求比任何具体公式都重要。hashCode 本质上服务于 HashMap、HashSet 等哈希结构的快速定位,它先决定对象应该落在哪个桶里,再通过 equals 做精确比较。如果公式再复杂,却没有和 equals 使用同一组语义字段保持一致,那么容器行为就会出错。

02

字段选择要和 equals 对齐

计算 hashCode 时,应该优先选择 equals 中参与相等性判断的字段,而不是把对象里所有字段都塞进去。例如一个用户对象如果 equals 只按 userId 判断,那么 hashCode 也应该主要按 userId 计算;如果 equals 同时比较 name 和 birthday,那么 hashCode 也应包含这些字段。这样可以保证相等对象的哈希值一致,也避免某些不参与身份语义的字段变化导致对象在哈希容器中失联。

03

组合方式追求稳定和分散

常见的手写方式是选一个非零初始值,然后按固定顺序把字段哈希值乘以质数再累加,例如 31 作为乘子很常见。这样做的目的不是保证绝对唯一,而是让不同字段组合产生相对均匀的整数分布,降低大量对象挤在同一个桶里的概率。对象字段为 null 时要给出稳定值,基本类型要按对应规则转换,long、double、boolean 等字段不能简单粗暴地忽略。

04

可变字段是高风险点

如果对象会被放入 HashMap 或 HashSet,参与 hashCode 的字段最好在对象作为 key 期间保持不变。假设某个字段变化导致 hashCode 改变,对象原来所在的桶位置和新计算出的桶位置不一致,后续 get、contains、remove 可能找不到它。这个问题通常很隐蔽,因为对象本身还在容器里,只是查找路径被破坏了。因此用于 key 的对象最好是不可变对象,至少关键字段不要在入容器后修改。

05

工具方法好用但要理解边界

实际开发中可以使用 Objects.hash 快速组合字段,代码简洁且不易漏掉 null 处理;但它底层涉及可变参数数组和装箱,在极高频场景可能不是最优。数组字段不能直接用数组对象自身的 hashCode,因为那通常是引用身份语义,应使用 Arrays.hashCode 或 Arrays.deepHashCode。还要强调,hashCode 允许冲突,不同对象可以有同一个哈希值,真正不能违反的是相等对象哈希值必须一致。

易错点

  • 把 hashCode 理解成对象唯一 ID,试图让每个对象都返回不同值。
  • equals 比较多个字段,但 hashCode 只随便返回其中一个无关字段。
  • 把会频繁变化的状态字段放进 hashCode,导致对象放入 HashMap 后无法正常查找。
  • 数组字段直接参与 Objects.hash 或调用默认 hashCode,却没有使用 Arrays.hashCode 或 Arrays.deepHashCode。

面试官追问

hashCode 是否必须保证不同对象返回不同值?

不必须。hashCode 的返回值是 int,取值空间有限,而对象数量理论上无限,所以冲突不可避免。契约只要求 equals 为 true 的对象 hashCode 必须相同,不要求 equals 为 false 的对象 hashCode 必须不同。

为什么很多实现里会用 31 作为乘子?

31 是奇质数,组合字段时有较好的分散效果,同时 31 * i 可以被编译器优化为位移和减法。它不是唯一正确选择,但已经成为 Java 中常见且足够可靠的经验做法。

Objects.hash 可以完全替代手写 hashCode 吗?

大多数普通业务对象可以使用 Objects.hash,代码清晰且不容易漏 null。但在性能敏感场景、对象创建非常频繁的场景,或包含数组字段时,需要关注装箱、可变参数数组和数组哈希语义。

可变对象能不能作为 HashMap 的 key?

可以,但风险较高。关键在于参与 equals 和 hashCode 的字段在作为 key 期间不能变化。更推荐使用不可变对象、字符串、枚举、ID 类型对象等稳定值作为 key。