Enum.hashCode()背后的原因是什么?

2022-08-31 22:12:41

类 Enum 中的方法 hashCode() 是 final,定义为 super.hashCode(),这意味着它根据实例的地址返回一个数字,这是程序员 POV 的随机数。

例如,定义它是跨不同JVM的确定性。它甚至可以更好地工作,因为最低有效位将“尽可能多地改变”,例如,对于包含最多16个元素的枚举和大小为16的HashMap,肯定不会发生冲突(当然,使用EnumMap更好,但有时是不可能的,例如没有ConcurrentEnumUmMap)。根据目前的定义,你没有这样的保证,对吧?ordinal() ^ getClass().getName().hashCode()

答案摘要

使用比较更好的哈希码,如上面的哈希码,如下所示:Object.hashCode()

  • 优点
    • 单纯
  • 禁忌症
    • 速度
    • 更多冲突(适用于任何大小的哈希映射)
    • 非确定性,它传播到其他对象,使它们无法用于
      • 确定性模拟
      • ETag 计算
      • 根据迭代顺序等方式搜索错误HashSet

我个人更喜欢更好的哈希码,但恕我直言,除了速度之外,没有理由权重太大。

更新

我对速度感到好奇,并写了一个具有令人惊讶结果基准测试。对于每个类的单个字段的价格,您可以获得确定性哈希代码,其速度几乎快四倍。在每个字段中存储哈希代码会更快,尽管可以忽略不计。

标准哈希代码速度不快的解释是,当对象被GC移动时,它不能是对象的地址。

更新 2

总的来说,表演一些奇怪的事情。当我理解它们时,仍然存在一个悬而未决的问题,为什么(从对象标头读取)比访问普通对象字段慢得多。hashCodeSystem.identityHashCode


答案 1

使用Object的hashCode()并使其最终确定的唯一原因,我能想象,就是让我问这个问题。

首先,您不应依赖此类机制在 JVM 之间共享对象。这根本不是受支持的用例。当您序列化/反序列化时,您应该依靠自己的比较机制,或者仅将结果与您自己的JVM中的对象“比较”。

允许枚举作为哈希代码(基于身份)实现的原因是,在一个 JVM 中,每个枚举对象只有一个实例。这足以确保这种实现有意义且正确。hashCodeObjects

你可以像“嘿,字符串和基元的包装器(Long,Integer,...)”这样争论。所有这些都有明确定义的,确定性的,哈希码的规范!为什么枚举没有它?“,好吧,首先,你可以有几个不同的字符串引用来表示同一个字符串,这意味着使用将是一个错误,所以这些类必然需要它们自己的hashCode实现。对于这些核心类,让它们具有明确定义的确定性哈希码是有意义的。super.hashCode

他们为什么选择这样解决它?

好吧,看看哈希码实现的要求。主要关注点是确保每个对象都应返回不同的哈希代码(除非它等于另一个对象)。基于身份的方法非常有效,可以保证这一点,而您的建议则不然。这个要求显然比任何关于简化序列化等的“便利奖金”都要强。


答案 2

我认为他们最终确定的原因是为了避免开发人员通过重写一个次优(甚至不正确)的哈希代码来开枪打脚。

关于所选的实现:它在JVM之间不稳定,但它非常快,避免冲突,并且不需要枚举中的额外字段。鉴于枚举类的实例数量通常很少,并且 equals 方法的速度也很小,如果算法的 HashMap 查找时间大于当前算法,我不会感到惊讶,因为它具有额外的复杂性。


推荐