黑马程序员-Java基础之Set接口及HashSet类

前面已经介绍过Set集合,它类似与一个罐子,一旦把对象“丢进”Set集合中,集合里多个对象之间没有明显的顺序。Set集合和Collection基本上完全一样,没有提供任何额外的方法。实际上Set就是Collection,只是行为不同(Set不允许包含重复元素)。

Set集合不允许包含相同的元素,如果试图把两个相同的元素添加到同一个Set集合中时,添加操作失败,add方法返回false,则新元素不会被添加。

Set集合判断两个元素相同不是使用==运算符,而是根据equals方法。也就是说,如果两个对象用equals方法比较返回trueSet就不会接受这两个对象,反之则可以接受两个对象(甚至这两对象是同一个对象,Set也可把它们当成两个对象来处理,后面程序可以看到这种极端的情况)。如下是使用普通Set的示例程序代码:

 1 package com.king.testcollection;
 2 
 3  
 4 
 5 import java.util.HashSet;
 6 
 7 import java.util.Set;
 8 
 9  
10 
11 public class TestSet {
12 
13  
14 
15 /**
16 
17  * @author 王者黑桃
18 
19  */
20 
21 public static void main(String[] args) {
22 
23 // 定义一个Set集合
24 
25 Set names=new HashSet();
26 
27 //添加一个字符串对象
28 
29 names.add(new String("王者黑桃"));
30 
31 //再次添加一个字符串对象
32 
33 //因为两次添加的字符串对象通过equals方法比较相等,所以添加失败,返回false
34 
35 boolean result=names.add(new String("王者黑桃"));
36 
37 System.out.println(result);
38 
39 //输出将看到集合中只有一个元素
40 
41 System.out.println(names);
42 
43  
44 
45 }
46 
47  
48 
49 }

 

    从上面程序中可以看出,names集合两次添加的字符串对象明显不是同一个对象(因为两次都调用了new关键字来创建字符串对象),这两个字符串对象通过==运算符判断肯定返回false,但它们通过equals方法判断返回true,所以添加失败。最后输出names集合将看到一个元素。

上面介绍的是Set集合的通用知识,因此完全适用与后面介绍的HashSetTreeSetEnumSet三个实现类,只是三个实现类还各有特色。

HashSet

HashSet类是Set接口的典型实现,大多数时候使用Set集合时就是使用这三个实现类。HashSetHash的算法来存储集合中的元素,因此具有很好的存取和查找性能。

HashSet具有以下特点:

1.不能保证元素的排列顺序,顺序有可能发生变化。

2.HashSet不是同步的,如果多个线程同时访问一个Set集合,如果多个线程同时访问一个HashSet,如果有2个或2个以上线程同时修改了HashSet集合时,必须通过代码保证其同步。

3.集合元素值可以是NULL

 当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来获取该对象的hashCode值,然后根据hashCode值来决定该对象在HashSet集合中的存储位置。如果两个元素通过equals方法比较返回true,但是它们的hashCode()方法返回值不相等,HashSet将会把它们存放在不同的位置,也就可以添加成功。

简单的说,HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较想等,并且两个对象的hashCode()方法的返回值也相等。

下面程序分别提供了三个类,它们分别重写了equalshashCode两个方法的一个或全部,通过此程序可以更清楚的了解HashSet集合判断两个元素想等的标准。

实例代码:

 1 package com.king.testcollection;
 2 
 3  
 4 
 5 import java.util.HashSet;
 6 
 7  
 8 
 9 /**
10 
11  * @author 王者黑桃
12 
13  */
14 
15 //类A的equals方法总是返回true,但并没有重写其hashCode()方法
16 
17 class A{
18 
19 public boolean equals(Object obj){
20 
21 return true;
22 
23 }
24 
25 }
26 
27 //类B的hashCode()方法总是返回1,但并没有重写其equals方法
28 
29 class B{
30 
31 public int hashCode(){
32 
33 return 1;
34 
35 }
36 
37 }
38 
39 //类B的hashCode()方法总是返回2,equals方法的返回值true
40 
41 class C{
42 
43 public int hashCode(){
44 
45 return 2;
46 
47 }
48 
49 public boolean equals(Object obj){
50 
51 return true;
52 
53 }
54 
55 }
56 
57 public class TestHashSet {
58 
59  
60 
61  
62 
63 public static void main(String[] args) {
64 
65 HashSet names=new HashSet();
66 
67 // 分别向names集合中添加三个类的两个对象
68 
69 names.add(new A());
70 
71 names.add(new A());
72 
73 names.add(new B());
74 
75 names.add(new B());
76 
77 names.add(new C());
78 
79 names.add(new C());
80 
81 System.out.println(names);
82 
83  
84 
85 }
86 
87  
88 
89 }

 

上面程序中names集合中分别添加了2A对象、2B对象和2C对象,其中C类重写的equals()方法总是返回truehashCode()总是返回2,这将导致HashSet将会把2C对象当成同一个对象。运行程序将看到如下结果:

[com.king.testcollection.B@1, com.king.testcollection.B@1, com.king.testcollection.C@2, com.king.testcollection.A@6d4b473, com.king.testcollection.A@456d3d51]

这里有个问题需要注意:如果需要把一个对象放入HashSet集合中时,如果重写了equals()方法时,也应该重写其hashCode()方法,其规则是:如果两个对象通过equals()方法比较返回true时,这两个hashCode也应该相同。

如果两个对象通过quals()方法比较返回true,但这两个对象的hashCode()方法返回不同的hashCode时,这将导致HashSet会把这两个对象保存在HashSet的不同位置,从而这两个对象都将添加成功,这与Set集合的规则有点出入。

如果两个对象的hashCode()方法返回的hashCode相同,但它们通过equals()方法比较返回False时,这将更麻烦:因为两个对象的hashCode值相同,HashSet将试图把它们保存在同一位置,但实际上又不行(否则将只剩下一个对象),所以处理起来比较麻烦;而且HashSet访问集合元素时也是根据元素的hashCode值来访问的,如果HashSet中包含两个元素有相同的hashCode值,将导致性能下降。

HashSet中每个能存储元素的“槽位”通常别称为“桶”,如果有多个hashCode的值相同,但它们通过equals()方法比较返回False,就需要在一个“桶”里放多个元素,从而导致性能下降。

重写hashCode()方法的基本规则:

1.当两个对象通过equals()方法比较返回true时,这两个对象的hashCode也应该相等。

2.对象中用作equals比较标准的属性,都应该用来计算hashCode值。

 HashSet还有一个子类LinkedHashSetLinkedHashSet集合也是根据元素hashCode值来决定元素的存储位置,但他同时使用链表维护元素的次序,这样使的元素看起来是以插入的顺序保存的。也就是说当遍历LinkedHashSet集合里元素时,HashSet将会按元素的添加顺序来访问集合里的元素。

 

posted @ 2014-01-07 10:43  code4a  阅读(176)  评论(0编辑  收藏  举报