Java中的HashCode方法与内存泄漏问题,你了解过吗?


Java中的HashCode方法与内存泄漏问题,你了解过吗?

文章插图

这是一篇关于hashCode方法,可变对象和内存泄漏问题的文章 。
1. 重写 hashCode() 和 equals() 的契约
每个 JAVA 对象都有两个非常重要的方法,比如 hashCode() 和 equals() 方法 。这些方法旨在根据其特定的一般规则进行重写 。本文描述了为什么以及如何覆盖 hashCode() 方法,该方法在使用 HashMap , HashSet 或任何 Collection 时保留 HashCode 的契约 。
1.1 hashCode 契约
hashCode 的契约就是:
如果两个对象相等,那么调用两个对象的 hashCode() 方法一定会返回相同的 hash 值 。
现在你应该想到的问题是:上述陈述是否应该永远是真的?
考虑一下这样一个事实,当我们为我们的类提供了一个正确的 equals 实现,那么如果我们不遵守上述规则会发生什么 。
【Java中的HashCode方法与内存泄漏问题,你了解过吗?】为了回答上面的问题,我们来考虑两个问题:
  1. 对象是相等的,但是返回了不同的 hashCode
  2. 对象不是相等的,但是它们却有相同的 hashCode
1.1.1 对象是相等的,但是返回了不同的 hashCode
当两个对象是相等的,但是返回了不同的 hashCode 会发生什么?你的代码会运行的很好 。除非你没有将对象存储在像 HashSet 或 HashMap 这样的集合中,否则永远不会遇到麻烦 。但是当你将你的对象存储到上面提到的那种集合中的时候,在运行的时候可能会发生一些奇怪的问题 。
为了更好的理解这个问题,你必须理解集合类中像 hashMap , HashSet 这样的数据结构是如何工作的 。这些集合类取决于您作为其中的键放置的对象,且必须遵守上述契约的事实 。如果你没有遵循上面的契约, 并且尝试将对象存储在集合中,那么在运行期你将会得到一个奇怪并且不可预料的结果 。
以 HashMap 为例子来说明 。当你在 hashMap 中存储值的时候,这些值实际存储在一组桶中 。每个桶都分配了一个用于识别它的号码 。当你在 HashMap 中 put 一个值的时候,它就会在那些桶中存储数据 。具体存储在哪个桶中,取决于你的对象所返回的 hashcode。换句话说,如果一个对象调用 hashCode() 方法返回了49,那么它就会存储在 HashMap 编号为49的这个桶中 。
随后,当你尝试通过调用 contains(element) 方法去检查集合中是否包含该元素 。HashMap 首先会得到这个 element 的 hashCode, 然后,它将查看与 hashCode 对应的存储桶 。如果存储桶为空,则表示我们已完成,并且返回 false ,这意味着 HashMap 不包含该元素 。
如果存储桶中有一个或多个对象,则它将使用您定义的 equals() 函数将 element 与该存储桶中的所有其他元素进行比较 。
1.1.2 对象不是相等的,但是它们却有相同的 hashCode
hashCode 契约没有说明上述语句 。因此,不同的对象可能返回相同的 hashCode 值, 但是如果不同的对象返回相同的 hashCode 值,则像 HashMap 这样的集合将无法正常使用 。
1.2 为什么是存储桶?
您可以想象,如果放在 HashMap 中的所有对象都存储在一个大列表中,那么当您想要检查特定元素是否在 Map 中时, 您必须将输入与列表中的所有对象进行比较 。通过使用存储桶,您现在只比较特定存储桶的元素, 并且任何存储桶通常只包含 HashMap 中所有元素的一小部分 。
1.3 重写 hashCode 方法
编写一个好的 hashCode() 方法对于新类来说总是一项棘手的任务 。
1.3.1 返回固定的值
您可以实现 hashCode() 方法,以便始终返回固定值,例如:
  •  
//bad performance @Override public int hashCode() {return 1; }上述方法满足所有要求,并根据哈希码合同被认为是合法的,但效率不高 。如果使用此方法,则所有对象将存储在同一个存储桶(即存储桶1)中,当您尝试确保特定对象是否存在于集合中时,它始终必须检查集合的整个内容 。另一方面,如果为您的类重写 hashCode() 方法,并且该方法违反了契约,则调用 contains() 方法可能会对集合中存在但在另一个存储桶中的元素返回 false。
1.3.2 Effective Java中的方法
Joshua Bloch 在 Effective Java 中提供了一个生成 hashCode() 值的指导方法:
  1. 存储一些常量非零值;比方说17,在一个名为 result 的 int 变量中 。
  2. 对于对象中的每个重要字段 f ( equals() 考虑的每个字段),请执行以下操作:
a. 为字段 c 计算一个 int 类型的 hashCode ;
i. 如果值域是一个布尔类型值,计算 c=(f?1:0)


推荐阅读