1.善用Collection接口,它是集合的底層。
2.迭代器
public interface Iterator<E>{
E next();
boolean hasNext();
void remove();
}
善用迭代器
Collection<String> c = ...;
Iterator<String> iter = c.iterator();
while(iter.hasNext()){
String element = iter.next();
do something with element
}
還有“for each”循環:
for(String element:c){
do something with element
}
3.刪除元素
Iterator接口的remove方法將會刪除上次調用next方法時返回的元素。如刪除字符串集合中的第一個元素:
Iterator<String> iter = c.iterator();
iter.next();
iter.remove();
注意:對next方法和remvoe方法的調用具有互相依賴性。如果調用remvoe之前沒有調用next將是不合法的,會拋出IlleStateException異常。
如果想刪除兩個相鄰的元素,不能直接這樣調用:
iter.remove();
iter.remove();
必須先調用next越過將要刪除的元素。
iter.next();
iter.remove();
iter.next();
iter.remove();
4.善用collection接口提供的方法,如:
int size();
boolean isEmpty();
boolean contains(Object o);
boolean containsAll(Collection<?> c);
boolean equals(Object o);
boolean addAll(Collection<? extends E> c);
boolean remove(Object o);
boolean removeAll(Collection<?> c);
void clear();
boolean retainAll(Collection<?> c);
Object[] toArray();
<T> T[] toArray(T[] a);
5.以Map結尾的類實現了Map接口,其他類則實現了Collection接口
6.鏈表
數組和數組列表有一個重大缺陷:從中間位置刪除或插入一個元素要付出很大的代價,而鏈表解決了這個問題。在Java程序設計語言中,所有的鏈表都是雙向鏈接的,即每個結點還存放着指向前驅結點的引用。
List<String> staff = new LinkedList<String>();
staff.add("Amy");
staff.add("Bob");
staff.add("Carl");
staff.remove("Amy");
可以使用ListIterator向.鏈表新增元素:
List<String> staff = new LinkedList<String>();
staff.add("Amy");
staff.add("Bob");
staff.add("Carl");
ListIterator<String> iter = staff.listIterator();
iter.next();
iter.add("Juliet");
iter.next();
iter.remove();
使用“光標”進行元素的增刪時一定要注意“光標”位置
注意:如果在某個迭代器修改集合時,另一個迭代器對其進行遍歷,會出現混亂狀況,將拋出 ConcurrentModificationException異常。如
List<String> staff = ... ;
Iterator<String> iter1 = staff.iterator();
Iterator<String> iter2 = staff.iterator();
iter1.next();
iter1.remove();
iter2.next(); // 拋出ConcurrentModificationException異常
可以根據需要同時給窗口附加許多迭代器,但這些迭代器只能讀取列表。另外,單獨附加一個既能讀又能寫的迭代器。
LinkedList類提供了用來訪問某個特定元素的get方法:
LinkedList<String> list = ...;
String obj = list.get(n);
但這種方法效率並不太高,如果發現自己正在使用這個方法,說明 有可能對於所要解決的問題使用了錯誤的數據結構。
絕對不能使用下面這種方法遍歷鏈表,它的效率極低。
for(int i = 0; i < list.size(); i++)
do something with list.get(i);
優先使用鏈表,但避免使用以整數索引表示鏈表中位置的所有方法。如果需要對集合進行隨機訪問,就使用數組或ArrayList,而不要使用鏈表。
7.散列集Set
使用散列集存放自定義的類時,就是要負責實現這個類的hashCode方法。注意:自己實現的hashCode要與equals方法兼容,即如果a.equals(b)爲true,a與b必須具有相同 的散列碼。
8.樹集
TreeSet類是 一個有序集合。每次將一個元素添加到樹中,都被放置在正確的排序位置上。因此迭代器總是以排好序的順序訪問每個元素。(當前實現使用的紅黑樹)
將一個元素添加到樹中要比添加到散列表中慢,但是,與將元素添加到數組或鏈表的正確位置上相比還是快很多。
可以將Comparator對象傳遞給TreeSet構造器來告訴樹集使用的比較方法。如:
class ItemComparator implements Comparator<Item>{
public int compare(Item a,Item b){
String descrA = a.getDescription();
String descrB = b.getDescription();
return descrA.compareTo(descrB);
}
}
然後將這個類的對象傳遞給樹集的構造器:
ItemComparator comp = new ItemComparator();
SortedSet<Item> sortByDescription = new TreeSet<Item>(comp);
注意:Comparator這個比較器沒有任何數據。它只是比較方法的持有器。有時將這種對象稱爲函數對象。函數對象通常被定義爲“瞬時的”,那使用匿名內部類實現。
SortedSet<Item> sortByDescription = new TreeSet<Item>(new
Comparator<Item>{
public int compare(Item a,Item b){
String descrA = a.getDescription();
String descrB = b.getDescription();
return descrA.compareTo(descrB);
}
});
9.優先級隊列並沒有對所有的元素進行排序,而是使用了堆。堆是一個可以自我調整的二叉樹,對樹執行添加和刪除操作,可以讓最小的元素移動到根,而不必花費時間對元素進行排序。
10.映射表Map有三個視圖:
Set<K> keySet()
Collection<K> values()
set<Map.Entry<K,V>> entrySet()
注意:keySet既不是HashSet,也不是TreeSet,而是實現了Set接口的某個其他類。Set接口擴展了Collection接口,因此可以與使用任何集合一樣使用keySet。
可以調用迭代器的remove方法從眏射表中刪除鍵以及對應的值。但是不能將元素添加到鍵集的視圖中。如:
Set<String> keys = map.keySet();
Iterator<String> iter = keys.iterator();
iter.next();
iter.remove();
12.專用集與映射表類
(1)弱散列映射表
垃圾回收器中活動的對象。只要眏射表對象是活動的,其中的所有桶也是活動的,它們不能被回收。因此,需要由程序負責從長期存活的眏射表中刪除那些無用的值。但也可以使用WeakHashMap來完成這件事情。
13.視圖與包裝器
1)Arrays類的靜態方法asList:
Card[] cardDeck = new Card[52];
List<Card> card = Arrays.asList(cardDeck );
注意:方法返回的對象不是ArrayList,而是一個視圖對象,帶有訪問底層數組的get和set方法。改變數組大小的所有方法(如迭代器的add和remove方法)都會拋出一個UnsupportedOperationException異常。
從Java SE5.0開始,可以直接將各個元素傳遞給這個方法。
List<String> names = Arrays.asList("Amy","Bob","Carl");
將返回一個實現了List接口不修改的對象。
2)可以爲很多集合建立子範圍。假如有一個列表staff,使用:List group2 = staff.subList(10,20)從中取出了第10個~第19個元素,第一個索引包含在內,第二個索引不包含在內,與String類的substring操作中的參數情況相同。
對子範圍的所有操作都會自動反映到整個列表。如:group2.clear();則元素自動從staff列表中清除,並且group2爲空。
對於有序集和映射表,可以使用排序順序而不是元素位置建立子範圍。SortedSet接口聲明3個方法:
SortedSet<E> subSet(E from,E to);
SortedSet<E> headSet(E to);
SortedSet<E> tailSet(E from)
這些方法將返回大於等於from且小於to的所有元素子集。有序映射表可以使用:
SortedMap<K,V> subMap(K from,K to);
SortedMap<K,V> headMap(K to);
SortedMap<K,V> tailMap(K from);
Java SE6 引入 NavigableSet接口使子範圍可以指定是否包括邊界:
NavigableSet<E> subSet(E from,boolean fromInclusive,E to,boolean toInclusive);
NavigableSet<E>headSet(E to,boolean toInclusive);
NavigableSet<E> tailSet(E from,boolean fromInclusive);
3)不可修改的視圖
可以使用下面6種方法獲得不可修改視圖:
Collections.unmodifiableCollection
Collections.unmodifiableList
Collections.unmodifiableSet
Collections.unmodifiableSortedSet
Collections.unmodifiableMap
Collections.unmodifiableSortedMap
不可修改視圖對現在的集合增加了一個運行時的檢查。如果發現試圖對集合進行修改,就拋出一個異常,同時這個集合將保持未修改的狀態。如下:
List<String> unmodi = Collections.unmodifiableList(list);
unmodi.add("test");
將拋出一個UnsupportedOperationException異常。
4)注意:unmodifiableCollection返回一個集合,它的equals方法不調用底層集合的equals方法,它繼承了Object的equals方法,這個方法只是檢測兩個對象是否爲同一個對象,hashCode方法也是一樣的處理方法。但unmodifiableList類和unmodifiableSet類卻使用底層集合的equals方法和hasCode方法。
List<String> list = new LinkedList<String>();
List<String> list2 = new LinkedList<String>();
list2.add("test");
list.add("test");
System.out.println(list2.equals(list)); // true
Collection<String> colle = Collections.unmodifiableCollection(list);
Collection<String> colle2 = Collections.unmodifiableCollection(list2);
System.out.println(colle.equals(colle2)); //false
System.out.println(colle.hashCode() + " " + colle2.hashCode()); //兩個code不相同
List<String> unmodiList = Collections.unmodifiableList(list);
List<String> unmodiList2 = Collections.unmodifiableList(list2);
System.out.println(unmodiList.equals(unmodiList2)); //true
System.out.println(unmodiList.hashCode() + " " + unmodiList2.hashCode()); //兩個code相同
4)同步視圖
如果由多個線程訪問集合,就必須確保集不會被意外地破壞。例如,如果一個線程試圖將元素添加到散列表中,同時另一個線程正在對散列表進行再散列,其結果將是災難性的。
類庫的設計者使用視圖機制來確保常規集合的線程安全,而不是實現線程安全的集合類。如:Collections類的靜態方法synchronizedMap可以將任何一個眏射錶轉換成具有同步訪問方法的Map:
Map<String,Employee> map = Collections.synchronizedMap(new HashMap<String,Employee>());
現在就可以由多線程訪問map對象了。像get和put這類方法都是串行操作的,即一個方法必須調用完成,才允許另一個線程調用另一個方法。
5)檢測視圖
Java SE 5.0 增加了一組“被檢驗”視圖,用來對泛型類型發生問題時提供調試支持。
ArrayList<String> strings = new ArrayList<String>();
ArrayList rawList = strings; //編譯警告,但不報錯
rawList.add(new Date()); //編譯警告,但一樣不報錯
String date = (String) rawList.get(0); //報錯
上面代碼中的add命令在運行時檢測不到,只有在調用get方法並將結果轉化爲String時才拋出異常。而被檢測視圖可以探測到這類問題:
ArrayList<String> strings = new ArrayList<String>();
List<String> safeStrings = Collections.checkedList(strings, String.class);
List rawList = safeStrings;
rawList.add(new Date()); //拋出異常java.lang.ClassCastException
注意:被檢測視圖受限於虛假機可以運行的運行時檢查。例如,對於ArrayList<Pari<String>>,由於虛擬機有一個單獨的“原始‘Pair類,所以,無法阻止插入Pair<Date>
14.集合與數組之間的轉換
可以使用Arrays.asList將一個數組轉換爲集合,如
String[] values = ... ;
HashSet<String> staff = new HashSet<String>(Arrays.asList(values));
可以通過以以下方法將集合轉換爲數組 :
String[] values = staff.toArray(new String[staff.size()]);
15.算法
Collections類中的方法sort方法可以實現對List接口的集合進行排序。
List<String> staff = new LinkedList<String>();
Collections.sort(staff);
可以將Comparas對象作爲第二個參數傳遞給sort方法。
sort方法使用的歸併排序。
傳遞給排序算法的集合必須滿足:
列表支持set方法,則是可修改的。
列表支持add和remove方法,則是可改變大小的
Collections類中的shuffle算法可以隨機地混排列表中的元素。
ArrayList<Card> cards = ... ;
Collections.shuffle(cards);
只有可以隨機訪問的容器,如ArrayList才使用二分查找。如果必須利用迭代方式一次次地遍歷鏈表的一半元素找到中間位置的元素,二分查找就完全失去了優勢了。因此,如果爲binarySearch算法提供一個鏈表,它將自動變爲線性查找。