是否可以使用可变对象的hashCode()?

时间:2016-09-20 14:59:32

标签: java hash hashcode

我一直在浏览Java Collections框架的源代码,并注意到AbstractListAbstractMap将其元素的哈希码的总和作为自己的哈希码值返回。这使得List和Map实现根据集合的内容返回不同的哈希代码值。

我所知道的hashCode()方法的唯一用途是将元素分配到使用散列的数据结构内的桶中,例如HashSetHashMap。但是,SetMap合同指定:

  

如果在对象是集合中的元素时,以影响等于比较的方式更改对象的值,则不指定集合的​​行为。

  

如果在对象是地图中的关键字时,以影响等于比较的方式更改对象的值,则不会指定地图的行为。

这些契约使对象可以返回不同的哈希值,具体取决于它们在集合中使用的危险状态以及映射中的键。这让我想知道:

  1. 返回变量哈希值是否有意义?
  2. 返回常量(每个对象类)哈希值不是更好吗?
  3. hashCode()方法除了在哈希集合中使用之外还有什么用处?

4 个答案:

答案 0 :(得分:2)

  

返回变量哈希值是否有意义?

数据结构不知道您是否更改了键或元素的hashCode。这样做会破坏数据结构。但是,如果在将对象添加到哈希集合之前对其进行变更,然后不进行更改,则可以使用该对象。

  

返回一个常量哈希值不是更好吗?

是的,但是该常量必须与equals一致,在这种情况下,返回常量以不改变用于创建hashCode()的任何字段的唯一方法。

  

除了在哈希集合中使用hashCode()方法之外还有什么用途

它可用于识别单个对象以进行调试/记录,但如果您使用内置的Object.hashCode()或System.identityHashCode(),则更有用。这是检查两个对象是否是相同引用的快速方法,例如。

ByteBuffer bb = ByteBuffer.allocate(1024);
ByteBuffer bb2 = ByteBuffer.allocate(1024);

useBB(bb);
useBB(bb2);

public static void useBB(ByteBuffer bb) {
    System.out.println(Thread.currentThread()+" - Using bb " + System.identityHashCode(bb));
    // do something
}

如果您看到两个线程使用相同的ByteBuffer,则可能会出现问题....

  

你可以在实例化时在构造函数中生成它,就像它对String对象一样。

String的hashCode是按需生成的,以减少创建不需要hashCode的字符串的开销。注意:拥有hashCode为0的String虽然很少见,但是很少见,除非有人尝试使用hashCode为0创建字符串。

类似地,Object的hashCode是按需生成的。它存储在Oracle / OpenJDK的头文件中,初始值为0表示未设置,但在首次使用时设置为1 - 2 ^ 31-1。

该程序通过将hashCode重置为0来对Object.hashCode()的成本进行基准测试。

https://github.com/peter-lawrey/Performance-Examples/blob/master/src/main/java/vanilla/java/unsafe/HashCodePerf.java

此程序基准测试使用更简单的策略设置Object.HashCode()。

https://github.com/peter-lawrey/Performance-Examples/blob/master/src/main/java/vanilla/java/unsafe/FasterHashCodePref.java

  

我的意思是你需要根据final字段生成它,所以hash不会改变。

这不足以保证hashCode不会改变,例如。

class A {
    final List<String> strings = new ArrayList<>();

    public int hashCode() {
        return strings.hashCode();
    }
}

您的字段必须同时为final且不可变。确保hashCode不会改变。但是,如果您不更改它们,它们也不会更改。

答案 1 :(得分:1)

  

返回变量哈希值是否有意义?

是。类的hashCode()方法应与其equals()方法一致。如果一个类根据可变状态定义实例相等性,那么它的hashCode()也应该根据相同的可变状态来定义。

这些对象很少适合包含在基于散列的集合中,但是如果人们小心避免在它们保留在内部时对它们进行变异,那么就可以在这些集合中使用。

  

返回一个常量哈希值不是更好吗?

Object.hashCode()实际上是这样做的,假设你的意思是每个实例意义上的“常数”。如果你没有覆盖equals()那么也没有理由覆盖那个常量哈希值,但如果你覆盖equals()那么你真的应该提供一致的hashCode()实施。

  

除了在哈希集合中使用hashCode()方法之外还有什么用处?

如果一个类缓存其计算的哈希码,那么比较实例的哈希码可以作为一种廉价的“可能相等”的测试,允许在有问题的对象实际上不相等的许多情况下绕过更昂贵的精确相等性测试。

类似地,如果根据可变状态定义哈希码,则哈希码的更改是该状态已更改的正指示(尽管反过来不是真的)。​​

答案 2 :(得分:0)

  
      
  1. 返回变量哈希值是否有意义?
  2.   

没有。但是,如果用户在将列表用作哈希表中的键时不小心不修改列表及其内容,则哈希值不会发生变化,哈希表操作将正常工作。这当然很危险,因为用户有责任确保哈希码在需要时不会改变,但这是可能的。

  
      
  1. 返回一个常量哈希值不是更好吗?
  2.   

不是真的。确实,它确保哈希码值始终遵循其契约(2个相等的对象总是返回相同的哈希码),即使列表发生了变化......但是如果所有元素都得到了,那么使用哈希表的重点是什么?因为常量哈希码而放在同一个桶中?性能会很差,最好在那时使用另一种集合类型。

  
      
  1. 除了在哈希集合中使用hashCode()方法之外还有什么用处
  2.   

没有别的。有些人创造性地使用了哈希码值,但它们通常是无效的,并且基于对哈希码的含义的错误理解。

答案 3 :(得分:0)

  

如果对象的值是,则不指定集合的​​行为   在对象中以一种影响等于比较的方式改变   是集合中的一个元素。

如果我们将这句话翻译成实际意义,事情会变得更加清晰。用简单的语言:

  

java.util.Set不应该用于存储具有可变性的对象   身份。在这种用法中,两个对象A和B是相同的   目的是A.hashCode() == B.hashCode()A.equals(B)B.equals(A)   > nul 2>&1 返回true。

所以问题的答案是:

  1. 返回变量哈希码没有意义。
  2. 哈希码必须在相等的对象之间保持不变。每个类不变的哈希码不会破坏合同,但对性能没有用。
  3. 哈希集合之外的重写hashCode()方法没有用处。