【问题标题】:Default hashCode() implementation for Java ObjectsJava 对象的默认 hashCode() 实现
【发布时间】:2018-03-08 12:06:48
【问题描述】:

我试图理解 Java 对象的 hashCode() 并看到 Java 对象的 hashCode() 方法的以下代码:

package java.lang;
public class Object {

 // Some more code

 public native int hashCode();

 // Some other code

}

现在,我们知道,如果我们创建一个类,它会隐式扩展 Object 类,为此我编写了一个示例:

package com.example.entity;
public class FirstClass {
    private int id;
    private String name;
    // getters and setters
}

因此,这个类即:FirstClass 将隐式扩展 Object 类。

主类:

package com.example.app.main;
import com.example.entity.FirstClass;
    public class MainApp {
        public static void main(String[] args) {
             FirstClass fs = new FirstClass();
             fs.setId(1);
             fs.setName("TEST");
             System.out.println("The hasCode for object fs is " + fs.hashCode());
         }
 }

由于FirstClass 隐式扩展Object 类,因此它将具有Object 类的hashCode() 方法。

我在FirstClass 对象上调用了hashCode(),并且由于我没有覆盖hashCode(),理论上它应该调用Object 类的hashCode()。

我的疑问是:

由于 Object 类没有任何实现,它如何计算任何对象的哈希码?

就我而言,当我运行程序时,它返回的哈希码是 366712642。

谁能帮我理解这个?

【问题讨论】:

  • 是什么让你认为Java的object类没有实现hashCode?
  • 它有 native 修饰符,这意味着它最有可能在 C 或 C++ 中实现,并且这些本地方法随 JDK 一起提供。 stackoverflow.com/questions/18900736/…。注意 Native 和 abstract 方法是不一样的!
  • @Neijwiert:是的,我对此并不完全了解,这就是我问这个问题的原因。由于我只看到方法声明(没有正文),我怀疑它如何仍然能够获得值。

标签: java java-8 hashcode


【解决方案1】:

尽管这里有一些答案表明默认实现是基于“内存”的,但这显然是错误的。现在很多年都不是这种情况。

在java-8下,你可以这样做:

java -XX:+PrintFlagsFinal | grep hashCode

获取所使用的确切算法(5 为默认值)。

  0 == Lehmer random number generator, 
  1 == "somehow" based on memory address
  2 ==  always 1
  3 ==  increment counter 
  4 == memory based again ("somehow")
  5 == read below

默认情况下(5)使用 Marsaglia XOR-Shift 算法,与内存无关。

如果你这样做,这并不难证明:

 System.out.println(new Object().hashCode());

多次,在一个新的虚拟机中 - 你会得到相同的值,所以 Marsaglia XOR-Shift 从一个种子开始(总是相同的,除非其他代码没有改变它)并以此为基础工作。

但是,即使您切换到一些基于内存的 hashCode,并且对象可能会四处移动(垃圾收集器调用),您如何确保在 GC 移动此对象之后 采用相同的 hashCode ?提示:indentityHashCode 和 Object 标头。

【讨论】:

  • 感谢您的信息。此标志显示什么信息?没见过这个选项,能解释一下吗?
  • @CuriousMind 我不知道我还能在这里添加什么......正如我在回答中所说,hashCode 有 5 个选项,默认为 5。你会在这儿吃什么more?
  • 我正在考虑 -XX:+PrintFlagsFinal ;你的答案很完美;但是我从来没有听说过这个选项;这就是我要的信息。不过我会尽量在网上找。感谢您抽出宝贵时间提供帮助。
  • @CuriousMind 哦,那个。它只是打印 JVM 拥有的所有标志及其默认值...
  • 一个人永远不会停止学习。看看我可以做些什么来更新我的答案以更准确。
【解决方案2】:

你搞错了:

public native int hashCode();

并不意味着有没有实现。这只是意味着该方法是在 native 又名 JVM 的 C/C++ 部分中实现的。这意味着您找不到该方法的 Java 源代码。但是,当您在某个 Object 上调用 hashCode() 时,JVM 中仍有一些代码会被调用。

正如另一个答案所解释的那样:“默认”实现使用了底层对象的“内存”地址。事情是:使用 java 意味着,没有“内存地址”的知识。请记住:JVM 是用 C/C++ 编写的 - 真正的 内存管理发生在 JVM 的这些 native 部分中。

换句话说:您不能编写告诉您对象的“本机内存地址”的 Java 代码。

但正如 Eugene 的另一个回答所表明的那样:关于“内存位置”的哈希已成为过去。

【讨论】:

  • @CuriousMind 我在答案中添加了另一段。希望对您有所帮助。
  • @GhostCat 错误。默认实现是不基于内存的。阅读:stackoverflow.com/a/49175508/1059372
  • @Eugene 这里有两个方面。由于垃圾收集器移动对象时对象的内存地址可能会发生变化,因此一旦查询到对象,它们就必须以某种方式记住其报告的哈希码。但是,存在一个问题,即对象是在线程的 TLAB 中创建的,也就是说,在移动到幸存者空间之前,对象是在相同的内存区域中创建的,如果仍然可以访问的话。因此,使用基于内存的哈希码存在值非常接近的风险(哈希分布不佳)。
  • @Holger 对,IIRC 在对象标头中有一个标志,如果 hashCode 已经被计算或没有被计算并保存在该标头的 identityHashCode 的空间中
  • @Eugene Object.hashCode still 的规范提到了对象的地址,这是误导性的。我已经提交了JDK-8199394 来解决这个问题。
【解决方案3】:

对象类中Hashcode的默认实现是对象的十六进制内存地址。 JVM 调用 this 的实现。

一些有用的链接是:

https://docs.oracle.com/javase/tutorial/java/IandI/objectclass.html https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html

【讨论】:

  • 错误。默认实现是不基于内存的
  • 除了不是内存地址之外,哈希码也不是“十六进制”。这没有任何意义;哈希码只是一个int。 toString() 的默认实现生成哈希码值的十六进制表示,但这不是哈希码本身的属性。
  • 感谢您的链接!不知道为什么 cmets 争论它不是基于地址,而它在 oracle 文档中写得很清楚
  • @dontloo 因为即使是 oracle 文档也可能是错误的或误导性的。甚至在您发表评论之前,this comment 已经解决了这个问题。因此,up-to-date versions of this documentation 不再提及内存地址。
  • @Paul 好吧,Eugene 还写了一个答案,提供了更多细节。除此之外,如果有人花时间阅读得分最低的答案及其下方的 cmets 并发表评论,那么期望该人还阅读其他用户投票认可的其他答案是否真的不合理?有用,也许是他们的 cmets,尤其是那些突出显示积极投票数的?
猜你喜欢
  • 1970-01-01
  • 2021-11-18
  • 1970-01-01
  • 2010-10-19
  • 2013-08-06
  • 1970-01-01
  • 2019-06-02
  • 2013-04-05
  • 1970-01-01
相关资源
最近更新 更多