【问题标题】:Why does Netbeans generate hashCode() the way that it does?为什么 Netbeans 会以这种方式生成 hashCode()?
【发布时间】:2014-03-21 19:30:50
【问题描述】:

我已经使用 Netbeans 进行 Java 开发已有一段时间了,有些事情我只是依赖于工作而没有真正质疑如何工作。其中包括自动生成的 hashCode() 和 equals() 方法。

equals 方法很容易理解,但我发现 hashCode 方法有点神秘。我不明白为什么它选择乘数并应用它所做的操作。

import java.util.Arrays;
import java.util.Objects;

public class Foo {

    int id;
    String bar;
    byte[] things;

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 89 * hash + this.id;
        hash = 89 * hash + Objects.hashCode(this.bar);
        hash = 89 * hash + Arrays.hashCode(this.things);
        return hash;
    }    
}

在文档、此站点和 Google 中搜索“netbeans 生成哈希码”之类的内容似乎没有任何相关信息。这里有没有人熟悉这个生成策略是什么以及 Netbeans 使用它的原因?

编辑:
感谢您到目前为止的答案!特别是由于this answer on the linked SO question,我现在更全面地理解了在设计 hashCode 方法时使用素数背后的逻辑。然而,到目前为止,没有人真正解决我的问题的另一个方面是 Netbeans 如何以及为什么选择它为其生成的方法所做的素数。 hash 字段和其他乘数(在我的示例中为 89)似乎因类的各种因素而异。

例如,如果我在类中添加第二个String,则 hashCode() 变为

public int hashCode() {
    int hash = 7;
    hash = 13 * hash + this.id;
    hash = 13 * hash + Objects.hashCode(this.bar);
    hash = 13 * hash + Objects.hashCode(this.baz);
    hash = 13 * hash + Arrays.hashCode(this.things);
    return hash;
}

那么,为什么 Netbeans 选择这些特定的素数,而不是其他任何素数?

【问题讨论】:

  • 我认为这只是一种方法,IntelliJ IDEA使用31
  • 乘以 31 很容易优化为移位和减号,不确定 89。不过两者都应该是素数。
  • 一个不错的选择是:return Objects.hash(id, bar, baz, things); 或多或少做同样的事情。
  • 今天早上看到你的编辑,因为我正在考虑如何用我的答案绕过这个问题。 :-) 这是一个很好的问题,所以我为你投了赞成票。
  • 关于 netbeans 为什么选择那个素数,zapl 在他的评论中给出了一个很好的暗示。如果您真的想确定,请查看他们的代码。一般提示:如果您喜欢目前得到的答案,只需点赞即可,而不是在您的问题中表示感谢。

标签: java netbeans hashcode


【解决方案1】:

这是一项旨在更好地分配哈希值的优化。 Eclipse 也是这样做的。看看Why use a prime number in hashCode?Why does Java's hashCode() in String use 31 as a multiplier?

这绝不是必需的。即使return 0; 也足以满足equals/hashcode 合约。唯一的原因是基于散列的数据结构具有良好的分布式散列值性能更好。

有些人会称之为过早优化。我想这没关系,因为它 a) 免费(生成)和 b) 被广泛认可(几乎每个 IDE 都这样做)。

【讨论】:

  • 技术上不需要产生一个好的散列,但return 0 会导致任何HashMap 的绝对最坏情况的性能,任何理智的人都不应该使用这样一个损坏的散列。我不会称之为过早优化。
  • @zapl 我并没有说零是一个好的散列(“Even ...”),只是这是一个有效的散列。将素数多次乘以完全有效且健全的哈希(int、string、byte[])可能被认为是过早的优化,我会支持该声明。
  • 你是对的,调整主要因素等很容易过早优化。在 return 0 之外创建一个基本实现不仅仅是 IMO 优化,还需要让 hashCode 按预期工作,否则你就是违反合同。
  • 您链接到的第一个 SO 问题为我指明了正确的方向。我想如果我想获得更多特定于 Netbeans 的答案,我会深入研究他们的代码 :)
【解决方案2】:

来自 Joshua Bloch 的第 9 条,Effective Java 2nd ed.,要记住的重要一点是在覆盖 equals() 时始终覆盖 hashCode() 以确保相等的对象具有相等的哈希代码——否则你可能很容易违反本合同。虽然他说最先进的哈希函数是博士研究的主题,但他为一个好的通用 hashCode 提供的配方可能,在你的情况下,产生:

@Override
public int hashCode() {
    int result = 17;
    result = 31 * result + id;
    result = 31 * result + bar.hashCode();
    result = 31 * result + Arrays.hashCode(things);
    return result ;
}  

正如@zapl 和 David Ehrmann 所提到的,编译器可以轻松地将 31 的乘法优化为位移位和负 1 运算,因此如果这很重要,它可能会更快一点。

【讨论】:

    【解决方案3】:

    IBM 有 an article 关于如何编写自己的 equals()hashCode() 方法。他们所做的很好,尽管 31 往往是更好的素数,因为可以更好地优化乘法。

    还可以查看how String.hashCode() works。正是这样,但有不同的素数和齐次类型。

    【讨论】:

    • 在您的 hashCode() 方法中返回 String 属性的哈希码时,是否建议 @davidehrmann 考虑(如您所说),String 类已经做了类似的事情(使用 31)来生成它哈希码。例如,如果一个类的字符串属性为“name”,用于定义相等性,那么简单地使用 return Objects.hashCode(this.name); 是否足够好?或者是否值得编写如上所示的内容/让 IDE 生成方法。似乎是不必要的重复,有没有优势?如果没有,会在哪里推荐?
    • 如果您有一个具有单个字符串属性的类,您希望在HashMapHashSet 中与Strings 一起使用,则可能值得乘以某个值或传递一个第二个参数到Objects.hashCode(),但这感觉像是一个边缘案例。几乎所有时候我都使用自定义类作为地图键,它只是地图中的那个类和子类;我不把它和Strings混在一起。
    • 谢谢@davidehrmann,我的意思是说我有一个HashMap,它把一个自定义的'House'对象作为键,然后作为值,如果House对象采用单个String属性(比如“houseName”)并用它来确定相等性,是否可以简单地返回 houseName 属性的 hashCode(因为 String 类创建了一个非常好的哈希),或者是否有必要编写我们自己的哈希,如图所示更多?希望这很清楚。干杯
    猜你喜欢
    • 2016-04-21
    • 2014-08-28
    • 1970-01-01
    • 1970-01-01
    • 2013-04-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-19
    相关资源
    最近更新 更多