原文:http://www.ibm.com/developerworks/cn/java/j-jtp05273/
虽然Java语言不直接支持关联数组 -- 可以使用任何对象作为一个索引的数组 -- 但在根 Object
类中使用 hashCode()
方法明确表示期望广泛使用 HashMap
(及其前辈 Hashtable
)。理想情况下基于散列的容器提供有效插入和有效检索;直接在对象模式中支持散列可以促进基于散列的容器的开发和使用。
Object
类有两种方法来推断对象的标识: equals()
和 hashCode()
。一般来说,如果您 Override 了其中一种,您必须同时 Override 这两种,因为两者之间有必须维持的至关重要的关系。特殊情况是根据 equals()
方法,如果两个对象是相等的,它们必须有相同的hashCode()
值(尽管这通常不是真的)。
特定类的 equals()
的语义在Implementer的左侧定义;定义对特定类来说 equals()
意味着什么是其设计工作的一部分。 Object
提供的缺省实施简单引用下面等式:
public boolean equals(Object obj) { return (this == obj); } |
在这种缺省实施情况下,只有它们引用真正同一个对象时这两个引用才是相等的。同样, Object
提供的 hashCode()
的缺省实施通过将对象的内存地址对映于一个整数值来生成。由于在某些架构上,地址空间大于 int
值的范围,两个不同的对象有相同的 hashCode()
是可能的。如果您 Override 了 hashCode()
,您仍旧可以使用 System.identityHashCode()
方法来接入这类缺省值。
缺省情况下, equals()
和 hashCode()
基于标识的实施是合理的,但对于某些类来说,它们希望放宽等式的定义。例如, Integer
类定义 equals()
与下面类似:
public boolean equals(Object obj) { return (obj instanceof Integer && intValue() == ((Integer) obj).intValue()); } |
在这个定义中,只有在包含相同的整数值的情况下这两个 Integer
对象是相等的。结合将不可修改的 Integer
,这使得使用 Integer
作为 HashMap
中的关键字是切实可行的。这种基于值的Equal方法可以由Java类库中的所有原始封装类使用,如 Integer
、 Float
、Character
和 Boolean
以及 String
(如果两个 String
对象包含相同顺序的字符,那它们是相等的)。由于这些类都是不可修改的并且可以实施 hashCode()
和 equals()
,它们都可以做为很好的散列关键字。
为什么 Override equals()和hashCode()?
如果 Integer
不 Override equals()
和 hashCode()
情况又将如何?如果我们从未在 HashMap
或其它基于散列的集合中使用 Integer
作为关键字的话,什么也不会发生。但是,如果我们在 HashMap中
使用这类 Integer
对象作为关键字,我们将不能够可靠地检索相关的值,除非我们在 get()
调用中使用与 put()
调用中极其类似的 Integer
实例。这要求确保在我们的整个程序中,只能使用对应于特定整数值的 Integer
对象的一个实例。不用说,这种方法极不方便而且错误频频。
Object
的interface contract要求如果根据 equals()
两个对象是相等的,那么它们必须有相同的 hashCode()
值。当其识别能力整个包含在 equals()
中时,为什么我们的根对象类需要 hashCode()
? hashCode()
方法纯粹用于提高效率。Java平台设计人员预计到了典型Java应用程序中基于散列的集合类(Collection Class)的重要性--如 Hashtable
、 HashMap
和 HashSet
,并且使用 equals()
与许多对象进行比较在计算方面非常昂贵。使所有Java对象都能够支持 hashCode()
并结合使用基于散列的集合,可以实现有效的存储和检索。
实施 equals()
和 hashCode()
有一些限制, Object
文件中列举出了这些限制。特别是 equals()
方法必须显示以下属性:
- Symmetry:两个引用,
a
和b
,a.equals(b) if and only if b.equals(a)
- Reflexivity:所有非空引用,
a.equals(a)
- Transitivity:If
a.equals(b)
andb.equals(c)
, thena.equals(c)
- Consistency with
hashCode()
:两个相等的对象必须有相同的hashCode()
值
Object
的规范中并没有明确要求 equals()
和 hashCode()
必须 一致-- 它们的结果在随后的调用中将是相同的,假设“不改变对象相等性比较中使用的任何信息。”这听起来象“计算的结果将不改变,除非实际情况如此。”这一模糊声明通常解释为相等性和散列值计算应是对象的可确定性功能,而不是其它。
人们很容易满足Object类规范对 equals()
和 hashCode()
的要求。决定是否和如何 Override equals()
除了判断以外,还要求其它。在简单的不可修值类中,如 Integer
(事实上是几乎所有不可修改的类),选择相当明显 -- 相等性应基于基本对象状态的相等性。在Integer
情况下,对象的唯一状态是基本的整数值。
对于可修改对象来说,答案并不总是如此清楚。 equals()
和 hashCode()
是否应基于对象的标识(象缺省实施)或对象的状态(象Integer和String)?没有简单的答案 -- 它取决于类的计划使用。对于象 List
和 Map
这样的容器来说,人们对此争论不已。Java类库中的大多数类,包括容器类,错误出现在根据对象状态来提供 equals()
和 hashCode()
实施。
如果对象的 hashCode()
值可以基于其状态进行更改,那么当使用这类对象作为基于散列的集合中的关键字时我们必须注意,确保当它们用于作为散列关键字时,我们并不允许更改它们的状态。所有基于散列的集合假设,当对象的散列值用于作为集合中的关键字时它不会改变。如果当关键字在集合中时它的散列代码被更改,那么将产生一些不可预测和容易混淆的结果。实践过程中这通常不是问题 -- 我们并不经常使用象 List
这样的可修改对象做为 HashMap
中的关键字。
一个简单的可修改类的例子是Point,它根据状态来定义 equals()
和 hashCode()
。如果两个 Point
对象引用相同的 (x, y)
座标,Point
的散列值来源于 x
和 y
座标值的IEEE 754-bit表示,那么它们是相等的。
对于比较复杂的类来说, equals()
和 hashCode()
的行为可能甚至受到superclass或interface的影响。例如, List
接口要求如果并且只有另一个对象是 List,
而且它们有相同顺序的相同的Elements(由Element上的 Object.equals()
定义), List
对象等于另一个对象。 hashCode()
的需求更特殊--list的 hashCode()
值必须符合以下计算:
hashCode = 1; Iterator i = list.iterator(); while (i.hasNext()) { Object obj = i.next(); hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode()); } |
不仅仅散列值取决于list的内容,而且还规定了结合各个Element的散列值的特殊算法。( String
类规定类似的算法用于计算 String
的散列值。)
Override 缺省的 equals()
方法比较简单,但如果不违反对称(Symmetry)或传递性(Transitivity)需求,Override 已经 Override 的equals()
方法极其棘手。当 Override equals()
时,您应该总是在 equals()
中包括一些Javadoc注释,以帮助那些希望能够正确扩展您的类的用户。
作为一个简单的例子,考虑以下类:
class A { final B someNonNullField; C someOtherField; int someNonStateField; } |
我们应如何编写该类的 equals()
的方法?这种方法适用于许多情况:
public boolean equals(Object other) { // Not strictly necessary, but often a good optimization if (this == other) return true; if (!(other instanceof A)) return false; A otherA = (A) other; return (someNonNullField.equals(otherA.someNonNullField)) && ((someOtherField == null) ? otherA.someOtherField == null : someOtherField.equals(otherA.someOtherField))); } |
现在我们定义了 equals()
,我们必须以统一的方法来定义 hashCode()
。一种统一但并不总是有效的定义 hashCode()
的方法如下:
public int hashCode() { return 0; } |
这种方法将生成大量的条目并显著降低 HashMap
s的性能,但它符合规范。一个更合理的 hashCode()
实施应该是这样:
public int hashCode() { int hash = 1; hash = hash * 31 + someNonNullField.hashCode(); hash = hash * 31 + (someOtherField == null ? 0 : someOtherField.hashCode()); return hash; } |
注意:这两种实施都降低了类状态字段的 equals()
或 hashCode()
方法一定比例的计算能力。根据您使用的类,您可能希望降低superclass的 equals()
或 hashCode()
功能一部分计算能力。对于原始字段来说,在相关的封装类中有helper功能,可以帮助创建散列值,如 Float.floatToIntBits
。
编写一个完美的 equals()
方法是不现实的。通常,当扩展一个自身 Override 了 equals()
的instantiable类时,Override equals()
是不切实际的,而且编写将被 Override 的 equals()
方法(如在抽象类中)不同于为具体类编写 equals()
方法。关于实例以及说明的更详细信息请参阅 Effective Java Programming Language Guide, Item 7 ( 参考资料) 。
将散列法构建到Java类库的根对象类中是一种非常明智的设计折衷方法 -- 它使使用基于散列的容器变得如此简单和高效。但是,人们对Java类库中的散列算法和对象相等性的方法和实施提出了许多批评。 java.util
中基于散列的容器非常方便和简便易用,但可能不适用于需要非常高性能的应用程序。虽然其中大部分将不会改变,但当您设计严重依赖于基于散列的容器效率的应用程序时必须考虑这些因素,它们包括:
- 太小的散列范围。使用
int
而不是long
作为hashCode()
的返回类型增加了散列冲突的几率。 - 糟糕的散列值分配。短strings和小型integers的散列值是它们自己的小整数,接近于其它“邻近”对象的散列值。一个循规导矩(Well-behaved)的散列函数将在该散列范围内更均匀地分配散列值。
- 无定义的散列操作。虽然某些类,如
String
和List
,定义了将其Element的散列值结合到一个散列值中使用的散列算法,但语言规范不定义将多个对象的散列值结合到新散列值中的任何批准的方法。我们在前面 编写自己的equals()和hashCode()方法中讨论的List
、String
或实例类A
使用的诀窍都很简单,但算术上还远远不够完美。类库不提供任何散列算法的方便实施,它可以简化更先进的hashCode()
实施的创建。 - 当扩展已经 Override 了
equals()
的 instantiable类时很难编写equals()
。当扩展已经 Override 了equals()
的 instantiable类时,定义equals()
的“显而易见的”方式都不能满足equals()
方法的对称或传递性需求。这意味着当 Overrideequals()
时,您必须了解您正在扩展的类的结构和实施详细信息,甚至需要暴露基本类中的机密字段,它违反了面向对象的设计的原则。
通过统一定义 equals()
和 hashCode(),
您可以提升类作为基于散列的集合中的关键字的使用性。有两种方法来定义对象的相等性和散列值:基于标识,它是 Object
提供的缺省方法;基于状态,它要求 Override equals()
和 hashCode()
。当对象的状态更改时如果对象的散列值发生变化,确信当状态作为散列关键字使用时您不允许更更改其状态。
- 您可以参阅本文在 developerWorks 全球站点上的 英文原文.
- 参加本文的 讨论论坛。(您还可以点击本文顶部或底部的 讨论进入论坛。)
- 阅读Brian Goetz撰写的一整套 Java理论和实践文章 。尤其是2003年2月“ Java 理论与实践:变还是不变?,”它讨论使用可变对象作为散列关键字的危害。
- Joshua Bloch杰作的第 7和 8部分 Java编程语言指南 ,详细阐述围绕
equals()
和hashCode()
的问题。
- Tony Sintes在 JavaWorld提供的本文中解释 基于散列的容器是如何工作的以及如何使用
equals()
和hashCode()
(2002年7月)。
- 在幻灯片中,新西兰奥克兰大学计算机科学系的Robert Uzgalis介绍一些 Java散列模式的批评意见,解释一些散列函数背后的问题。
- Mark Roulo 在自己的文章“如何避免陷阱和正确 Override java.lang.Object的方法”( JavaWorld, 1999年1月)一文中提供了一些Override
equals()
和hashCode()
的实例程序代码 。
- 新西兰坎特伯雷大学计算机科学系提供的这一份技术报告详细描述了 what makes an effective hash function(PDF)。
- IBM软件实验室软件工程师Sreekanth Iyer 探讨了Java对象相等性的各种不同意义( developerWorks, 2002年9月)。
-
JavaWorld(获得许可后才可再版)的提示略微谈到 相等性比较的缺陷。
- 在 developerWorksJava技术专区 可以找到数百篇有关 Java 技术的参考资料
相关推荐
在Java编程语言中,`hashCode()`和`equals()`方法是对象身份验证的关键组成部分,它们主要用于对象的比较和哈希表(如HashMap、HashSet等)的操作。理解这两个方法的工作原理对于编写高效和可靠的代码至关重要。 ...
在Java编程中,`hashCode()`、`equals()`以及`==`是三个经常被提及的概念,它们在处理对象的比较和存储时起着关键作用。本文将深入探讨这三个概念的介绍、区别以及它们在Java对象比较中的应用。 首先,`hashCode()`...
Java 中HashCode重复的可能性 Java 中的 HashCode 重复可能性是 Java 开发中一个常见的问题。 HashCode 是 Java 中一个重要的概念,它用于标识对象的唯一性。然而,Hash Code 有可能重复,这会导致程序出错。下面...
1. **Java中的hashCode方法**:在Java中,对象的hashCode方法用于获取该对象的哈希码值,该值通常基于对象的内容计算得出。hashCode的目的是为了在使用如HashMap等哈希表数据结构时提高效率。Java中String类的...
Java 中的每个对象都有 hashCode() 和 equals() 方法,这两个方法的正确实现对于 Java 开发人员来说是非常重要的。本文将详细介绍 hashCode() 和 equals() 的本质区别和联系,并探讨在创建 Java 类时如何定义这些...
Java中的`hashCode()`方法是基于对象的内部状态计算出的一个整数值,它在处理集合特别是HashSet、HashMap等基于哈希表的数据结构时起着至关重要的作用。哈希码(Hash Code)被用来快速定位对象在哈希表中的位置,...
在Java编程语言中,`hashCode()` 和 `equals()` 方法是两个非常重要的概念,尤其是在处理集合类如 `List`, `Set`, `Map` 时。它们主要用于优化存储和查找效率,尤其是当涉及到哈希表(如 `HashMap` 和 `HashSet`)时...
在Java应用程序执行期间,在同一对象上多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是对象上equals比较中所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需...
"Java中Hashcode的作用" Hashcode是Java编程语言中一个非常重要的概念,它在equals方法中扮演着关键角色。在Java中,每个对象都具有一个独特的Hashcode,它可以用来标识对象的身份。但是Hashcode是什么?它是如何...
在Java编程语言中,`hashCode()`方法是`Object`类的一个重要成员,它用于生成对象的哈希码,这个哈希码是一个整数,通常用于优化基于哈希的集合操作,如`HashSet`、`HashMap`和`HashTable`。这些集合依赖于`hashCode...
Java重写equals同时需要重写hashCode的代码说明,以及如何重写hashCode方法,此代码演示按照effective java书籍说明的重写思路。代码中演示了使用集合存储对象,并且对象作为key,需重写equals和hashCode.
在Java编程语言中,`equals()` 和 `hashCode()` 方法是对象的基本组成部分,它们在很多场景下都发挥着至关重要的作用。这两个方法与对象的相等性比较和哈希表(如HashMap、HashSet)的运作紧密相关。这篇博客将深入...
在讲解数组时(java提高篇(十八)——数组),我们提到数组是java中效率高的数据结构,但是“高”是有前提的。第一我们需要知道所查询数据的所在位置。第二:如果我们进行迭代查找时,数据量一定要小,对于大数据量...
安装npm install string-hashcode 例子var hashCode = require ( 'string-hashcode' ) ;var s = 'abc' ;console . log ( s . hashCode ) ; // undefinedvar code = hashCode ( s ) ;console . log ( s . hashCode ) ...
Hashcode是一个关键的概念,它是Java中的一个方法,用于将对象映射到一个整数值,通常用于哈希表的实现。每个对象都有一个唯一的哈希码,除非两个对象完全相等(equals()返回true),否则它们的哈希码必须不同。如果...
### Java 高级特性详解 #### 一、`hashCode` ...正确地重写 `equals` 和 `hashCode` 方法、使用 `Comparator` 进行排序、利用反射机制和序列化技术,以及实现 `clone` 方法都是开发高质量 Java 应用程序的重要技能。
Java 中的 hashCode 和 equals 方法详解 本文详细介绍了 Java 中的 hashCode 和 equals 方法,探讨了这两个方法的作用、实现机制和使用场景。通过对 hashCode 和 equals 方法的深入分析,我们可以更好地理解 Java ...
* Java 中什么是 hashCode 和 equals 方法?hashCode 方法用于返回对象的哈希码,equals 方法用于比较两个对象是否相等。 50. Java 接口继承 * Java 中什么是接口继承?接口继承是一种机制,用于继承多个接口。 ...