如何在 Java 中计算枚举的哈希码,以及将枚举 hashCodes 组合为 HashMap 的键

Posted

技术标签:

【中文标题】如何在 Java 中计算枚举的哈希码,以及将枚举 hashCodes 组合为 HashMap 的键【英文标题】:How hashcodes for enums are calculated in Java, and combining enum hashCodes for a HashMap's key 【发布时间】:2012-11-14 21:46:34 【问题描述】:

我有一个包含不同枚举(不同类型)的类。此类用作HashMap 的键。目前类 hashCode 是这样实现的:

  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。很难看出这是一个真正的问题。 你说对了吗?我相信您可以通过调用myEnum.ordinal()myEnum.hashCode() 并比较结果来轻松检查自己。 【参考方案1】:

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

另一方面,有更好的方法可以生成冲突概率更低的哈希码。例如,检查 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 抱歉,我没明白你的意思。你能再解释一下吗?【参考方案2】:

如上所述,枚举在Java中是不可变的, 因此,为 Enum 生成的 hashcode 是 Hash 集合的完美键,就像 String 是完美键一样。

枚举声明是一种特殊的类声明。枚举类型对于每个命名的枚举常量都有公共的、自类型的成员。所有枚举类都有高质量的 toString、hashCode 和 equals 方法。所有这些都是可序列化的、可比较的和有效的最终结果。没有一个是可克隆的。除了 toString 之外的所有“对象方法”都是最终的:我们负责比较和序列化,并确保 t 帽子做得对。

【讨论】:

【参考方案3】:

刚刚在 Oracle 1.6 JVM 上对此进行了测试。枚举确实委托给 Object.hashCode()。它在不同的运行之间有所不同。请记住,尽管密钥在不同的虚拟机/虚拟机实例之间是不稳定的。因此,当您序列化 HashMap 并在不同的 VM 中读回它时,您将无法使用在该 VM 中构建的键来查找那里的值。

【讨论】:

HashMap 假设键哈希值可能会从序列化变为反序列化。 您关于地图未正确反序列化的观点实际上并不正确:HashMap 自定义其自己的序列化,其serialized form 包含作为列表写出的条目,因此实际的哈希表将是在反序列化时使用本地适当的哈希码进行重构。【参考方案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); @RohitBanga Objects.hash(Object... o) 只是 Arrays.hasCode(Objects o[]) 的包装器

以上是关于如何在 Java 中计算枚举的哈希码,以及将枚举 hashCodes 组合为 HashMap 的键的主要内容,如果未能解决你的问题,请参考以下文章

如何将哈希表添加到多维数组?无法通过成员枚举赋值

通过反编译字节码来理解 Java 枚举

巧用Python 枚举类设计状态码信息

巧用Python 枚举类设计状态码信息

巧用Python 枚举类设计状态码信息

《Effective Java》第6章 枚举和注解