如果没有重写hashCode()方法,一个对象的哈希码是什么?

87
如果没有重写hashCode()方法,在Java中调用任何对象的hashCode()方法会得到什么结果?

2
看一下System.identityHashCode()它会给你默认的哈希码 - 如果你没有重写方法,这就是将会被返回的哈希码。 - Ustaman Sangat
这取决于您使用的JVM。对于Hotspot,请参见此处 - mightyWOZ
12个回答

75
默认情况下,在HotSpot JVM中第一次调用非重载的Object.hashCodeSystem.identityHashCode时会生成一个随机数并存储在对象标头中。随后对Object.hashCodeSystem.identityHashCode的调用只是从标头中提取此值。默认情况下,它与对象内容或对象位置没有任何关系,只是一个随机数。此行为由-XX:hashCode=n HotSpot JVM选项控制,该选项有以下可能的值:
  • 0:使用全局随机生成器。这是Java 7的默认设置。它的缺点是多个线程同时调用可能会导致竞争条件,从而导致为不同的对象生成相同的hashCode。在高度并发的环境中,由于使用来自不同CPU核心的相同内存区域,可能会出现延迟。
  • 5:使用一些线程本地的xor-shift随机生成器,免受先前的缺点的影响。这是Java 8的默认设置。
  • 1:使用对象指针混合某些随机值,在“停止世界”事件之间更改,因此在停止世界事件(例如垃圾收集)之间生成的hashCodes是稳定的(用于测试/调试目的)
  • 2:始终使用1(用于测试/调试目的)
  • 3:使用自增数字(用于测试/调试目的,还使用全局计数器,因此可能存在争用和竞争条件)
  • 4:根据需要将对象指针裁剪为32位(用于测试/调试目的)
请注意,即使您设置了-XX:hashCode=4,hashCode也不总是指向对象地址。对象可能稍后移动,但hashCode仍将保持不变。而且,对象地址分布不均(如果您的应用程序使用的内存不多,则大多数对象将靠近彼此),因此如果使用此选项,则可能会导致不平衡的哈希表。

这是最好的解释,谢谢!由于-XX:hashCode=n是一个实验性功能,需要先启用它,然后使用上述标志,因此组合的vm标志为:-XX:+UnlockExperimentalVMOptions -XX:hashCode=4,在java-11中进行了测试。 - dkb

46

如果你没有覆盖(override)hashCode()方法,通常它会返回对象在内存中的地址。

1得知:

尽可能地讲,由Object类定义的hashCode方法返回不同的整数。 (通常这是通过将对象的内部地址转换为一个整数来实现的,但Java编程语言并不要求使用这个实现技巧)

1 http://java.sun.com/javase/6/docs/api/java/lang/Object.html#hashCode


18
我强烈反对“转换内部地址”,并一直在想Sun/Oracle是否会从他们的javadocs中删除那行。对象的内部地址在JVM中不能保证不变,因为垃圾收集器在堆压缩期间可能会将其移动。 - Ustaman Sangat
19
JavaDoc 的引用是正确的,但回答是不正确的。很多年来,对象的地址没有被使用。 - Tagir Valeev
1
@ernesto 不,它们并非一直是唯一的。我在我的JVM上进行了测试,并发现在生成120,000个对象后,开始出现重复的哈希码。 您可以在此处阅读更多信息 https://dev59.com/rZ3ha4cB1Zd3GeqPNwUp - Sameer
1
正如其他人所说,文档中提到的内存地址是不合适的,但最终得到了修复。首先,它删除了“通常”的说法,因此它表示hashCode“可能或可能不会在某个时间点被实现为对象内存地址的某些函数”,然后完全删除了地址的提及,消除了任何混淆的源头。案件结束。 - Holger

16

hashCode() 的实现因类而异,但 hashCode() 的契约却非常明确和清晰地在 Javadoc 中规定:

返回对象的哈希码值。此方法支持哈希表(例如 java.util.Hashtable)等使用。

hashCode 的通用契约如下:

  • 在 Java 应用程序的同一次执行过程中,只要不修改用于对象相等比较的信息,多次调用 hashCode 方法必须始终返回相同的整数。这个整数不需要在应用程序的一个执行过程中保持一致到另一个执行过程。
  • 如果根据 equals(Object) 方法两个对象相等,则对这两个对象中的每个对象调用 hashCode 方法都必须产生相同的整数结果。
  • 虽然不要求如果两个对象根据 equals(java.lang.Object) 方法是不相等的,那么对这两个对象中的每个对象调用 hashCode 方法必须产生不同的整数结果,但是程序员应该意识到,为不相等对象生成不同的整数结果可能会提高哈希表的性能。

尽可能实际,由 Object 类定义的 hashCode 方法确实为不同的对象返回不同的整数。(这通常是通过将对象的内部地址转换为整数来实现的,但 JavaTM 编程语言并不要求此实现技术。)

hashCode()方法与equals()方法密切相关,如果你要覆盖equals()方法,那么也应该覆盖hashCode()方法。


2
“hashCode()与equals()密切相关,如果你实现了其中一个,就应该实现另一个”这种说法并不完全正确。只有在重写equals方法时才需要重写hashCode方法。技术上讲,重写hashCode而不重写equals也是有效的。 - Steve Kuo
@Steve Kuo:好的,根据您的评论,我重新措辞了最后一句话。 - Asaph

4
默认的hashCode()实现与对象的内存地址无关。在openJDK中,在6和7版本中是随机生成的数字。在8和9中,它是基于线程状态的数字。
参考此链接:hashCode != address 因此,身份哈希生成的结果(默认实现的hashCode()方法返回的值)仅生成一次,并缓存在对象的标头中。
如果您想了解更多信息,可以阅读OpenJDK,其中定义了hashCode()的入口点:
src/share/vm/prims/jvm.h

src/share/vm/prims/jvm.cpp 如果您查看上述目录,似乎有数百行功能,这些功能似乎更加复杂难懂。因此,为了简化此过程,表示默认hashcode实现的方式如下所示:
if (obj.hash() == 0) {
    obj.set_hash(generate_new_hash()); 
} 
return obj.hash();

3
如果没有重写hashcode方法,你将调用Object的hashcode方法,以下是其javadoc的摘录:
尽可能合理地说,由Object类定义的hashCode方法确实为不同的对象返回不同的整数。(通常通过将对象的内部地址转换为整数来实现这一点,但JavaTM编程语言不要求使用此实现技术。)

2

如果一个类重写了equals方法,那么它必须也重写hashCode方法。否则会违反Object.hashCode的通用契约,从而导致你的类无法与所有基于哈希的集合(包括HashMap、HashSet和Hashtable)一起正常运行。


2
默认的哈希码实现会将对象在 JVM 中的内部地址作为一个 32 位整数返回。因此,两个不同的(在内存中)对象将具有不同的哈希码。
这与 equals 的默认实现一致。如果您想要重写对象的 equals 方法,您需要调整 hashCode 方法以使它们一致。
请参见 http://www.ibm.com/developerworks/java/library/j-jtp05273.html 以获取良好的概述。

1

哈希码对于将对象存储在集合中(例如哈希集)非常有用。通过允许对象定义一个唯一的哈希码,它使得哈希集算法能够有效地工作。

Object本身使用内存中的对象地址,这是非常独特的,但如果两个不同的对象(例如两个相同的字符串)应该被视为相同,即使它们在内存中重复,这种方法可能并不是很有用。


0

这并不是一个答案,而是对我之前评论的补充

对象的内部地址在JVM中不能保证不变,因为垃圾回收器可能会在堆压缩期间将其移动。

我尝试做了这样一件事:

public static void main(String[] args) {
    final Object object = new Object();
    while (true) {
        int hash = object.hashCode();
        int x = 0;
        Runtime r = Runtime.getRuntime();
        List<Object> list = new LinkedList<Object>();
        while (r.freeMemory() / (double) r.totalMemory() > 0.3) {
            Object p = new Object();
            list.add(p);
            x += object.hashCode();//ensure optimizer or JIT won't remove this
        }
        System.out.println(x);
        list.clear();
        r.gc();
        if (object.hashCode() != hash) {
            System.out.println("Voila!");
            break;
        }
    }
}

但是哈希码确实没有改变...有人能告诉我Sun的JDK实际上如何实现Obect.hashcode吗?


OpenJDK http://hg.openjdk.java.net/jdk6/jdk6-gate/jdk/file/tip/src/share/classes/java/lang/Object.java 将其作为本地函数。我想看到本地函数的实现...https://dev59.com/dnRC5IYBdhLWcg3wFdJx. 有人可以提供帮助吗? - Ustaman Sangat
也许默认的哈希码方法会存储第一个值并且永远不会改变它。 - Ustaman Sangat

0
你应该尝试实现哈希码,使得不同的对象产生不同的结果。我认为这没有标准的做法。
阅读本文获取更多信息

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接