- HashSet类
HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode方法返回值也相等。当向HashSet集合中存入一个元素时,HashSet会调用对象的hashCode方法来得到该对象的hashCode值,然后根据该hashCode值决定该对象在HashSet中的存储位置。如果有两个元素通过equals方法返回true,但是hashCode方法返回不同的值,HashSet会把这两个对象存入不同的位置,不会把它们当作同一个对象。
package com.ivy.collection; import java.util.HashSet; class A { public boolean equals(Object obj) { return true; } } class B { public int hashCode() { return 1; } } class C { public boolean equals(Object obj) { return true; } public int hashCode() { return 2; } } public class HashSetDemo { public static void main(String[] args) { HashSet set = new HashSet(); set.add(new A()); set.add(new A()); set.add(new B()); set.add(new B()); set.add(new C()); set.add(new C()); System.out.println(set); } }
输出结果
[com.ivy.collection.B@1, com.ivy.collection.B@1, com.ivy.collection.C@2, com.ivy.collection.A@e3f6d, com.ivy.collection.A@a4b78b]
两个A,两个B是不同的,但是两个C是同一个对象。
所以,当把一个对象放入HashSet中时,如果需要重写该对象对应类的equals方法,也应该重写其hashCode方法。其规则是:如果两个对象通过equals方法比较返回true,那这两个对象的hashCode值也应该相同。对象中用作equals方法比较标准的Field,都应该用来计算hashCode值。
当向HashSet中添加可变对象时,必须十分小心,如果修改HashSet集合中的对象,有可能导致该对象与集合中的其他对象相等,从而导致HashSet无法准确访问该对象。
- LinkedHashSet类
LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet。
- TreeSet类
TreeSet可以确保机荷元素处于排序状态。TreeSet支持两种排序方法:自然排序和定制排序。
- 自然排序
TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素按升序排列,这种方式就是自然排序。Java提供了一个Comparable接口,该接口中定义了一个compareTo(Object obj)方法,实现了该接口的类的对象就可以比较大小。如果试图把一个对象添加到TreeSet时,则该对象的类必须实现Comparable接口,否则程序将会抛出异常。
对于TreeSet而言,它判断两个对象是否相等的唯一标准是:两个对象通过compareTo方法比较是否返回0,如果返回0,TreeSet会认为它们相等,不会被添加进TreeSet。
例如:
向TreeSet添加两个SomeClass对象A和B,添加A时,因为TreeSet中没有任何元素,所以可以添加进去,但是当添加B时,TreeSet会调用B.compareTo(Object obj)与TreeSet中其他元素比较,如果SomeClass没有实现Comparable接口,则会引发ClassCastException。
当需要把一个对象放入TreeSet中,重写该对象对应类的equals方法时,应保证该方法与compareTo方法有一致的结果,其规则是:如果两个对象通过equals方法比较返回true时,这两个对象通过compareTo方法比较应该返回0。
推荐HashSet和TreeSet中只放入不可变对象,因为如果向TreeSet中添加一个可变对象,并且后面程序修改了该对象的Field,这将导致它与其他对象的大小发生改变,但TreeSet不会再次调整它们的顺序,甚至可能导致TreeSet中保存的这两个对象通过compareTo方法比较返回0。
2. 定制排序
如果需要实现定制排序,需要在创建TreeSet集合对象时,提供一个Comparator对象与该TreeSet集合关联,由该Comparator对象负责集合元素的排序逻辑。Comparator接口包含一个int compare(Object o1, Object o2)方法,用于比较o1和o2的大小。
import java.util.Comparator; import java.util.TreeSet; class M { int age; public M(int age) { this.age = age; } public String toString() { return "M[age:" + age + "]"; } } public class TreeSetDemo { public static void main(String[] args) { TreeSet ts = new TreeSet(new Comparator() { public int compare(Object o1, Object o2){ M m1 = (M)o1; M m2 = (M)o2; return m1.age > m2.age ? -1 : m1.age < m2.age ? 1 : 0; } }); ts.add(new M(5)); ts.add(new M(-3)); ts.add(new M(9)); System.out.println(ts); } }
输出结果:
[M[age:9], M[age:5], M[age:-3]]
- EnumSet类
EnumSet没有提供构造器来创建EnumSet,程序应该通过它提供的static方法来创建。例如:
import java.util.Collection; import java.util.EnumSet; import java.util.HashSet; enum Season { SPRING, SUMMER, FALL, WINTER } public class EnumSetDemo { public static void main(String[] args) { // TODO Auto-generated method stub EnumSet es1 = EnumSet.allOf(Season.class); System.out.println(es1); EnumSet es2 = EnumSet.noneOf(Season.class); System.out.println(es2); es2.add(Season.WINTER); es2.add(Season.SPRING); System.out.println(es2); EnumSet es3 = EnumSet.of(Season.SUMMER, Season.WINTER); System.out.println(es3); EnumSet es4 = EnumSet.range(Season.SUMMER, Season.WINTER); System.out.println(es4); EnumSet es5 = EnumSet.complementOf(es4); System.out.println(es5); Collection c = new HashSet(); c.clear(); c.add(Season.FALL); c.add(Season.SPRING); EnumSet es6 = EnumSet.copyOf(c); System.out.println(es6); c.add("Java"); c.add("C#"); es6 = EnumSet.copyOf(c); } }
输出结果:
[SPRING, SUMMER, FALL, WINTER]
[]
[SPRING, WINTER]
[SUMMER, WINTER]
[SUMMER, FALL, WINTER]
[SPRING]
[SPRING, FALL]
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Enum
at java.util.EnumSet.copyOf(EnumSet.java:168)
at com.ivy.collection.EnumSetDemo.main(EnumSetDemo.java:41)
最后一行抛出异常,原因是当复制Collection集合中的所有元素来创建EnumSet时,要求Collection集合中的所有元素必须是同一个枚举类的枚举值。
- 各Set实现类的性能分析
HashSet vs TreeSet
HashSet的性能总是比TreeSet好,因为TreeSet需要额外的红黑树算法来维护集合次序。
只有当需要一个保持排序的Set时,才应该使用TreeSet.
HashSet vs LinkedHashSet
对于普通的插入删除操作,LinkedHashSet比HashSet要慢,这是由维护链表所带来的额外开销造成的。
但因为有了链表,遍历LinkedHashSet会更快。
EnumSet是所有set中性能最好的,但它只能保存同一个枚举类的枚举值作为集合元素。
Set的三个实现类HashSet/TreeSet/EnumSet都是线程不安全的。通常可以通过如下方法来包装Set集合,手动保证该Set集合的同步性:
SortedSet s = Collections.synchronizedSortedSet(new TreeSet(...));