目录
1 前言
java中==,equals(),hashcode()三种方法都与对象比较有关,本文将分别探究三种方法各自的功能与特点,同时重点讨论equals()与hashcode()两种方法的联系与区别。
2 对象比较的方法
2.1 ==
java有两种等价性比较,分别是引用等价性和对象等价性。==在java中进行的比较是引用等价性,即比较的是两个对象在内存中的引用地址是否相同。
对于基本数据类型,==比较的是它们的值是否相等;
对于对象类型,==比较的是两个对象在内存中的引用地址是否相等(即是否指向内存里的同一段空间)。
2.2 equals()
与==不同,equals()在java中进行的比较是对象等价性,即对于对象类型,判断它们的值是否相等。但需要注意的一点是,在自定义ADT时,需要重写Object的equals() !
这是因为equals()方法是被Object定义的,且它的默认执行是:
public class Object {
...
public boolean equals(Object that){
return this == that;
}
}
因此在Object中实现的默认equals()是在判断引用等价性,所以我们需要重写equals()方法(对于封装类如String、Integer等,它们的equals方法是已经被重写了的)。
同时,当重写equals()方法时,必须要遵照它的通用约定:
1. equals必须定义一个等价关系(自反,传递,对称)
2. 除非对象被修改了,否则调用多次equals应是同样的结果
3. 对于一个非空的对象x,x.equals(null)应该返回false
4. “相等”的对象,其hashCode()的结果必须一致
2.3 hashCode()
2.3.1 Hash Tables
首先我们要先知道Java中的集合。总的来说,Java中的集合(Collection)有两类,一类是List,再有一类是Set。前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。
而我们保证元素不重复的方法就是Object.equals方法。但是如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。这显然会大大降低效率。于是,Java采用了 Hash Tables 的原理。
我们介绍一下 Hash Tables 的概念:一个hash table就是一种映射keys到values的抽象数据型。对于keys和values,我们需要计算keys的hashcode,并将它转换为一个数组范围内的索引,最后values就插入在那个索引处。
因此,当集合需要添加新的元素时,通过hashcode方法,我们可以返回一个地址索引值:
1. 当那个索引处没有元素,则直接存入;
2. 当那个索引处有元素,则调用equals方法,若两个元素相等,则无需再存;否则就再寻找其他地址。
通过这样,我们可大大提高性能。
2.3.2 hashCode()方法
hashCode()方法在官方文档的定义如下:
public native int hashCode();
即返回一个int型数据,在Object类中的默认实现是“将该对象的内部地址转换成一个整数返回”。
3 equals与hashCode关系探究
3.1 equals与hashCode
首先查阅官方文档中 hashCode 的常规协定:
1. 在 Java 应用程序执行期间,在同一对象上多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是对象上 equals 比较中所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
2. 如果根据 equals(Object) 方法,两个对象是相等的,那么在两个对象中的每个对象上调用 hashCode 方法都必须生成相同的整数结果。
3. 以下情况不 是必需的:如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么在两个对象中的任一对象上调用 hashCode 方法必定会生成不同的整数结果。但是,程序员应该知道,为不相等的对象生成不同整数结果可以提高哈希表的性能。
4. 实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。)
由此,我们可以总结:
1. equals判定等价的对象的hashCode必须相同;
2. equals判定不相等的对象也可以映射为相同的hashCode,但性能会变差。
3.2 重写equals方法需要重写hashCode吗?
根据官方文档:
当equals方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。
同时,Object的默认hashCode()的执行与默认的equals()的执行是一致的。
public class Object{
...
public boolean equals(Object that) { return this == that; }
public int hashCode() { return /*the memory address of this */ ; }
}
因此,对于类的对象存储在HashTable,HashSet,HashMap等散列存储结构的情况,那么重写equals后必须也重写hashCode,否则会导致存储数据的不唯一性(存储了两个equals相等的数据)。
最后,我们可以总结:当你重写了equals方法后,最好也重写hashCode方法,除非你能保证你的ADT不会被放在Hash类型的集合类中。