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

时间:2011-02-03 10:39:53

标签: java enums hash

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

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

答案摘要

使用Object.hashCode()与上面的更好的hashCode进行比较,如下所示:

  • 赞成
    • 简单
  • CONTRAS
    • 速度
    • 更多碰撞(适用于任何大小的HashMap)
    • 非确定性,传播到其他对象,使其无法使用
      • 确定性模拟
      • ETag计算
      • 追捕错误,例如在HashSet迭代订单

我个人更喜欢更好的hashCode,但恕我直言,没有理由权重,可能除了速度。

更新

我对速度感到好奇并写了benchmark令人惊讶的results。对于每个类的单个字段的价格,您可以使用确定性哈希码,其快速快四倍。将哈希码存储在每个字段中会更快,尽管可以忽略不计。

标准哈希码不快得多的原因是它不能成为对象的地址,因为GC会移动对象。

更新2

有一些奇怪的事情going onhashCode表现一般。当我理解它们时,仍有一个悬而未决的问题,为什么System.identityHashCode(从对象标题读取)比访问普通对象字段慢。

7 个答案:

答案 0 :(得分:24)

  

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

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

让枚举hashCode实现为Objects哈希码(基于身份)的原因是因为,在一个JVM中,只会是每个枚举对象的一个​​实例。这足以确保这种实现有意义并且是正确的。

你可以说像“嘿,字符串和原语的包装(Long,Integer,...)都有明确定义的,确定性的hashCode规范!为什么不是枚举有吗?“,嗯,首先,你可以有几个不同的字符串引用代表相同的字符串,这意味着使用super.hashCode将是一个错误,所以这些类必然需要自己的hashCode实现。对于这些核心类,让它们具有明确定义的确定性hashCodes是有意义的。

  

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

好吧,看看the requirements of the hashCode implementation。主要关注的是确保每个对象都应返回 distinct 哈希码(除非它等于另一个对象)。基于身份的方法是超级有效的,并保证这一点,而你的建议没有。这个要求显然比任何关于放宽序列化等的“便利奖励”更强。

答案 1 :(得分:11)

我认为他们最终决定的原因是避免开发人员通过重写次优(甚至不正确)的hashCode来自我攻击。

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

答案 2 :(得分:2)

我问了同样的问题,因为没有看到这个问题。 Why in Enum hashCode() refers to the Object hashCode() implementaion, instead of ordinal() function?

我在定义自己的哈希函数时遇到了一个问题,对于依赖于枚举hashCode作为复合体之一的Object。当检查函数返回的一组对象中的值时,我按顺序检查它们,我希望它是相同的,因为我自己定义了hashCode,所以我希望元素落在同一个节点上在树上,但由于enum返回的hashCode从开始变为开始,这个假设是错误的,测试可能会偶尔失败。

所以,当我发现问题时,我开始使用序数代替。 我不确定每个人都为他们的Object编写hashCode实现这一点。

所以基本上,你不能定义你自己的确定性hashCode,而依赖于enum hashCode,而你需要使用序数

P.S。这对评论来说太大了:)

答案 3 :(得分:1)

JVM 强制执行对于枚举常量,内存中只存在一个对象。您无法在单个VM中使用相同枚举常量的两个不同实例对象,而不是使用反射,而不是通过序列化/反序列化在网络中。

话虽如此,由于它是唯一表示此常量的对象,因此其hascode是其地址并不重要,因为其他对象不能同时占用相同的地址空间。它保证是独一无二的。 “确定性”(在某种意义上说,在同一个虚拟机中,在内存中,所有对象都具有相同的引用,无论它是什么)。

答案 4 :(得分:0)

只要我们不能将枚举对象 1 发送到不同的JVM,我认为没有理由对枚举(以及通常的对象)提出这样的要求


1 我认为很清楚 - 对象是一个类的实例。 序列化对象是一个字节序列,通常存储在字节数组中。我在谈论对象

答案 5 :(得分:0)

没有要求哈希码在JVM之间具有确定性,如果它们没有获得优势。如果你依赖这个事实,你就错了。

由于每个枚举值只存在一个实例,因此保证Object.hashcode()永远不会发生冲突,良好的代码重用并且非常快。

如果通过身份定义了相等性,那么Object.hashcode()将始终提供最佳性能。

其他哈希码的确定性只是其实现的副作用。由于它们的相等性通常由字段值定义,因此混合非确定性值将浪费时间。

答案 6 :(得分:0)

我可以想象它实现的另一个原因是因为要求hashCode()和equals()保持一致,并且对于Enums的设计目标,它们应该易于使用和编译时常量(使用它们是“case”常量)。这也使得将枚举实例与“==”进行比较是合法的,并且您根本不希望“等于”与枚举的“==”行为不同。这再次将hashCode与默认的Object.hashCode()基于引用的行为联系起来。 如前所述,我也不希望equals()和hashCode()将来自不同JVM的两个枚举常量视为相等。在谈论序列化时:例如,类型为枚举的字段,Java中的默认二进制序列化器具有一种特殊行为,只能序列化常量的名称,而在反序列化时,将重新创建对反序列化JVM中相应枚举值的引用。 JAXB和其他基于XML的序列化机制以类似的方式工作。所以:别担心