Enum.hashCode()背后的原因是什么?
Enum类中的hashCode()方法是final的,定义为super.hashCode(),这意味着它返回一个基于实例地址的数字,这个数字是程序员POV的一个随机数。
将其定义为例如ordinal() ^ getClass().getName().hashCode()
在不同的JVM中是确定性的。 它甚至会更好一些,因为最不重要的位将“尽可能地变化”,例如,对于包含多达16个元素的枚举和16的HashMap,肯定没有冲突(当然,使用EnumMap更好,但有时不可能,例如没有ConcurrentEnumMap)。 目前的定义你没有这样的保证,是吗?
答案摘要
使用Object.hashCode()
比较像上面那样的更好的hashCode,如下所示:
- PROS
- 简单
- CONTRAS
- 速度
- 更多的碰撞(对于任何大小的HashMap)
- 非决定论,传播到其他对象使他们无法使用
- 确定性模拟
- ETag计算
- 根据例如
HashSet
迭代顺序search错误
我个人更喜欢更好的hashCode,但恕我直言,没有理由重量多less,除了速度。
UPDATE
我对速度感到好奇,写了一个令人惊讶的结果 。 对于每个类的单个字段的价格,您可以确定性的哈希码快四倍 。 在每个字段中存储哈希码将更快,尽pipe可以忽略不计。
标准哈希代码速度不是很快的解释是,当对象被GC移动时,它不能是对象的地址。
更新2
一般来说, hashCode
性能有一些奇怪的事情发生 。 当我理解他们时,仍然有一个悬而未决的问题,为什么System.identityHashCode
(从对象头中读取)比访问一个普通的对象字段慢。
我认为他们最终做出这个决定的原因是为了避免开发者通过重写一个次优的(甚至是不正确的)hashCode来让自己在脚下自我牺牲。
关于select的实现:它在JVM之间并不稳定,但速度非常快,避免冲突,并且在枚举中不需要额外的字段。 由于枚举类实例的数量通常很less,并且等于方法的速度,所以如果HashMap查找时间比用当前algorithm更大,由于其额外的复杂性,我不会感到惊讶。
使用Object的hashCode()并使其成为最终的我唯一可以想象的原因是让我问这个问题。
首先,您不应该依赖这种机制在JVM之间共享对象。 这根本不是一个支持的用例。 当你序列化/反序列化时,你应该依靠你自己的比较机制,或者只将结果与你自己的JVM中的对象进行“比较”。
让枚举hashCode
被实现为Objects
哈希码(基于身份)的原因是因为在一个JVM中,每个枚举对象只有一个实例 。 这足以确保这种实施是有道理的,是正确的。
你可能会争辩说, “嘿,string和基元的包装(长,整数,…)都具有明确的,确定性的, hashCode
规范!为什么没有枚举呢? 首先,你可以有几个不同的string引用代表相同的string,这意味着使用super.hashCode
将是一个错误,所以这些类一定需要自己的hashCode实现。 对于这些核心类,让它们具有定义明确的hashCode是有意义的。
他们为什么select这样解决呢?
那么,看看hashCode
实现的要求 。 主要关心的是确保每个对象应该返回一个明确的哈希码(除非它等于另一个对象)。 基于身份的方法是超高效的,保证了这一点,而你的build议却没有。 这个要求显然比任何有关简化系列化的“便利奖金”都要强。
我问过同样的问题,因为没有看到这个问题。 为什么在Enum中hashCode()是指Object的hashCode()实现,而不是ordinal()函数?
我在定义我自己的散列函数时遇到了一个问题,对于一个依赖枚举hashCode的对象作为组合之一。 当检查函数返回的对象集合中的值时,我按顺序检查了它们,我希望它们是相同的,因为我定义了自己的hashCode,所以我期望元素落在相同的节点上在树上,但由于枚举返回的hashCode从开始到开始改变,这个假设是错误的,testing可能会偶尔失败。
所以,当我找出问题时,我开始使用序号。 我不确定每个人为他们的对象写hashCode意识到这一点。
所以基本上,你不能定义你自己的确定性的hashCode,而依靠枚举hashCode,你需要使用序号
PS这太大了评论:)
JVM 强制为一个枚举常量,只有一个对象将存在于内存中。 没有办法,你可能会得到两个不同的实例对象具有相同的枚举常量在一个单一的虚拟机,而不是reflection,而不是通过序列化/反序列化networking。
这就是说,由于它是表示这个常量的唯一对象,因此没有其他对象可以同时占用相同的地址空间,因此它的hascode就是它的地址。 它保证是唯一的“确定性”(即在同一个虚拟机中,在内存中,所有的对象将有相同的引用,不pipe它是什么)。
散列码在JVM之间没有要求是确定性的,如果它们没有得到好处。 如果你依靠这个事实,你就错了。
由于每个枚举值只有一个实例存在, Object.hashcode()
保证永远不会碰撞,是很好的代码重用,速度非常快。
如果相等是由身份定义的,那么Object.hashcode()
将始终给出最佳性能。
其他哈希码的确定性只是其实现的副作用。 由于他们的平等通常是由领域价值来定义的,所以混合在非确定性价值中将浪费时间。
只要我们不能把一个枚举对象1发送给不同的JVM,我就没有理由把这样的需求放在枚举(和一般的对象)上,
1我认为这很清楚 – 一个对象是一个类的一个实例。 序列化对象是一个字节序列,通常存储在一个字节数组中。 我正在谈论一个对象 。
还有一个原因就是这样实现的,我可以想象的是因为要求hashCode()和equals()是一致的,而且为了Enums的devise目标,它们应该很简单,并且编译时常量使用它们是“case”常量)。 这也使得将枚举实例与“==”进行比较是合法的,并且您不希望“equals”与枚举的“==”行为不同。 这再次将hashCode绑定到默认的基于Object.hashCode()引用的行为。 如前所述,我也不希望equals()和hashCode()将来自不同JVM的两个枚举常量视为相等。 在讨论序列化时:对于枚举types的实例字段,Java中的默认二进制序列化程序有一个特殊的行为,它仅序列化常量的名称,而在反序列化中,重新创build对反序列化JVM中相应枚举值的引用。 JAXB和其他基于XML的序列化机制以类似的方式工作。 所以:别担心