【问题标题】:Use Objects.hash() or own hashCode() implementation?使用 Objects.hash() 还是自己的 hashCode() 实现?
【发布时间】:2018-01-31 14:45:44
【问题描述】:

我最近发现了Objects.hash() 方法。

我的第一个想法是,这大大整理了您的 hashCode() 实现。请参阅以下示例:

@Override
//traditional
public int hashCode() {
    int hash = 5;
    hash = 67 * hash + (int)(this.id ^ (this.id >>> 32));
    hash = 67 * hash + (int)(this.timestamp ^ (this.timestamp >>> 32));
    hash = 67 * hash + Objects.hashCode(this.severity);
    hash = 67 * hash + Objects.hashCode(this.thread);
    hash = 67 * hash + Objects.hashCode(this.classPath);
    hash = 67 * hash + Objects.hashCode(this.message);
    return hash;
}

@Override
//lazy
public int hashCode() {
    return Objects.hash(id, timestamp, severity, thread, classPath, message);
}

虽然我不得不说这似乎好得令人难以置信。我也从未见过这种用法。

与实现自己的哈希码相比,使用Objects.hash() 有什么缺点吗?我什么时候会选择这些方法?

更新

尽管此主题已标记为已解决,但请随时发布提供新信息和疑虑的答案。

【问题讨论】:

  • 但是公共构建器使用反射。它很方便,但绝对是性能杀手。
  • @NPE 我真的很想把它留给当地人。我不是整个外部 apache 常见的东西的忠实粉丝
  • 使用 Lombok 和 @EqualsAndHashCode
  • @MartinSchröder 谢谢,但我想保持我的依赖关系干净。

标签: java hash


【解决方案1】:

注意Objects.hash的参数是Object...。这有两个主要后果:

  • 哈希码计算中使用的原始值必须装箱,例如this.idlong 转换为Long
  • 必须创建一个Object[] 才能调用该方法。

如果频繁调用hashCode,创建这些“不必要”对象的成本可能会增加。

【讨论】:

  • 与所有性能问题一样,您应该先向自己证明您在关心之前是在关心的。
  • @CandiedOrange 是的;另一方面,不要编写您可能必须关心的代码。我的意思是,如果您在证明并非如此之前不关心性能,则可以使用return 0; 实现hashCode。由于您只需要编写一次,而且您的 IDE 很可能会为您生成代码,因此您不妨选择详细但高性能的版本。但我不会急于将现有代码中的任何一种形式更改为另一种形式。
【解决方案2】:

以下是 Objects.hash 的实现 - 它在内部调用 Arrays.hashCode。

public static int hash(Object... values) {
    return Arrays.hashCode(values);
}

这是 Arrays.hashCode 方法的实现

public static int hashCode(Object a[]) {
    if (a == null)
        return 0;

    int result = 1;

    for (Object element : a)
        result = 31 * result + (element == null ? 0 : element.hashCode());

    return result;
}

所以我同意@Andy 如果频繁调用 hashCode,创建这些“不必要的”对象的成本可能会增加。如果你自己实现它会更快。

【讨论】:

    【解决方案3】:

    我想尝试为两者提出强有力的论据。

    开始免责声明

    对于这个答案,Objects.hash()Objects.hashCode() 以及由任何库提供的任何执行此角色的函数都是可以互换的。首先,我想争辩说,使用Objects.hash() 或者根本不使用静态对象函数。任何支持或反对此方法的论点都需要对编译后的代码做出不保证为真的假设。 (例如,编译器优化器可能会将函数调用转换为内联调用,从而绕过额外的调用堆栈和对象分配。就像无用的循环不会进入编译版本一样(除非你关闭优化器)。您也不能保证未来的 Java 版本不会像 C# 那样在此方法的版本中包含 JVM 版本。(出于安全原因,我相信))因此,您可以就使用此函数提出唯一安全的论点,与尝试实现自己的幼稚版本相比,将适当哈希的详细信息留给此函数通常更安全。

    对于 Objects.hash

    • 保证是一个好的哈希。
    • 实施需要 5 秒。
    • 你的会有一个错误(不知何故,尤其是如果你复制粘贴了实现)

    反对Objects.hash

    • Java docs 没有对哈希交叉兼容性做出任何承诺(JVM v6 和 JVM v8 会给出相同的值吗?总是?跨操作系统?)
    • 关于hashCodes 的事情,如果“均匀分布”,它们的效果最好。因此,如果一个 int 值仅在 1 到 100 范围内有效,您可能希望“重新分配”其哈希码,使其不都属于同一个存储桶。
    • 如果您有任何要求让您质疑 Objects.hash 的工作原理、可靠性/性能方面的问题,请仔细考虑哈希码是否真的是您想要的,并实施一种自定义哈希编码方法来解决 您的问题 需要。

    【讨论】:

    • 你在这里指的是Objects.hashCode:你的意思是这样做,还是你的意思是Objects.hash(这是OP所问的)?
    • @AndyTurner 就我对这个答案的真正关注而言,任何库提供的散列函数都是可互换的,并且不会真正影响这一点。如果您真的关心 hash 和 hashCode 工作方式之间的区别,您可能不应该使用它们中的任何一个。
    • 我认为您错过了问题的重点,即询问 Objects.hash 从多个字段计算哈希码的优点/缺点,而不是做它“手动”。 Objects.hashCode 只是在其参数上调用 Object.hashCode(),处理 null 的情况。如果你使用它,你仍然必须以某种方式组合哈希码。两者不可互换。
    • @AndyTurner 在一个是“散列这个”和另一个是“散列这些”的意义上,它们是不可互换的。在您要求 API 为您处理散列逻辑的意义上,我认为它们是可互换的。在我的回答中,我试图说“如果你不使用Objects.hash,你可能也不应该使用Objects.hashCode。它真的只能归结为“为什么这个可用功能不足,我该怎么办需要做一个足够的版本吗?”
    【解决方案4】:

    Joshua Bloch 在他的《Effective Java》一书中,第 3 版,p。如果性能至关重要,则 53 不鼓励使用 Objects.hash(...)

    基元正在自动装箱,创建Object 数组会受到惩罚。

    【讨论】:

      【解决方案5】:

      我个人首先支持短代码,因为它可以更快地阅读、更改和验证是否正确,所有这些都有助于在修改类时避免错误。

      那么对于性能关键的类,或者字段的散列成本很高,另一个可用的“工具”是缓存结果(就像String 所做的那样):

      // volatile not required for 32-bit sized primitives
      private int hash;
      
      @Override
      public final int hashCode() {
          // "Racy Single-check idiom" (Item 71, Effective Java 2nd ed.)
          int h = hash;
          if (h == 0) {
              h = Objects.hash(id, timestamp, severity, thread, classPath, message);
              hash = h;
          }
          return h;
      }
      

      在这种无锁线程安全模式(它假定一个不可变的类,自然)中,hash 可能会被不同的线程多次初始化,但这并不重要,因为公共方法的结果总是相同的. (内存可见性)正确性的关键是确保 hash 在方法中不会被多次写入和读取。

      一旦代码通过 C2 编译器进行 JIT 内联和优化,Objects.hash 的数组构造惩罚可能会低于您的想象。

      (更新)JDK 的好人有一些烘焙方法,旨在消除其他答案中提到的Objects::hash 的开销,但显然差异不足以影响特定方法:JEP 348: Java Compiler Intrinsics for JDK APIs

      【讨论】:

      • 很好的实现可以避免额外的计算,但是如果 Object 是可变的并且用于哈希计算的参数之一发生了变化怎么办?
      • 当然,然后不要缓存它,但是:在使用任何可变对象作为基于哈希的映射/集合中的键之前也要三思而后行。这样做可能会破坏整个集合 - 对象可能会在其中“丢失”,等等。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-20
      • 2015-10-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多