首页 百科知识Java语言equals和hashCode方法源代码的研究

Java语言equals和hashCode方法源代码的研究

【摘要】:深入浅出地讲解清楚集合中用到的equals方法和hashCode方法,让程序员在使用到集合的类时得心应手。在所有构造方法以及add、equals和hashCode方法的基础上,Set接口还加入了其他规定,这些规定超出了从Collection接口所继承的内容。

java语言中equals方法和hashCode方法源代码的研究

王善发 吴道勇

摘 要: java程序设计中,集合是很主要的一部分,文章深入Java Web编程中经常用到的API中的集合类的源代码,针对集合元素加载时不能有重复对象这一特性进行研究。深入浅出地讲解清楚集合中用到的equals方法和hashCode方法,让程序员在使用到集合的类时得心应手。

关键词: java Web 编程 对 象equals方法 hashCode 方法

一、引 言

在java中,Collection是集合类层次结构中的根接口。Collection表示一组对象,这些对象也称为Collection的元素。一些Collection允许有重复的元素,而另一些则不允许。一些Collection是有序的,而另一些则是无序的。JDK不提供此接口的任何直接实现,它提供更具体的子接口(如Set和List)实现。此接口通常用来传递Collection,并在需要最大普遍性的地方操作这些Collection。

Set接口是一个不包含重复元素的Collection。更确切地讲,Set不包含满足e1.equals(e2)的元素对e1和e2,并且最多包含一个null元素。正如其名称所暗示的,此接口模仿了数学上的Set抽象。

在所有构造方法以及add、equals和hashCode方法的基础上,Set接口还加入了其他规定,这些规定超出了从Collection接口所继承的内容。出于方便考虑,它还包括了其他继承方法的声明(这些声明的规范已经专门针对Set接口进行了修改)。对这些构造方法的其他规定是:所有构造方法必须创建一个不包含重复元素的Set。

Set接口是Java Collections Framework的成员。

二、在使用HashSet类时出现的一些问题

HashSet类是Set接口的一个实现类,该类由哈希表(实际上是一个HashMap实例)支持。它不保证Set的迭代顺序,特别是它不保证该顺序恒久不变。该类允许使用null元素。该类不能加载重复的对象,但是,有时该源代码对一些特殊问题的处理不能满足编程人员的要求,必须要编程人员自己根据实际情况去处理。

下面是HashSet集合的一个测试程序(Test. java):

importjava.util.*;

public class Test

{

public static void main(String[]args)

{

HashSet set1= new HashSet(  );

HashSet set2= new HashSet(  );

HashSet set3= new HashSet(  );

HashSet set4= new HashSet(  );

set1.add(new People("zhangsan"));

set1.add(new People("zhangsan"));

People p1= new People("lisi");

set2.add(p1);

set2.add(p1);

String str1= new String("wangwu");

String str2= new String("wangwu");

set3.add(str1);

set3.add(str2);

set4.add("Hello");

set4.add("Hello");

for(Iteratoriter= set1.iterator(  ); iter.hasNext(  );)

{

System.out.println("set1:"+((People) iter.next

(  )).name);

}

for(Iterator iter= set2. iterator(); iter.hasNext(  );)

{

System.out.println("set2:"+((People) iter.next

(  )).name);

}

for(Iterator iter= set3. iterator(); iter.hasNext(  );)

{

System.out.println("set3:"+(String)iter.next());

}

for(Iterator iter= set4. iterator(); iter.hasNext(  );)

{

System.out.println("set4:"+(String)iter.next(  ));

}

}

}

class People

{

public String name;

public People(String name)

{

this.name= name;

}

}

运行结果:

set1: zhangsan

set1: zhangsan

set2: lisi

set3: wangwu

set4: Hello

在上述程序运行结果中,用相同名字zhangsan为参数实例化的两个People对象被HashSet集合的对象set1调用add()方法两次,就被加载了两次。而用名字lisi为参数实例化的一个People对象被HashSet集合的对象set2调用add()方法两次,却只被加载了一次。用名字wangwu为参数实例化的两个String对象被HashSet集合的对象set3调用add()方法两次,也只被加载了一次。字面常量“Hello”被HashSet集合的对象set4调用add()方法两次,也只被加载了一次。

三、对使用Hash Set类出现问题的分析(www.chuimin.cn)

HashSet类从java. lang.Object类中继承了equals方法和hashCode方法。

(1)在java.util.Hash Set中add方法的特点如下:

publicboolean add(Ee)方法规定,如果此Set中尚未包含指定元素,则添加指定元素。更确切地讲,如果此Set没有包含满足(e== null? e2== null: e.equals(e2))的元素e2,则向此Set添加指定的元素e。如果此Set已包含该元素,则该调用不更改Set并返回false。

(2)在java. lang.Object中equals方法的特点如下:

publicboolean equals(Object obj)指示其他某个对象是否与此对象“相等”。

equals方法在非空对象引用上实现相等关系。

自反性:对于任何非空引用值x,x.equals(x)都应返回true。

对称性:对于任何非空引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)才应返回true。

传递性:对于任何非空引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)返回true,那么x.equals(z)应返回true。

一致性:对于任何非空引用值x和y,多次调用x.equals(y)始终返回true或始终返回false,前提是对象上equals比较中所用的信息没有被修改。

对于任何非空引用值x,x.equals(null)都应返回false。

Object类的equals方法实现对象上差别可能性最大的相等关系,即,对于任何非空引用值x和y,当且仅当x和y引用同一个对象时,此方法才返回true(x== y具有值true)。

注意:当此方法被重写时,通常有必要重写hashCode方法,以维护hashCode方法的常规协定,该协定声明相等对象必须具有相等的哈希码。

(3)在java. lang.Object中hashCode方法的特点如下:

publicint hashCode()返回该对象的哈希码值。支持此方法是为了提高哈希表(例如java.util.Hashtable提供的哈希表)的性能。

hashCode的常规协定是:

在java应用程序执行期间,在对同一对象多次调用hash-Code方法时,必须一致地返回相同的整数,前提是将对象进行equals比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无须保持一致。

如果根据equals(Object)方法,两个对象是相等的,那么对这两个对象中的每个对象调用hashCode方法都必须生成相同的整数结果。

如果根据equals(java. lang.Object)方法,两个对象不相等,那么对这两个对象中的任一对象上调用hashCode方法不要求一定生成不同的整数结果。为不相等的对象生成不同整数结果可以提高哈希表的性能。

实际上,由Object类定义的hashCode方法确实会针对不同的对象返回不同的整数(这一般是通过将该对象的内部地址转换成一个整数来实现的)。

(4)当使用HashSet时,hashCode()方法就会得到调用,判断已经存储在集合中的对象的hash code值是否与增加的对象的hash code值一致。如果不一致,直接加进去;如果一致,再进行equals方法的比较,equals方法如果返回true,表示对象已经加进去了,就不会再增加新的对象,否则要再加进去。

(5)如果我们重写equals方法,那么也要重写hashCode方法,反之亦然。

从上面的分析就不难理解在Test. java程序中产生的结果。

由于参数名字相同的两个zhangsan产生了两个People对象,对象的地址不同。People继承了Object的equals方法和hashCode方法,equals方法比较的结果为false,hashCode方法返回的hash code不相同,所以被加载了两次。

由lisi为参数产生的一个People对象,对象的地址只有一个。People继承了Object的equals方法和hashCode方法,equals方法比较的结果为true,hashCode方法返回的hash code相同,所以只被加载了一次。

由于参数名字相同的两个wangwu被new了两个对象。虽然对象的地址不同,但是,由于String类已经重写了equals方法和hashCode方法,它们的hashCode方法返回的hash code相同,它们的equals比较结果是true,所以只被加载了一次。

由于Hello是用字面常量产生的对象,所以两个Hello只在string pool中产生一个字符串对象,由于String类已经重写了equals方法和hashCode方法,它们的hashCode方法返回的hash code相同,它们的equals比较结果是true,所以只被加载了一次。

四、对使用HashSet类出现特殊问题的处理

要想在Test. java程序中把People对象加载到HashSet集合中时,相同名字的不同对象只能加载一次,就要在People类重写equals方法和hashCode方法,让People类重写的equals方法和hashCode方法覆盖java. lang.Object类的equals方法和hashCode方法。

为了实现程序员的要求,改写后的People类代码如下:

class People

{

String name;

public People(String name)

{

this.name= name;

}

publicint hashCode()

{

return this.name.hashCode();

}

publicboolean equals(Object obj)

{

if(this== obj)

{

return true;

}

if(null!= obj&&obj instance of Student)

{

Student s=(Student) obj;

if(name.equals(s.name))

{

return true;

}

}

return false;

}

}

五、结束语

文章从HashSet类的元素加载(add方法)入手,追溯到java.util.AbstractSet类中继承了equals方法和hashCode方法、java. lang.Object类的equals方法和hashCode方法,直到程序员在自己编写的People类中重写equals方法和hashCode方法,理清了思路,解决了问题。

参考文献:

[1]John LewisWilliam Loftus. Java程序设计教程[M].北京:电子工业出版社,2009.

[2]李芝兴,杨瑞龙. Java程序设计之网络编程:第2版[M].北京:清华大学出版社,2009.

[3]林信良. JDK 6 Java学习笔记[M].北京:清华大学出版社,2007.

[4]阎宏. Java与模式[M].北京:电子工业出版社,2002.

[5]孙萍,迟耀丹. Java中Object类的方法应用[J].吉林建筑工程学院学报,2007(9).

[6]庞家乐,王兴斌,胡成玉. Java编程常见问题若干例[J].武汉科技学院学报,2004(8).