java的集合體系分為兩類,一類是Collection,一類是Map體系。詳細見下圖:

Collection是指一組元素的集合,比如常用的ArrayList,LinkedList,HashSet、linkedHashSet、TreeSet等,而Map則是一組鍵值對的集合,比如常用的HashMap,LinkedHashMap等。
這裡不贅述具體的api用法,而是講講map和set之間的聯系,比如HashMap和HashSet,看下HashSet内部的接口:
1、HashSet并沒有提供get某個元素的方式,隻提供了contains方法用于判斷是否包含某個元素:
2、對于add方法,内部使用了HashMap去存儲,把我們要入set的元素放到map的key上:
private transient HashMap<E,Object> map;
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
3、contains方法實作是map.containsKey():
public boolean contains(Object o) {
return map.containsKey(o);
}
由上述幾點可初步得出結論:HashSet是在内部是通過HashMap實作的。
那是否又可以推出另外一個結論就是,Set體系的類,其内部是通過Map類去實作的?觀察下TreeMap的add方法:
private transient NavigableMap<E,Object> m;
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
進一步坐實“Set體系的類,其内部是通過Map類去實作”這個結論。
Set中存在HashSet、LinkedHashSet、TreeSet這幾個比較常用的類,名稱上相似,但實際聯系是什麼呢?本質的聯系是,HashSet不保證添加和輸出的元素順序一緻,LinkedHashSet則保證添加和輸出的順序一緻,而TreeSet則是保證輸出的結果按照從小到大自然排序,如下方示例代碼:
Set<Integer> set1 = new HashSet<Integer>();
Set<Integer> set2 = new LinkedHashSet<Integer>();
Set<Integer> set3 = new TreeSet<Integer>();
List<Integer> list = new ArrayList<Integer>(Arrays.asList(1000, 2, 9, -5, 16));
set1.addAll(list);
set2.addAll(list);
set3.addAll(list);
System.out.println("HashSet="+set1);//[16, 2, -5, 1000, 9] 不保證插入和輸出的順序一緻
System.out.println("LinkedHashSet="+set2);//[1000, 2, 9, -5, 16] 保證插入和輸出的順序一緻
System.out.println("TreeSet="+set3);//[-5, 2, 9, 16, 1000] 預設從小到大進行排序,如果要控制順序,可以傳遞Comparator參數
set内部是由map實作的,自然map中也有類似的實作:LinkedHashMap、HashMap以及TreeMap,把兩者類比起來邊得到下面的關聯,彼此之間的性質是相類似的:
1、LinkedHashSet和LinkedHashMap
2、HashSet和HashMap
3、TreeSet和TreeMap
map中存在一個比較坑的地方,map.keySet會傳回内部對象的引用,如果外部對其進行操作,會影響map的内部狀态:
Map<String, String> map = new HashMap<String, String>();
map.put("attrOne", "aaaa");
map.put("attrTwo", "bbb");
map.put("attrThree", "ccc");
Set<String> keys = map.keySet();
List<String> values = new ArrayList<String>(map.values());//要寫成這種文法,我也是醉了。。。直接向下轉型會直接報錯
System.out.println("key size="+keys.size());//3
System.out.println("value size="+values.size());//3
map.remove("attrTwo");
System.out.println("key size="+keys.size());//2 這裡反映出,map.keySet傳回的是内部的引用類型,可變,會導緻潛在的bug
System.out.println("value size="+values.size());//3
由這一點可以看出,map的接口設計其實有問題,即不應該傳回内部引用對象給外界,避免出bug,這一個原則在 《effective java》第50條講到,必要時進行保護性拷貝。具體可參見原書,大意為如下僞代碼:
class MyHashMap{
private HashMap map= new HashMap();
public Set keySet(){
return new HashSet(map.keySet());
}
}
通過類似的接口封裝,把keySet的結果簡單複制下,外部對傳回的set做任何操作,也就不會影響到Map的狀态。