【问题标题】:Why is the default hashcode() in java bad?为什么java中的默认hashcode()不好?
【发布时间】:2013-04-05 08:59:00
【问题描述】:

如果我没记错的话,Java 中 Object() 类型对象的默认 hashCode() 实现是返回对象的内存地址。当我们创建自己的类时,我读到我们想要覆盖 hashCode() 以便当我们将它们插入到像 HashMap() 这样的与哈希相关的集合中时,它会正常工作。但是为什么内存地址不好呢?

当然,我们最终会耗尽内存并且您会遇到冲突,但我认为这是一个问题的唯一情况是您正在处理大量数据并且内存非常少,然后它会开始影响性能,因为 java 中与哈希相关的集合通过链接解决冲突(存储桶将链接到解析为相同哈希码/索引的值列表)。

【问题讨论】:

  • 你不是问题所在。你可以开始阅读this。不能独立于平等概念来看待这个问题。
  • 它没有什么不好的,它有时需要因其他原因而改变。
  • 默认hashCode()与内存地址无关。文档提到内存地址作为哈希码计算的可能输入,但这不是在任何最近的 JVM 中实现的方式。
  • @Holger 它可能与内存访问有关,有5种策略可供选择;但默认值确实不是基于内存的,至少是 Marsaglia XOR Shift in 8
  • 可能在问这个问题之前你应该了解哈希映射的工作原理

标签: java hash hashcode


【解决方案1】:

虽然您不使用equals 方法,但不定义hashCode 是可以的,但在hashCode 的合同之后,将为对象提供相同的结果,即等于。你不能确定它会,所以你需要自己重写它

hashCode

【讨论】:

    【解决方案2】:

    如果每个对象都是唯一的,则默认实现可以正常工作。但是,如果您覆盖 equals() ,那么您就是在暗示具有不同地址的对象可以彼此等价。在这种情况下,您还必须覆盖 hashCode()。

    想想 String 类。

    String s1 = new String("foo");
    String s2 = new String("foo");
    

    这两个字符串相等,因此它们的哈希码必须相等。但它们是具有不同地址的不同对象。

    s1 == s2         // false, different addresses
    s1.equals(s2)    // true, same contents
    

    使用他们的地址作为哈希码是错误的。因此,String 覆盖 hashCode() 以确保相等的字符串具有相等的哈希码。这有助于满足 hashCode() 的约定,即:

    如果a.equals(b) 为真,则a.hashCode() == b.hashCode()

    底线:如果你覆盖equals(),也覆盖hashCode()。

    【讨论】:

    • 谢谢,看起来这一切都取决于你如何定义平等。在您提供的示例中,相等性由它们的字符串值确定,因此使用 Object() 的 hashCode() 会很糟糕,因为尽管它们是相同的,但您的哈希图中会有 2 个不同的条目。我查看了 Java 如何为字符串实现 hashCode() 并发现:java-bytes.blogspot.com/2009/10/hashcode-of-string-in-java.html;有人愿意解释这一点或一般如何覆盖 hashCode() 吗?我在网上看到的所有例子看起来都很复杂。
    • Josh Bloch 的《Effective Java》很好地涵盖了这一点,并制定了实施策略。 Ides 也会为你生成合理的实现。例如,一个 Employee 类可能由它的雇员 Id 唯一标识。在这种情况下,您将使用它进行相等和散列。 Josh Bloch 建议使用一些技术来传播密切相关的数字(使哈希更有效(核心 Java 书籍解释了为什么这样做很好)。如果您的类在多个字段(复合键)中是唯一的,那么这些中的每一个都应该在您的相等和哈希方法
    • 我认为“foo”字符串示例并不精确,因为 Java 实习生字符串文字和常量,因此 s1 == s2 是正确的,即使它们是不同的对象。
    • @ChaimKut 这些对象都不是字符串文字; "foo" == "foo" 确实会在 hotpost 上返回 true(也可能是所有其他 JVM),但 new String("foo") 创建了一个新对象,该对象将具有不同的地址。
    【解决方案3】:

    将对象放入 hashmap 时使用的确切关键对象可能无法在您的程序中稍后访问该映射。所以你最终会覆盖 equals 方法。当您覆盖 equals 方法时,请确保它们的哈希码也相等,否则您无法从哈希图中检索对象。

    【讨论】:

      【解决方案4】:

      决定数据结构应该使用哪个 hashCode() 实现是 hashmap API 的隐含部分。

      向某些 API 提供未经您严格处理的信息,会让您失去控制权。

      使用默认的 hashCode() 实现将您希望管理的键 与内存地址结合,这不是由您处理的,您也不需要有任何控制权。

      假设有一天你会想要使用某种内存优化器,它可以在内存中移动对象以实现一致性和更好的性能。 您将无法再使用您的数据结构,因为它包含您密钥的原始哈希地址。

      从更实际的角度来看,

      为了在整个程序中从数据结构中检索值,您必须保留对您之前插入的所有键的引用(通过使用另一个数据结构)。 您将无法使用在对象状态方面“相似”的键。

      Person steve1 = new Person("Steve","21","engineer");
      Person steve2 = new Person("Steve","21","engineer");
      
      map.put(steve1,"great worker");
      
      map.get(steve2); 
      // returns null because "steve2" is not considered a key like "steve1"
      
      map.get(steve1); 
      // returns "great worker"
      

      【讨论】:

      • 您的答案不正确。哈希被缓存并且对象将在对象的生命周期内保持其默认哈希。 GC 的一部分是重新分配内存,所以地址的变化并不少见。由于 hashCode 永远不会改变,因此 hashCode 不能轻易返回地址。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-02
      • 2011-04-13
      • 1970-01-01
      • 2017-03-04
      • 1970-01-01
      相关资源
      最近更新 更多