Java中枚举类型的哈希码是如何计算的?如何将枚举类型的哈希码组合成HashMap的键?

27

我有一个包含不同枚举类型的类。这个类被用作 HashMap 的键。该类的哈希码目前实现如下:

  public static class Key implements Comparable<Key> {
    final int a;
    final Enum1 enum1;
    final Enum2 enum2;

    @Override
    public int hashCode() {
      return a ^ enum1.hashCode() ^ enum2.hashCode();
    }

    // ... definition of equals and toString ...
  }

如果枚举的 hashCode 方法只是返回枚举值在枚举定义中的索引,那么这并不是最优的(会导致太多冲突)。Enum.hashCode() 方法的定义如下:

/**
 * Returns a hash code for this enum constant.
 *
 * @return a hash code for this enum constant.
 */
public final int hashCode() {
    return super.hashCode();
}

假设这个委托给了 Object.hashCode(),因为对于每个枚举常量只存在一个实例,在理论上 Object.hashCode() 将会是从对象的内部地址派生出来的整数。我说得对吗?

PS: 当同一个枚举在键中被多次使用时,当然你需要使用更复杂的东西。


它不能委托给除Object.hashCode()之外的任何东西,因为Enum扩展了Object。很难看出这是一个真正的问题。 - user207421
你确定吗?你可以轻松地通过调用 myEnum.ordinal()myEnum.hashCode() 并比较结果来检查。 - Dunes
4个回答

20

是的,你说得对,枚举元素的哈希码将来自于静态实例,绑定到内存位置,并且是唯一的。

另一方面,有更好的方法生成哈希码以减少碰撞概率。例如,可以查看Eclipse可以为您自动生成的默认值(右键单击,Source> Generate hashCode and equals)。

public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((enum1 == null) ? 0 : enum1.hashCode());
    result = prime * result + ((enum2 == null) ? 0 : enum2.hashCode());
    return result;
}

通过将质数加入计算(确切的数学方法我不太清楚),你应该会更加抗干扰。

请注意,你也可以让eclipse为你生成一个equals方法!(甚至是toString)。并不是说你必须盲目相信它们,但通常它们是一个非常好的起点。


@TomHawtin-tackline 不好意思,我不太明白您的意思。您能再解释详细一些吗? - Miquel

6
正如上文所述,Java中的枚举类型是不可变的。 因此,为枚举生成的哈希码是哈希集合的完美键,就像字符串是完美的键一样。
枚举声明是一种特殊类型的类声明。枚举类型具有每个命名枚举常量的公共、自我类型的成员。所有枚举类都具有高质量的toString、hashCode和equals方法。它们都是可序列化的、可比较的和有效地final的,但没有可克隆性。除了toString之外的所有“Object方法”都是final的:我们负责比较和序列化,并确保这样做正确。

4
在Java 8中,您可以使用Objects.hash()来实现这个目的。
例如,您可以重写您的hashCode方法为:
//
import static java.util.Objects.hash;

// 
@Override
public int hashCode() {
  return hash(a, enum1, enum2);
}

你可以使用 Arrays.hashCode(a, enum1, enum2); - Rohit Banga
@RohitBanga Objects.hash(Object ... o)只是对Arrays.hasCode(Objects o [])的一个包装器。 - Himanshu Shekhar

4

在 Oracle 1.6 JVM 上进行了测试。枚举确实委托给了 Object.hashCode()。而且它在不同的运行之间变化。但要记住,在不同的 VM / VM 实例之间,密钥是不稳定的。因此,当您序列化 HashMap 并在不同的 VM 中读取它时,您将无法使用在该 VM 中构造的键查找那里的值。


1
“HashMap” 假设键哈希值可能在序列化到反序列化时发生更改。 - Tom Hawtin - tackline
1
关于地图无法正确反序列化的观点并不准确:HashMap 自定义了自己的序列化,其序列化形式包括作为列表写出的条目,因此在反序列化时将重建实际的哈希表,并具有本地适当的哈希码。 - Tom Anderson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接