Java——(三)Collection之Set集合、HashSet类
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
一、Set集合
Set集合不允许包含相同的元素,如果试图把两个相同的元素加入同一个Set集合中,则添加
操作失败,add方法返回false,而新元素不会被加入。
Set判断两对象相同不是使用==运算符,而是根据equals方法。也就是说,只要两个对象用
equals方法比较返回true,Ser就不会接受这两个对象;反之,只要两个对象用equals方法比较
返回false,SEt就会接受这两个对象(甚至这两个对象是同一个对象,Set也可把它们当成两个对
象出来)。下面是使用普通Set的示例程序。
1 import java.util.HashSet; 2 import java.util.Set; 3 4 5 public class SetTest { 6 7 public static void main(String[] args) { 8 Set names = new HashSet<>(); 9 //添加一个字符串对象 10 names.add(new String("暨雪")); 11 //再次添加一个字符串对象 12 //因为两个字符串对象通过equals方法比较相等 13 //所以添加失败,返回false 14 boolean reasult = names.add(new String("暨雪")); 15 //从下面输出看到集合只有一个元素 16 System.out.println(reasult + "-->" + names); 17 } 18 19 }
运行结果:
false-->[暨雪]
从上面程序中可以看出,names集合两次添加的字符串对象明显不是同一对象(因为两次都调
用了new关键字来创建字符串对象),这两个字符串对象使用==运算符判断肯定返回false,但通过
equals方法比较将返回true,所以添加失败。最后结果输出只有一个元素。
1.HashSet类
HashSet是Set接口的典型实现,大多数时候使用Set集合是就是使用这个实现类。HashSet按Hash
算法来存储集合中的元素,因此具有很好的存取和查找性能。
HashSet具有以下特点:
1)不能保证元素的排序顺序,顺序有可能发生变化。
2)HashSet不是同步的,如果多线程同时访问一个HashSet,假设有两个或两个以上线程同时
修改了HashSet集合时,则必须通过代码来保证其同步。
3)集合元素值可以是null。
HashSet集合判断两个以上相等的标准是两个对象通过equals()方法比较相等,并且两个对象的
hashCode()方法返回值也相等。
下面程序分别提供了三个类A、B和C,它们分别重写了equals()、hashCode()两个方法的一个或全部
,通过此程序可以看到HashSet判断集合元素相同的标准。
1 import java.util.HashSet; 2 3 4 public class HashSetTest { 5 //类A的equals方法总是返回true,但没有重写hashCode方法 6 static class A { 7 @Override 8 public boolean equals(Object o) { 9 return true; 10 } 11 } 12 //类B的hashCode总是返回1,但没有重写equals方法 13 static class B { 14 @Override 15 public int hashCode() { 16 return 1; 17 } 18 } 19 //类C重写了hashCode方法和equals方法 20 static class C { 21 @Override 22 public int hashCode() { 23 return 2; 24 } 25 @Override 26 public boolean equals(Object obj) { 27 return true; 28 } 29 } 30 31 public static void main(String[] args) { 32 33 HashSet names = new HashSet<>(); 34 names.add(new A()); 35 names.add(new A()); 36 names.add(new B()); 37 names.add(new B()); 38 names.add(new C()); 39 names.add(new C()); 40 41 System.out.println(names); 42 } 43 44 }
运行结果:
[HashSetTest$B@1, HashSetTest$B@1, HashSetTest$C@2, HashSetTest$A@7e5284e9, HashSetTest$A@12720f6c]
从上面程序可以看出,即使两个A对象通过equals()方法比较返回true,但HashSet依然把它们当成
两个对象;即使两个B对象的hashCode()返回相同值(都是1),但HashSet依然把它们当成同一个
对象。
注意:
当把一个对象放入HashSet中时,如果需要重写该方法对应类的equals()方法,则也应该重写
hashCode()方法。其规则是:如果两个对象通过equals()方法比较返回true,这两个对象的hashCode
()值也应该相同。
重写hashCode()方法的基本规则:
1)在程序运行中,同一对象多次调用hashCode()方法应该返回相同的值。
2)当两个对象通过equals()方法比较返回true时,这两个对象的hashCode()方法应返回相等的值。
3)对象中用作equals()方法比较标准的Filed,都应该用计算hashCode值。
hashCode返回值的计算:
return f1.hashCode() + (int) f2;
为了避免直接相加产生偶然相等(两个对象f1、f2Field并不相等,但他们的和恰好相等),
可以通过为个Field乘以任意一个质数后在相加。
return f1.hashCode() * 17 + (int) f2 * 13;
如果向HashSet中添加一个可变对象后,后面出现修改了该可变对象的Field,则可能导致
它与集合中的其他元素相同(即两个对象通过equals()方法比较返回true,两个对象的
hashCode()值也相等)。这就有可能导致HashSet中包含两个相同的对象。下面程序演示了这
种情况。
1 import java.util.HashSet; 2 import java.util.Iterator; 3 4 class R { 5 int count; 6 public R (int count){ 7 this.count = count; 8 } 9 @Override 10 public String toString() { 11 12 return "R[count:" + count + "]"; 13 } 14 @Override 15 public boolean equals(Object obj) { 16 17 if (this == obj) { 18 return true; 19 } 20 if (obj != null && obj.getClass() == R.class) { 21 R r = (R) obj; 22 if (r.count == this.count) { 23 return true; 24 } 25 } 26 return false; 27 } 28 @Override 29 public int hashCode() { 30 31 return this.count; 32 } 33 } 34 public class HashSetTest2 { 35 36 public static void main(String[] args) { 37 38 HashSet hs = new HashSet<>(); 39 hs.add(new R(5)); 40 hs.add(new R(-3)); 41 hs.add(new R(9)); 42 hs.add(new R(-2)); 43 //打印HashSet集合,集合没有重复 44 System.out.println(hs); 45 //取出第一个元素 46 Iterator it = hs.iterator(); 47 R first = (R) it.next(); 48 //为第一个元素的count实例变量赋值 49 first.count = -3; 50 //再次输出HashSet集合,集合元素有重复元素 51 System.out.println(hs); 52 //删除count为-3的R对象 53 hs.remove(new R(-3)); 54 System.out.println(hs); 55 System.out.println("hs是否包count为-3的R对象?" + hs.contains(new R(-3))); 56 System.out.println("hs是否包count为5的R对象?" + hs.contains(new R(5))); 57 System.out.println("hs是否包count为9的R对象?" + hs.contains(new R(9))); 58 } 59 60 }
运行结果:
[R[count:5], R[count:9], R[count:-3], R[count:-2]] [R[count:-3], R[count:9], R[count:-3], R[count:-2]] [R[count:-3], R[count:9], R[count:-2]] hs是否包count为-3的R对象?false hs是否包count为5的R对象?false hs是否包count为9的R对象?true
上面程序中的first.count = -3;因为改变了HashSet集合中第一个R对象的count实例变量
的值,这将导致该R对象与集合中的其他对象相同。当试图删除count = -3的R对象时,HashSet
会计算出该对象的hashCode值,从而找出该对象在集合中保存位置,然后把此处的对象与count
为-3的R对象通过equals()方法进行比较,如果相等则删除该对象——HashSet只有第三元素才
满足该条件(第一个元素实际上保存在count为5的R对象对应的位置),所以第三个元素被删除
。至于第一个count为-3的R对象,它保存在count为5的R对象对应的位置,但是用equals()方法
拿它和count为5的R对象比较时有返回false——这将导致HashSet不可能准确访问该元素。