【问题标题】:Is there a use for hashCode() of a mutable object?可变对象的 hashCode() 是否有用?
【发布时间】:2016-09-20 14:59:32
【问题描述】:

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

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

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

和:

如果对象的值以影响等于比较的方式更改,而对象是映射中的键,则不指定映射的行为。

这些合约使可能根据状态返回不同哈希值的对象在集合中使用和作为映射中的键是危险的。这让我想知道:

  1. 返回变量哈希值有什么意义吗?
  2. 返回一个常量(每个对象类)哈希值不是更好吗?
  3. hashCode() 方法除了用于哈希集合还有什么用途?

【问题讨论】:

  • 拥有一个对象的常量哈希码会违反它的contract。例如,如果您在第一次调用时将哈希码锁定为一个值,那么两个相等的不同实例可能具有不同的哈希码。
  • @resueman,是的,但是您可以在实例化时在构造函数中生成它,就像对 String 对象所做的那样。
  • 您仍然会遇到同样的问题。具有不同哈希码的两个对象可以修改为彼此相等。
  • @resueman,抱歉我不清楚:我的意思是你需要根据最终字段生成它,所以哈希不会改变。

标签: java hash hashcode


【解决方案1】:

返回一个变量哈希值有什么意义吗?

数据结构不知道您是否更改了键或元素的 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 的地方创建 String 的开销。注意:除非有人尝试创建 hashCode 为 0 的字符串,否则拥有一个 hashCode 为 0 的字符串是昂贵的,但很少见。

同样,Object 的 hashCode 也是按需生成的。它存储在 Oracle/OpenJDK 的 header 中,初始值为 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

我的意思是你需要根据最终字段生成它,所以哈希不会改变。

这不足以保证 hashCode 不变,例如

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

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

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

【讨论】:

  • 我的意思是String hash是根据final字段生成的)
  • @RobertMugattarov 是的,hashCode 是基于仅在构造函数中设置的字段。
  • It's a quick way to check if two objects are the same reference 绝对不是。 API 甚至没有强制 Object#hashCode() 的默认行为,即使它通常是地址:This is typically implemented by converting the internal address of the object into an integer, but this implementation technique is not required by the JavaTM programming language. 即使那样,你也不能依赖它,因为如果有人重写了这个方法,那么它就不会不再意味着地址,即使您使用的是使用地址作为默认返回值的 Java 实现。
  • 具有相同 hashCode 的示例并不意味着您拥有相同的对象 twitter.com/PeterLawrey/status/856448017376923648 所有这些字符串的 hashCode 为 0
  • 即使identityHashCode 也不能保证是唯一的。
【解决方案2】:

返回一个变量哈希值有什么意义吗?

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

此类对象很少适合包含在基于哈希的集合中,但如果小心避免在它们留在内部时对其进行变异,它们可以在此类集合中使用。

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

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

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

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

类似地,如果哈希码是根据可变状态定义的,那么哈希码的变化是该状态已改变的积极迹象(尽管反过来不正确)。

【讨论】:

    【解决方案3】:
    1. 返回变量哈希值有什么意义吗?

    没有。但是如果用户在使用列表作为哈希表中的键时注意不要修改列表及其内容,那么哈希值不会改变并且哈希表操作将正常工作。这当然很危险,因为用户需要负责确保哈希码在需要时不会更改,但这是可能的。

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

    不是真的。确实,它保证哈希码值将始终遵守其约定(即 2 个相等的对象始终返回相同的哈希码),即使列表发生更改......但是如果所有元素都得到,那么使用哈希表的意义何在由于恒定的哈希码而放在同一个桶中?性能会很糟糕,此时最好使用其他集合类型。

    1. hashCode() 方法除了用于哈希集合还有什么用处

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

    【讨论】:

      【解决方案4】:

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

      如果我们把这句话翻译成实践中的意思,事情就会变得更清楚。用简单的语言:

      不应使用 java.util.Set 来存储可变对象 身份。在这种用法中,两个对象 A 和 B 对于 如果A.hashCode() == B.hashCode()A.equals(B)B.equals(A) 返回真。

      所以问题的答案:

      1. 返回可变哈希码没有意义。
      2. 哈希码在相等对象中必须是常数。每个类都是恒定的哈希码不会违反约定,但对性能毫无用处。
      3. 在哈希集合之外没有使用覆盖的 hashCode() 方法。

      【讨论】:

        猜你喜欢
        • 2011-06-10
        • 2015-12-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-07-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多