SitePoint 探索 Java 世界:诚邀 Java 开发者投稿
SitePoint 持续拓展内容领域,近期将重点关注 Java。如果您是经验丰富的 Java 开发者,并希望为我们的 Java 内容贡献力量,欢迎联系我们,分享您想撰写的文章主题构想。
Java 中 equals
与 hashCode
方法的正确实现
您已为您的类实现了 equals
方法?很棒!但您也必须实现 hashCode
方法。让我们了解原因以及如何正确实现它。
关键要点:
equals
方法,则必须创建匹配的 hashCode
实现,以确保在基于哈希的集合中存储和检索对象的准确性和一致性。hashCode
时,应使用与 equals
方法中使用的相同字段。应尽量避免使用可变字段和集合,因为这可能会导致性能问题。equals
和 hashCode
方法
虽然 equals
方法从一般角度来看是合理的,但 hashCode
方法则更具技术性。严格来说,它只是一个用于提高性能的实现细节。
大多数数据结构使用 equals
方法来检查它们是否包含某个元素。例如:
List<String> list = Arrays.asList("a", "b", "c"); boolean contains = list.contains("b");
变量 contains
为真,因为虽然 "b" 的实例并不相同(再次忽略字符串驻留),但它们是相等的。
然而,将每个元素与传递给 contains
方法的实例进行比较效率低下,而一类数据结构则使用更高效的方法。它们不将请求的实例与它们包含的每个元素进行比较,而是使用快捷方式来减少可能相等的实例数量,然后只比较这些实例。
这个快捷方式就是哈希码,它可以被视为对象的相等性简化为一个整数值。具有相同哈希码的实例不一定是相等的,但相等的实例具有相同的哈希码。(或者应该具有相同的哈希码,我们稍后将讨论这一点。)此类数据结构通常以其技术名称命名,其名称中包含 "Hash",其中 HashMap
是最著名的代表。
它们通常的工作方式如下:
contains
方法时,使用其哈希码计算桶。只有其中的元素才会与该实例进行比较。这样,实现 contains
方法可能只需要很少的,理想情况下不需要任何 equals
比较。
与 equals
方法一样,hashCode
方法也在 Object
类中定义。
关于哈希的思考
如果 hashCode
方法用作确定相等性的快捷方式,那么我们真正应该关心的只有一件事:相等的对象应该具有相同的哈希码。
这也是为什么如果我们重写 equals
方法,就必须创建一个匹配的 hashCode
实现的原因!否则,根据我们的实现相等的事物可能不会具有相同的哈希码,因为它们使用 Object
类的实现。
hashCode
方法的约定
引用源代码:
hashCode
方法的一般约定是:
- 每当在 Java 应用程序的执行过程中多次对同一个对象调用它时,
hashCode
方法必须始终返回相同的整数,前提是没有修改在对象的equals
比较中使用的信息。此整数不必在一个应用程序的执行与同一应用程序的另一个执行之间保持一致。- 如果根据
equals(Object)
方法,两个对象相等,则对这两个对象中的每一个调用hashCode
方法必须产生相同的整数结果。- 如果根据
equals(Object)
方法,两个对象不相等,则不需要调用这两个对象上的hashCode
方法必须产生不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同的整数结果可以提高哈希表的性能。
第一点反映了 equals
方法的一致性属性,第二点是我们上面得出的要求。第三点说明了一个重要的细节,我们稍后将讨论。
实现 hashCode
方法
一个非常简单的 Person.hashCode
实现如下:
List<String> list = Arrays.asList("a", "b", "c"); boolean contains = list.contains("b");
人的哈希码是通过计算相关字段的哈希码并将它们组合起来计算的。两者都留给 Objects
的实用程序函数 hash
来处理。
选择字段
但是哪些字段是相关的呢?这些要求有助于回答这个问题:如果相等的对象必须具有相同的哈希码,那么哈希码计算不应包含任何未用于相等性检查的字段。(否则,只有在这些字段上不同的两个对象将是相等的,但具有不同的哈希码。)
因此,用于哈希的字段集应该是用于相等性的字段集的子集。默认情况下,两者都将使用相同的字段,但有一些细节需要考虑。
一致性
首先,有一致性要求。它应该被相当严格地解释。虽然它允许在某些字段发生更改时哈希码发生更改(对于可变类来说这通常是不可避免的),但哈希数据结构并未为此场景做好准备。
正如我们上面所看到的,哈希码用于确定元素的桶。但是,如果哈希相关字段发生更改,则不会重新计算哈希,并且不会更新内部数组。
这意味着使用相等的对象或甚至使用完全相同的实例的后续查询将失败!数据结构计算当前的哈希码(与用于存储实例的哈希码不同),并在错误的桶中查找。
结论:最好不要使用可变字段进行哈希码计算!
性能
哈希码的计算次数可能与 equals
方法的调用次数大致相同。这很可能发生在代码的关键性能部分,因此考虑性能是有意义的。并且与 equals
方法不同,这里有更多空间进行优化。
除非使用复杂的算法或涉及许多字段,否则组合其哈希码的算术成本与不可避免的成本一样微不足道。但是应该考虑是否需要将所有字段都包含在计算中!特别是应该对集合持怀疑态度。例如,列表和集合将为它们的每个元素计算哈希值。是否需要调用它们应该根据具体情况进行考虑。
如果性能至关重要,使用 Objects.hash
也可能不是最佳选择,因为它需要为其可变参数创建数组。
但是关于优化的通用规则仍然适用:不要过早优化!使用常见的哈希码算法,也许放弃包含集合,并且只有在性能分析显示存在改进的可能性后才进行优化。
冲突
全力以赴追求性能,那么这个实现怎么样?
List<String> list = Arrays.asList("a", "b", "c"); boolean contains = list.contains("b");
它肯定很快。并且相等的对象将具有相同的哈希码,所以我们在这方面也很好。作为奖励,没有涉及可变字段!
但是请记住我们之前关于桶的内容?这样所有实例都将进入同一个桶!这通常会导致一个链表保存所有元素,这对性能来说非常糟糕。例如,每个 contains
调用都会触发链表的线性扫描。
因此,我们希望尽可能减少同一个桶中的项目数量!即使对于非常相似的对象,也能返回差异很大的哈希码的算法是一个良好的开端。如何实现部分取决于所选字段。我们在计算中包含的细节越多,哈希码不同的可能性就越大。请注意,这与我们对性能的想法完全相反。因此,有趣的是,使用过多或过少的字段都可能导致性能不佳。
防止冲突的另一部分是用于实际计算哈希的算法。
计算哈希值
计算字段哈希码的最简单方法是对其调用 hashCode
方法。可以手动组合它们。一个常见的算法是从某个任意数字开始,然后重复地将其与另一个数字(通常是一个小的质数)相乘,然后再添加字段的哈希值:
List<String> list = Arrays.asList("a", "b", "c"); boolean contains = list.contains("b");
这可能会导致溢出,但这在 Java 中不会导致异常,因此问题不大。
请注意,即使是优秀的哈希算法,如果输入数据具有特定模式,也可能导致异常频繁的冲突。作为一个简单的例子,假设我们通过添加点的 x 和 y 坐标来计算点的哈希值。这听起来还不错,直到我们意识到我们经常处理直线 f(x) = -x 上的点,这意味着对于所有这些点,x y == 0。冲突,很多!
但是再次强调:使用常见的算法,并且除非性能分析显示存在问题,否则不要担心。
总结
我们已经看到,计算哈希码就像将相等性压缩为整数值:相等的对象必须具有相同的哈希码,并且出于性能原因,最好尽可能少的不相等对象共享相同的哈希码。
这意味着如果重写了 equals
方法,则必须始终重写 hashCode
方法。
实现 hashCode
方法时:
equals
方法中使用的相同字段(或其子集)。hashCode
方法。记住,hashCode
方法与性能有关,因此除非性能分析表明有必要,否则不要浪费太多精力。
关于正确实现 Java hashCode
方法的常见问题解答 (FAQ)
hashCode()
方法的意义是什么?Java 中的 hashCode()
方法是一个内置函数,它返回一个整数值。它主要用于基于哈希的集合(如 HashMap
、HashSet
和 HashTable
)以更有效地存储和检索对象。hashCode()
方法与 equals()
方法协同工作,以确保每个对象都有一个唯一的标识符。这有助于快速检索数据,尤其是在大型集合中,从而提高 Java 应用程序的性能。
hashCode()
方法是如何工作的?Java 中的 hashCode()
方法的工作原理是生成一个整数值,该值表示对象的内存地址。此值用作对象在基于哈希的集合中的索引号。当您对对象调用 hashCode()
方法时,它会使用哈希算法来生成此唯一整数。但是,需要注意的是,两个不同的对象可能具有相同的 hashCode
,这被称为哈希冲突。
equals()
和 hashCode()
方法之间的约定是什么?Java 中 equals()
和 hashCode()
方法之间的约定是一组规则,用于管理它们的交互。该约定指出,如果根据 equals()
方法,两个对象相等,则对这两个对象中的每一个调用 hashCode()
方法必须产生相同的整数结果。这确保了在基于哈希的集合中存储和检索对象时的一致性和准确性。
hashCode()
方法?在 Java 中重写 hashCode()
方法包括提供您自己的实现,该实现为每个对象返回一个唯一的整数。这可以通过使用对象的实例变量和质数乘数来实现。质数有助于将哈希码均匀地分布在集合中,从而减少哈希冲突的可能性。
哈希冲突是指 hashCode()
方法为两个不同的对象生成相同的整数。如果处理不当,这可能会导致数据丢失。为了避免哈希冲突,您可以改进哈希算法以生成更多唯一的整数。此外,使用更大的质数作为乘数可以帮助更均匀地将哈希码分布在集合中。
hashCode()
方法?重写 hashCode()
方法可以提高 Java 应用程序的性能,尤其是在处理大型集合时。通过提供您自己的实现,您可以生成更唯一且更均匀分布的哈希码,从而减少哈希冲突的可能性并确保更快的數據检索。
hashCode
吗?是的,在 Java 中,两个不相等的对象可以具有相同的 hashCode
。这被称为哈希冲突。但是,通过改进哈希算法和使用更大的质数作为乘数,可以降低这种情况发生的可能性。
hashCode()
方法会发生什么?如果您不重写 hashCode()
方法,Java 将使用其默认实现,这可能无法为每个对象提供唯一的哈希码。这可能导致哈希冲突和基于哈希的集合中更慢的数据检索。
hashCode()
方法如何提高 Java 应用程序的性能?hashCode()
方法通过为每个对象提供唯一的标识符来提高 Java 应用程序的性能。这允许在基于哈希的集合中更快地检索数据,因为可以使用对象的哈希码直接找到该对象,而无需搜索整个集合。
hashCode()
方法吗?虽然 hashCode()
方法主要用于基于哈希的集合,但它也可以用于非基于哈希的集合。但是,其好处可能不那么明显,因为非基于哈希的集合不依赖于哈希码进行数据存储和检索。
以上是如何正确实现java s HashCode的详细内容。更多信息请关注PHP中文网其他相关文章!