馴服Tiger(J2SE5.0)之集合框架
JDK 5.0 中最吸引人的地方在于集合框架的一些最突出的特性上,例如:支持泛型的語言級別上的新變化,以及可以在 java.util.concurrent 包中找到的并發集合工具包。在本文中,我將研究其他三個變化:更新過的 Arrays 和 Collections 類、新的 Queue 接口以及它的 PriorityQueue 實現。
數組(Array)
Arrays 類提供了一系列處理數組的靜態工具方法,這些索引的數據結構的大小是固定的。在 5.0 版本之前,Arrays 類擁有針對原始數據庫類型和通用 Object 類型的每種不同數組類型的 binarySearch()、equals()、fill() 和 sort() 方法。用于將 Object 數組轉換成 List 的附加 asList() 方法仍然有用。Tiger 為所有數組添加了 hashCode() 和 toString() 方法,還添加了特定于 Object 數組的 deepEquals()、deepHashCode() 和 deepToString() 方法。總計有 21 個新方法可用:
新添加的第一個方法是 hashCode()。對于任意數組類型,都可以調用 Arrays.hashCode(arrayVar) 方法來獲得格式良好的哈希碼。這個哈希碼可以用作 HashMap 或者其他相關目的的鍵。如果您不知道如何生成良好的哈希碼,那么最好使用 Arrays 類,它能產生更少沖突。Arrays 類生成等價于擁有相同元素的 List 的代碼。
在創建自己的類時,既需要提供 equals() 方法,又需要提供 hashCode() 方法。在 Arrays 的新方法 hashCode() 的幫助下,可以為任何本地數組類型生成哈希碼,而不用在每次需要它的時候折騰您自己。
所有數組類型都可用的另一個方法是 toString()。對于任何數組類型,都可以調用 Arrays.toString(arrayVar) 獲得逗號分隔的元素列表,列表用方括號包圍,如清單 1 的程序所示:
清單 1. 用 Arrays.toString 生成字符串
清單 2 顯示了結果:
清單 2. 清單 1 的結果
新的 deepEquals()、deepHashCode() 和 deepToString() 方法的工作方式類似于它們那些非深度(non-deep)的同類,但它們不僅會停下手來處理頂級數組的每個元素,還會更深入地研究生成結果的多維數組。
雖然不是一個新方法,但 asList() 方法在 5.0 的工作方式有所不同。以前,這個方法接受 Object[] 數組作為它的參數。現在,因為 Tiger 的可變參數列表特性,任何用逗號分隔的列表都可以接受,如清單 3 所示:
清單 3. Arrays.asList 的區別
如果傳遞給命令行的元素不同,清單 3 中的兩個示例沒必要產生同樣的結果 ,但是它確實展示了 Tiger 在語言級別上的變化如何擴展了 Arrays 原有的 asList() 方法。
集合
Arrays 用于處理不同集合的輔助類是 Collections 類。同樣,這個類也不是一個新類,但是該類的特性已經針對 5.0 作了擴展。現在有 13 個新方法:
清單 4. 檢測后的集合
使用 Java 5.0 平臺,您可能以為:由于將集合聲明為通用集合 (Collection<String> c = new HashSet<String>();),所以不需要進行運行時檢測了。但是如果向工具方法傳遞 String 版本的 HashSet,而工具方法只能處理非通用的 Set,那么該方法可能就會錯誤地向集合添加一個非 String 元素。通過臨時修改程序,用 Collection<String> c = Collections.checkedCollection(new HashSet<String>(), String.class); 添加運行時檢查,您可以迅速發現問題的根源。
三個 empty*() 方法 —— emptySet()、emptyList() 和 emptyMap() —— 生成空的不可改變的集合。雖然也可以用 new ArraySet() 這樣的方法創建空集合,但是還要通過某個 unmodifiable*() 方法才能確保新集合是不可改變的。empty 方法用更理想的方式提供了空的只讀集合。
隊列(Queue)接口
5.0 集合框架較大的一個改變就是添加了新的基接口 Queue。雖然這個接口是在“并發集合”技巧中描述的,但它的應用并不限于并發。在計算機科學中,隊列數據結構是基本的先進先出(FIFO) 結構。項目添加到尾部,并且要從頂部刪除。不僅能添加和刪除元素,還能查看隊列中有哪些元素。清單 5 顯示了 Queue 接口的 5 個方法:
清單 5. Queue 接口
請記住,Queue 是從 Collection 接口擴展的,所以實現 Queue 接口也就實現了 Collection。在使用 Queue 的實現時,應當將自己限制在接口的方法上。例如,向 Queue 添加元素可以用 Collection 的 add() 方法來實現,它在失敗時會拋出未檢測異常。相反,如果大小有限的隊列滿了,那么 offer() 方法會返回 false,而不需要處理隊列滿的異常。
java.util.concurrent 包中具有 Queue 接口的多個實現,但并不包含所有實現。LinkedList 類針對 JDK 5.0 的 Queue 接口作了修正,而 PriorityQueue 是隨 JDK 5.0 添加進來的。余下的實現 —— ArrayBlockingQueue、ConcurrentLinkedQueue、DelayQueue、LinkedBlockingQueue、PriorityBlockingQueue 和 SynchronousQueue —— 都是 java.util.concurrent 包的組成部分。
因為 LinkedList 不是新事物,所以我們來看一下新的 PriorityQueue 類。如清單 6 所示,可以用 6 種方法創建它。在不能使用 Comparator 時,可以使用元素的自然順序來確定優先級。如果元素沒有實現 Comparable 接口,那么就會產生運行時錯誤:
清單 6. PriorityQueue 構造函數
為了演示 PriorityQueue 的用法,清單 7 中的程序添加了所有命令行元素,并按字母順序處理它們。由于隊列結構是 LinkedList,所以順序應當是典型的 FIFO 順序,但是 PriorityQueue 將根據優先級對元素進行排序:
清單 7. PriorityQueue 的用法
清單 8 顯示了用命令行 one two three four 運行程序之后的輸出 :
清單 8. 清單 7 的結果
關于新的 Queue 接口,有件事需要提一下,這件事與 Collections 類有關:方法 checkedQueue()、emptyQueue()、synchronizedQueue() 和 unmodifiableQueue() 全都是 Collections 類中所缺少的。根據 bug 報告,除了 checkedQueue() 之外,所有類都是故意缺失的。對于 synchronizedQueue(),并發集合是比純粹的包裝器更好的選擇。其他方法則被認為不是必需的。也許新版本中會添加 checkedQueue()(和 checkedBlockingQueue()) 。
數組(Array)
Arrays 類提供了一系列處理數組的靜態工具方法,這些索引的數據結構的大小是固定的。在 5.0 版本之前,Arrays 類擁有針對原始數據庫類型和通用 Object 類型的每種不同數組類型的 binarySearch()、equals()、fill() 和 sort() 方法。用于將 Object 數組轉換成 List 的附加 asList() 方法仍然有用。Tiger 為所有數組添加了 hashCode() 和 toString() 方法,還添加了特定于 Object 數組的 deepEquals()、deepHashCode() 和 deepToString() 方法。總計有 21 個新方法可用:
- public static boolean deepEquals(Object[] a1, Object[] a2)
- public static int deepHashCode(Object[] a)
- public static String deepToString(Object[] a)
- public static int hashCode(boolean[] a)
- public static int hashCode(byte[] a)
- public static int hashCode(char[] a)
- public static int hashCode(double[] a)
- public static int hashCode(float[] a)
- public static int hashCode(int[] a)
- public static int hashCode(long[] a)
- public static int hashCode(Object[] a)
- public static int hashCode(short[] a)
- public static String toString(boolean[] a)
- public static String toString(byte[] a)
- public static String toString(char[] a)
- public static String toString(double[] a)
- public static String toString(float[] a)
- public static String toString(int[] a)
- public static String toString(long[] a)
- public static String toString(Object[] a)
- public static String toString(short[] a)
新添加的第一個方法是 hashCode()。對于任意數組類型,都可以調用 Arrays.hashCode(arrayVar) 方法來獲得格式良好的哈希碼。這個哈希碼可以用作 HashMap 或者其他相關目的的鍵。如果您不知道如何生成良好的哈希碼,那么最好使用 Arrays 類,它能產生更少沖突。Arrays 類生成等價于擁有相同元素的 List 的代碼。
在創建自己的類時,既需要提供 equals() 方法,又需要提供 hashCode() 方法。在 Arrays 的新方法 hashCode() 的幫助下,可以為任何本地數組類型生成哈希碼,而不用在每次需要它的時候折騰您自己。
所有數組類型都可用的另一個方法是 toString()。對于任何數組類型,都可以調用 Arrays.toString(arrayVar) 獲得逗號分隔的元素列表,列表用方括號包圍,如清單 1 的程序所示:
清單 1. 用 Arrays.toString 生成字符串
import java.util.Arrays; public class ArgsToString { public static void main(String args[]) { System.out.println(Arrays.toString(args)); } } |
清單 2 顯示了結果:
清單 2. 清單 1 的結果
>java ArgsToString One Two Three [One, Two, Three] |
新的 deepEquals()、deepHashCode() 和 deepToString() 方法的工作方式類似于它們那些非深度(non-deep)的同類,但它們不僅會停下手來處理頂級數組的每個元素,還會更深入地研究生成結果的多維數組。
雖然不是一個新方法,但 asList() 方法在 5.0 的工作方式有所不同。以前,這個方法接受 Object[] 數組作為它的參數。現在,因為 Tiger 的可變參數列表特性,任何用逗號分隔的列表都可以接受,如清單 3 所示:
清單 3. Arrays.asList 的區別
import java.util.Arrays; public class AsList { public static void main(String args[]) { // Before List before = Arrays.asList(args); // After List after = Arrays.asList("One", "Two", "Three"); } } |
如果傳遞給命令行的元素不同,清單 3 中的兩個示例沒必要產生同樣的結果 ,但是它確實展示了 Tiger 在語言級別上的變化如何擴展了 Arrays 原有的 asList() 方法。
集合
Arrays 用于處理不同集合的輔助類是 Collections 類。同樣,這個類也不是一個新類,但是該類的特性已經針對 5.0 作了擴展。現在有 13 個新方法:
- checkedCollection()
- checkedSet()
- checkedSortedSet()
- checkedList()
- checkedMap()
- checkedSortedMap()
- emptySet()
- emptyList()
- emptyMap()
- reverseOrder()
- frequency()
- disjoint()
- addAll()
清單 4. 檢測后的集合
public static <E> Collection<E> checkedCollection( Collection<E> c, Class<E> type) public static <E> Set<E> checkedSet( Set<E> s, Class<E> type) public static <E> SortedSet<E> checkedSortedSet( SortedSet<E> s, Class<E> type) public static <E> List<E> checkedList( List<E> list, Class<E> type) public static <K,V> Map<K,V> checkedMap( Map<K,V> m, Class<K> keyType, Class<V> valueType) public static <K,V> SortedMap<K,V> checkedSortedMap( SortedMap<K,V> m, Class<K> keyType, Class<V> valueType) |
使用 Java 5.0 平臺,您可能以為:由于將集合聲明為通用集合 (Collection<String> c = new HashSet<String>();),所以不需要進行運行時檢測了。但是如果向工具方法傳遞 String 版本的 HashSet,而工具方法只能處理非通用的 Set,那么該方法可能就會錯誤地向集合添加一個非 String 元素。通過臨時修改程序,用 Collection<String> c = Collections.checkedCollection(new HashSet<String>(), String.class); 添加運行時檢查,您可以迅速發現問題的根源。
三個 empty*() 方法 —— emptySet()、emptyList() 和 emptyMap() —— 生成空的不可改變的集合。雖然也可以用 new ArraySet() 這樣的方法創建空集合,但是還要通過某個 unmodifiable*() 方法才能確保新集合是不可改變的。empty 方法用更理想的方式提供了空的只讀集合。
隊列(Queue)接口
5.0 集合框架較大的一個改變就是添加了新的基接口 Queue。雖然這個接口是在“并發集合”技巧中描述的,但它的應用并不限于并發。在計算機科學中,隊列數據結構是基本的先進先出(FIFO) 結構。項目添加到尾部,并且要從頂部刪除。不僅能添加和刪除元素,還能查看隊列中有哪些元素。清單 5 顯示了 Queue 接口的 5 個方法:
清單 5. Queue 接口
public boolean offer(Object element) public Object remove() public Object poll() public Object element() public Object peek() |
請記住,Queue 是從 Collection 接口擴展的,所以實現 Queue 接口也就實現了 Collection。在使用 Queue 的實現時,應當將自己限制在接口的方法上。例如,向 Queue 添加元素可以用 Collection 的 add() 方法來實現,它在失敗時會拋出未檢測異常。相反,如果大小有限的隊列滿了,那么 offer() 方法會返回 false,而不需要處理隊列滿的異常。
java.util.concurrent 包中具有 Queue 接口的多個實現,但并不包含所有實現。LinkedList 類針對 JDK 5.0 的 Queue 接口作了修正,而 PriorityQueue 是隨 JDK 5.0 添加進來的。余下的實現 —— ArrayBlockingQueue、ConcurrentLinkedQueue、DelayQueue、LinkedBlockingQueue、PriorityBlockingQueue 和 SynchronousQueue —— 都是 java.util.concurrent 包的組成部分。
因為 LinkedList 不是新事物,所以我們來看一下新的 PriorityQueue 類。如清單 6 所示,可以用 6 種方法創建它。在不能使用 Comparator 時,可以使用元素的自然順序來確定優先級。如果元素沒有實現 Comparable 接口,那么就會產生運行時錯誤:
清單 6. PriorityQueue 構造函數
PriorityQueue() PriorityQueue(Collection<? extends E> c) PriorityQueue(int initialCapacity) PriorityQueue(int initialCapacity, Comparator<? super E> comparator) PriorityQueue(PriorityQueue<? extends E> c) PriorityQueue(SortedSet<? extends E> c) |
為了演示 PriorityQueue 的用法,清單 7 中的程序添加了所有命令行元素,并按字母順序處理它們。由于隊列結構是 LinkedList,所以順序應當是典型的 FIFO 順序,但是 PriorityQueue 將根據優先級對元素進行排序:
清單 7. PriorityQueue 的用法
import java.util.*; import java.util.concurrent.*; public class Priority { public static void main(String args[]) { Queue<String> queue = new PriorityQueue<String>(Arrays.asList(args)); String element; while ((element = queue.poll()) != null) { System.out.println(element); } } } |
清單 8 顯示了用命令行 one two three four 運行程序之后的輸出 :
清單 8. 清單 7 的結果
java Priority one two three four four one three two |
關于新的 Queue 接口,有件事需要提一下,這件事與 Collections 類有關:方法 checkedQueue()、emptyQueue()、synchronizedQueue() 和 unmodifiableQueue() 全都是 Collections 類中所缺少的。根據 bug 報告,除了 checkedQueue() 之外,所有類都是故意缺失的。對于 synchronizedQueue(),并發集合是比純粹的包裝器更好的選擇。其他方法則被認為不是必需的。也許新版本中會添加 checkedQueue()(和 checkedBlockingQueue()) 。