|
这篇文章最初发表在Galo 的博客上。我们在这里联合它是因为它是好奇心和卓越技术的一个很好的例子。它还与我们对如何hashCode正确实施的详尽解释相得益彰。 其中,触及表面会hashCode()导致通过 JVM 源进行洞穴学之旅,到达对象布局、偏向锁定以及依赖默认hashCode(). 非常感谢Gil Tene和Duarte Nunes审阅本文的草稿以及他们非常有价值的见解、建议和编辑。任何剩余的错误都是我自己的。 一个微不足道的谜团 上周在工作中,我向一个类提交了一个小小的更改,toString()因此日志的实现将是有意义的。令我惊讶的是,这一变化导致班级覆盖率下降了约 5%。我知道所有新代码都被现有的单元测试覆盖,那么,可能出了什么问题呢?比较覆盖率报告,一位更敏锐的同事注意到,实施hashCode()是在更改之前覆盖的,而不是在更改之后。当然,这是有道理的我们的自定义hashCode()不再被调用。我们错过了一次测试。
每个人都知道默认值,toString()但是...... 的默认实现是什么hashCode()? 默认实现返回的值hashCode()称为身份哈希码,因此从现在开始我将使用这个术语来将其与 重写实现提供的哈希区分开来hashCode()。仅供参考:即使一个类重写了,您始终可以通过 埃及 WhatsApp 号码列表 调用 来hashCode()获取对象的身份哈希码。oSystem.identityHashCode(o) 常识是身份哈希码使用内存地址的整数表示。这也是J2SE JavaDocs for Object.hashCode()的含义: ...通常通过将对象的内部地址转换为整数来实现,但 Java™ 编程语言不需要这种实现技术。 不过,这似乎有问题,因为方法契约要求: 每当在 Java 应用程序执行期间对同一对象多次调用 hashCode 方法时,它必须始终返回相同的整数。 鉴于 JVM 将重新定位对象(例如,在由于提升或压缩而导致的垃圾收集周期期间),在我们计算对象的身份哈希之后,我们必须能够以在对象重新定位后仍然存在的方式保留它。
种可能是在第一次调用时获取对象的当前内存位置hashCode(),并将其与对象一起保存在某个位置,例如对象的标头。这样,如果对象被移动到不同的内存位置,它将携带原始哈希值。此方法的一个警告是,它不会阻止两个对象具有相同的身份哈希,但这是规范允许的。 最好的确认是查看来源。不幸的是,默认 是本机函数 public native int hashCode(); 戴上头盔。 真实的hashCode()请站起来吗 请注意,身份hashCode()实现依赖于 JVM。由于我只会查看 OpenJDK 源代码,因此每当我谈论 JVM 时,您都应该假设这个特定的实现。所有链接均引用Hotspot 树的变更集 5820:87ee5ee27509,我假设其中大部分也适用于 Oracle 的 JVM,但其他情况可能(事实上)有所不同(稍后会详细介绍。后者有这似乎证实了我们的假设。monitor现在让我们忽略它,并满意它为我们提供了对象标头。
|
|