1、樹形結構: public interface Set<E> extends Collection<E>{} public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E>{} public class CopyOnWriteArraySet<E>extends AbstractSet<E>implements Serializable{} public abstract class EnumSet<E extends Enum<E>>extends AbstractSet<E>implements Cloneable, Serializable{} public class HashSet<E>extends AbstractSet<E>implements Set<E>, Cloneable, Serializable{} public final class JobStateReasonsextends HashSet<JobStateReason>implements PRintJobAttribute{} public class LinkedHashSet<E>extends HashSet<E>implements Set<E>, Cloneable, Serializable{} public class TreeSet<E>extends AbstractSet<E>implements SortedSet<E>, Cloneable, Serializable{} 可以看出,可以實例化的類為:CopyOnWriteArraySet,HashSet,LinkedHashSet,TreeSet。 2、Set是如何實現元素唯一性的 javadoc中對Set的描述第一段如下:“A collection that contains no duplicate elements. More formally, sets contain no pair of elements e1 and e2 such that e1.equals(e2), and at most one null element. As implied by its name, this interface models the mathematical set abstraction.” 這段話是對是錯,請看下面分析。 要進行下面的論述,我們先了解一下Map。Map中的元素是“鍵-值”對,其中“鍵”必須是唯一的。TreeSet和HashSet就是利用這個特性實現“no duplicate elements”。它把set中的元素作為Map中的“鍵”,從而保持元素的唯一性。這些鍵在Map中又是如何區分的呢?不同的Map有不同的做法,而且區別很大。 下面我們分別就TreeSet、HashSet和CopyOnWriteArraySet進行論述: 2.1、TreeSet部分:
以下以TreeSet為例進行分析。 請看TreeSet的部分實體: public class TreeSet<E> extends AbstractSet<E> implements SortedSet<E>, Cloneable, java.io.Serializable { // The backing Map private transient SortedMap<E,Object> m; // The keySet view of the backing Map private transient Set<E> keySet; // Dummy value to associate with an Object in the backing Map //這是每個鍵所指的對像 private static final Object PRESENT = new Object(); //constructor private TreeSet(SortedMap<E,Object> m) { this.m = m; keySet = m.keySet(); } public TreeSet() { this(new TreeMap<E,Object>()); } //以下省略.......... } 可以看到TreeSet使用了SortedMap作為其Map保存“鍵-值”對,而這個SortedMap的真正實體是TreeMap。
請看示例程序1: import java.util.*; public class SetTest1 { public static void main(String[] args){ Set set = new TreeSet(); set.add(new SetElement1("aa")); set.add(new SetElement1("bb")); } static class SetElement1{ String s; public SetElement1(String s){ this.s = s; } public String toString(){ return s; } public boolean equals(Object obj) { return s.equals(((SetElement1)obj).s); } } } 該程序能夠正常編譯,但是運行時會拋出異常java.lang.ClassCastException。為什么?
請看示例程序2: import java.util.*; public class SetTest2 { public static void main(String[] args){ Set set = new TreeSet(); set.add(new SetElement2("aa")); set.add(new SetElement2("aa")); set.add(new SetElement2("bb")); System.out.println(set); } static class SetElement2 implements Comparable{ String s; public SetElement2(String s){ this.s = s; } public String toString(){ return s; } public int compareTo(Object o){ return s.compareTo(((SetElement2)o).s); } public boolean equals(Object obj) { return s.equals(((SetElement2)obj).s); } } } 運行結果: [aa, bb] 這正是我們所期望的結果。那“示例程序1”和“示例程序2”有什么區別? 是因為SetElement2實現了Comparable接口,而SetElement1沒有。SetElement2實現Comparable接口有什么用呢?因為在TreeSet的add方法中需要比較兩個 元素的“值”。請看TreeMap中的compare方法: private int compare(K k1, K k2) { return (comparator==null ? ((Comparable</*-*/K>)k1).compareTo(k2) : comparator.compare((K)k1, (K)k2)); } 可見這個方法先把要比較的元素down cast成Comparable類型。這里就可以解釋“示例程序1”中為什么會拋出異常java.lang.ClassCastException,因SetElement1沒有實現Comparable接口,當然就不能down cast成Comparable。可見,要用TreeSet來做為你的Set,那么Set中所裝的元素都必須實現了Comparable接口。 說到這里,你是不是想到了TreeSet中是采用Comparable接口中的compareTo方法來判斷元素是否相同(duplicate),而不是采用其他類似equals之類的東東來判斷。
請看示例程序3: import java.util.Set; import java.util.*; public class SetTest3 { public static void main(String[] args){ Set set = new HashSet(); set.add(new SetElement3("aa")); set.add(new SetElement3("aa")); set.add(new SetElement3("bb")); System.out.println(set); } static class SetElement3 implements Comparable{ String s; public SetElement3(String s){ this.s = s; } public String toString(){ return s; } public int compareTo(Object o){ //return s.compareTo(((SetElement3)o).s); return -1; } public boolean equals(Object obj) { return s.equals(((SetElement3)obj).s); } } } 運行結果: [bb, aa, aa] 看到沒有,有兩個“aa”!!這是因為compareTo返回值始終是"-1",也就是說“把任何元素都看成不同”。
綜上所述,你是否對javadoc中對Set功能的描述有了懷疑?!
2.2、HashSet部分:
以下以HashSet為例進行分析。 從Hashset類的主體部分: public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable { static final long serialVersionUID = -5024744406713321676L; private transient HashMap<E,Object> map; // Dummy value to associate with an Object in the backing Map //這是每個鍵所指的對像 private static final Object PRESENT = new Object();
public HashSet() { map = new HashMap<E,Object>(); } public boolean add(E o) { return map.put(o, PRESENT)==null; } //以下省略.......... }
public HashSet() {
map = new HashMap<E,Object>();
} 可以看到HashSet使用了HashMap作為其Map保存“鍵-值”對。
請看示例程序4: import java.util.*;
public class SetTest4 { public static void main(String[] args){ Set set = new HashSet(); set.add(new SetElement4("aa")); set.add(new SetElement4("aa")); set.add(new SetElement4("bb")); System.out.println(set); } static class SetElement4{ String s; public SetElement4(String s){ this.s = s; } public String toString(){ return s; } public boolean equals(Object obj) { return s.equals(((SetElement4)obj).s); } } }