hashCode方法的作用:
总的来说,Java中的集合(Collection)有两类,一类是List,再有一类是Set。
你知道它们的区别吗?前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。
那么这里就有一个比较严重的问题了:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?
这就是Object.equals方法了。但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。
也就是说,如果集合中现在已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。
于是,Java采用了哈希表的原理。哈希(Hash)实际上是个人名,由于他提出一哈希算法的概念,所以就以他的名字命名了。
哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。如果详细讲解哈希算法,那需要更多的文章篇幅,我在这里就不介绍了。
初学者可以这样理解,hashCode方法实际上返回的就是对象存储的物理地址(实际可能并不是)。
这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。
如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,
就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。
所以这里存在一个冲突解决的问题。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。
每个Java对象都有 hashCode()
和 equals()
方法,因为这两个方法是在Object类里面定义的。
Object
类有两种方法来推断对象的标识: equals()
和 hashCode()
。一般来说,如果您 Override 了其中一种,您必须同时 Override 这两种,因为两者之间有必须维持的至关重要的关系。特殊情况是根据 equals()
方法,如果两个对象是相等的,它们必须有相同的hashCode()
值(尽管这通常不是真的)。
特定类的 equals()
的语义在Implementer的左侧定义;定义对特定类来说 equals()
意味着什么是其设计工作的一部分。 Object
提供的缺省实施简单引用下面等式:
|
在这种缺省实施情况下,只有它们引用真正同一个对象时这两个引用才是相等的。同样, Object
提供的 hashCode()
的缺省实施通过将对象的内存地址对映于一个整数值来生成。由于在某些架构上,地址空间大于 int
值的范围,两个不同的对象有相同的 hashCode()
是可能的。如果您 Override 了 hashCode()
,您仍旧可以使用 System.identityHashCode()
方法来接入这类缺省值。
为什么 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()
值必须符合以下计算:
|
不仅仅散列值取决于list的内容,而且还规定了结合各个Element的散列值的特殊算法。( String
类规定类似的算法用于计算 String
的散列值。)
Override 缺省的 equals()
方法比较简单,但如果不违反对称(Symmetry)或传递性(Transitivity)需求,Override 已经 Override 的equals()
方法极其棘手。当 Override equals()
时,您应该总是在 equals()
中包括一些Javadoc注释,以帮助那些希望能够正确扩展您的类的用户。
作为一个简单的例子,考虑以下类:
|
我们应如何编写该类的 equals()
的方法?这种方法适用于许多情况:
|
现在我们定义了 equals()
,我们必须以统一的方法来定义 hashCode()
。一种统一但并不总是有效的定义 hashCode()
的方法如下:
|
这种方法将生成大量的条目并显著降低 HashMap
s的性能,但它符合规范。一个更合理的 hashCode()
实施应该是这样:
|
注意:这两种实施都降低了类状态字段的 equals()
或 hashCode()
方法一定比例的计算能力。根据您使用的类,您可能希望降低superclass的 equals()
或 hashCode()
功能一部分计算能力。对于原始字段来说,在相关的封装类中有helper功能,可以帮助创建散列值,如 Float.floatToIntBits
。
编写一个完美的 equals()
方法是不现实的。通常,当扩展一个自身 Override 了 equals()
的instantiable类时,Override equals()
是不切实际的,而且编写将被 Override 的 equals()
方法(如在抽象类中)不同于为具体类编写 equals()
方法。关于实例以及说明的更详细信息请参阅 Effective Java Programming Language Guide, Item 7 ( 参考资料) 。