第 13 章 物件容器(Container)

在程式運行的過程中,很多時候您需要將物件暫時儲存在一個容器中統一管理,之後需要時再將物件取出,要使用什麼樣的容器依設計需求而定,您可以使用循序有索引的串列(List)結構來儲存物件,或是使用不允許重複相同物件的集合(Set)結構,您也可以使用「鍵-值」(Key-Value)存取的Map。

儲存結構與資料結構的設計是息息相關的,使用 Java SE 的話,即使您實際上不瞭解 List、Set、Map 資料結構的設計方式,也可以直接取用實作 java.util.List、java.util.Set、java.util.Map 介面的相關類別,以得到使用相同資料結構的功能,在 J2SE 5.0 中,這些容器類別更使用了「泛型」(Generics)功能重新改寫,如此您就不用擔心物件儲存至容器就失去其型態資訊的問題。


13.1 Collection 類

Collection 結構可持有各自獨立的物件,在 Java SE 中, Collection 包括了 List 與 Set,List 是實作 java.util.List 介面的相關類別,可依物件被放置至容器中的順序來排列物件,Set 是實作 java.util.Set 介面的相關類別,不接受重複的物件,並可擁有自己的一套排序規則。

13.1.1 簡介 List 介面

java.util.ArrayList 類別實作了 java.util.List 介面,所以要先認識一下 List 介面,List 介面是 java.util.Collection 介面的子介面,而 Collection 介面則是 java.lang.Iterable 的子介面,Iterable 介面要求實作一個 iterator() 方法。

  1. package java.lang;
  2. import java.util.Iterator;
  3. public interface Iterable<T> {
  4. Iterator<T> iterator();
  5. }

從 J2SE 5.0 開始增加了泛型設計的新功能,所以像 Iterable、Collection 相關介面與其實作類別,都使用泛型的功能重新改寫了,因而您可以在原始碼或是 API 文件中看到增加了不少與泛型相關的功能或說明。

Iterable 介面要求實作它的類別傳回一個實作 java.util.Iterator 介面的物件,事實上您在 Java SE 的 API 中找不到任何實作 Iterator 的類別,因為 Iterator 會根據實際的容器資料結構來迭代元素,而容器的資料結構實作方式對外界是隱藏的,使用者不用知道這個結構,只需要知道 Iterator 的操作方法,就可以取出元素,Iterator 介面的定義如下:

  1. package java.util;
  2. public interface Iterator<E> {
  3. boolean hasNext();
  4. E next();
  5. void remove();
  6. }

良葛格的話匣子 Iterator 是「Iterator 模式」的一個實例,有關 Iterator 模式,請參考我網站上的文件:

Collection 介面繼承了 Iterator 介面,定義了加入元素、移除元素、元素長度等方法,

  1. package java.util;
  2. public interface Collection<E> extends Iterable<E> {
  3. int size();
  4. boolean isEmpty();
  5. boolean contains(Object o);
  6. Iterator<E> iterator();
  7. <T> T[] toArray(T[] a);
  8. boolean add(E o);
  9. boolean remove(Object o);
  10. boolean containsAll(Collection<?> c);
  11. boolean addAll(Collection<? extends E> c);
  12. boolean removeAll(Collection<?> c);
  13. boolean retainAll(Collection<?> c);
  14. void clear();
  15. boolean equals(Object o);
  16. int hashCode();
  17. }

Collection 在移除元素及取得元素上的定義是比較通用,List 介面則又增加了根據索引取得物件的方法,這說明了 List 資料結構的特性,每個加入 List 中的元素是循序加入的,並可指定索引來存取元素(以下原始碼只是節錄部份)。

  1. package java.util;
  2. public interface List<E> extends Collection<E> {
  3. ....
  4. boolean addAll(int index, Collection<? extends E> c);
  5. E get(int index);
  6. E set(int index, E element);
  7. void add(int index, E element);
  8. E remove(int index);
  9. int indexOf(Object o);
  10. int lastIndexOf(Object o);
  11. List<E> subList(int fromIndex, int toIndex);
  12. ....
  13. }

List 資料結構的特性是,每個加入 List 中的元素是循序加入的,並可指定索引來存取元素,List 可以使用陣列(Array)或是鏈結串列(Linked List)來實作這個特性,前者在 Java SE 中的實作就是 java.util.ArrayList,後者就是 java.util.LinkedList,對於循序加入與存取,使用 ArrayList 的效率比較好,對於經常變動元素排列順序的需求,使用 LinkedList 會比較好。

良葛格的話匣子 以上的原始碼是從 JDK 安裝目錄下的 src.zip 中找出來的,記得如果您有需要參考 Java SE 中的 API 實作方式的話,都可以在 src.zip 中找到原始碼來參考。

13.1.2 ArrayList

ArrayList 實作了 List 介面,ArrayList 使用陣列結構實作 List 資料結構,陣列的特性是可以使用索引來快速指定物件的位置,所以對於快速的隨機取得物件來說,使用 ArrayList 可以得到較好的效能,但由於使用陣列實作,若要從中間作移除或插入物件的動作,會需要搬動後段的陣列元素以重新調整索引順序,所以速度上就會慢的多。

先來看看一個使用 ArrayList 的例子,如範例 13.1 所示。

範例 13.1 ArrayListDemo.java

  1. package onlyfun.caterpillar;
  2. import java.util.*;
  3. public class ArrayListDemo {
  4. public static void main(String[] args) {
  5. Scanner scanner = new Scanner(System.in);
  6. List<String> list = new ArrayList<String>();
  7. System.out.println("輸入名稱(使用quit結束)");
  8. while(true) {
  9. System.out.print("# ");
  10. String input = scanner.next();
  11. if(input.equals("quit"))
  12. break;
  13. list.add(input);
  14. }
  15. System.out.print("顯示輸入: ");
  16. for(int i = 0; i < list.size(); i++)
  17. System.out.print(list.get(i) + " ");
  18. System.out.println();
  19. }
  20. }

在 J2SE 5.0 之後新增了泛型(Generic)的功能,使用物件容器時可以宣告將儲存的物件型態,如此您的物件在存入容器會被限定為您所宣告的型態,編譯器在編譯時期會協助您進行型態檢查,而取出物件時也不至於失去原來的型態資訊,這可以避免型態轉換時的問題。

使用 add() 方法可以將一個物件加入 ArrayList中,使用 size() 方法可以傳回目前的 ArrayList 的長度,使用 get() 可以傳回指定索引處的物件,使用 toArray() 可以將 ArrayList 中的物件轉換為物件陣列,執行結果如下:

  1. 輸入名稱(使用quit結束)
  2. # 良葛格
  3. # 毛美眉
  4. # 米小狗
  5. # quit
  6. 顯示輸入: 良葛格 毛美眉 米小狗

良葛格的話匣子 在 9.2 中您已經學過如何使用套件(package)來管理類別,建議您在平常作題目練習時也試著使用套件管理類別,編譯時記得要這麼下指令:

  1. javac d . YourClass.java

執行時記得完整的類別名必須包括套件名稱,所以假設套件是 onlyfun.caterpillar,則執行時必須這麼下指令:

  1. java onlyfun.caterpillar.YourClass

從這個章節開始,我會為每個範例加上套件管理,如果有不熟悉套件管理的部份,建議複習一下9.2的內容。

您可以使用 get() 方法指定索引值取出物件,然而如果您的目的是要循序取出容器中所有的物件,則您可以使用 Iterator,Iterator 類為實作「Iterator 模式」的一個例子(見本章後網路索引),來使用 Iterator 的功能改寫一下範例 13.1 為範例 13.2。

範例 13.2 IteratorDemo.java

  1. package onlyfun.caterpillar;
  2. import java.util.*;
  3. public class IteratorDemo {
  4. public static void main(String[] args) {
  5. Scanner scanner = new Scanner(System.in);
  6. List<String> list = new ArrayList<String>();
  7. System.out.println("輸入名稱(輸入quit結束)");
  8. while(true) {
  9. System.out.print("# ");
  10. String input = scanner.next();
  11. if(input.equals("quit"))
  12. break;
  13. list.add(input);
  14. }
  15. // 使用 Iterator 取得元素
  16. Iterator iterator = list.iterator();
  17. while(iterator.hasNext()) { // 還有下一個元素嗎?
  18. // 使用 next() 取得下一個元素
  19. System.out.print(iterator.next() + " ");
  20. }
  21. System.out.println();
  22. }
  23. }

iterator() 方法會傳回一個 Iterator 物件,這個物件提供遍訪容器元素的方法,hasNext() 方法測試 Iterator 中是否還有物件,如果有的話,可以使用 next() 方法取出,您不用理會 Iterator 是如何實作的,事實上您在 Java SE 的 API 中也找不到實作 Iterator 的類別,在 ArrayList 的例子中,Iterator 的實例是在 ArrayList 中根據陣列的結構而實作的,但您不用理會實作細節,只要知道如何根據 Iterator 介面來操作就可以了,執行結果與範例 13.1 是相同的。

事實上在 J2SE 5.0 中,您不必使用 iterator() 方法返回 Iterator 實例並操作它來取得元素,您可以使用「增強的 for 迴圈」(Enhanced for loop)來直接遍訪 List 的所有元素,範例 13.3 改寫範例 13.1 作了示範。

範例 13.3 EnhancedForDemo.java

  1. package onlyfun.caterpillar;
  2. import java.util.*;
  3. public class EnhancedForDemo {
  4. public static void main(String[] args) {
  5. Scanner scanner = new Scanner(System.in);
  6. List<String> list = new ArrayList<String>();
  7. System.out.println("輸入名稱(輸入quit結束)");
  8. while(true) {
  9. System.out.print("# ");
  10. String input = scanner.next();
  11. if(input.equals("quit"))
  12. break;
  13. list.add(input);
  14. }
  15. // 使用foreach來遍訪List中的元素
  16. for(String s : list) {
  17. System.out.print(s + " ");
  18. }
  19. System.out.println();
  20. }
  21. }

執行結果與範例 13.1 的執行結果是相同的。您可以再嘗試使用一些 ArrayList 上的方法,像是 indexOf() 相關方法,如果有不明白的方法,記得查詢線上 API 文件。

13.1.3 LinkedList

List 預設是以物件加入(add)容器的順序來排列它們, List 上的 add() 也可以指定位置插入物件,如果您的物件加入之後大都是為了取出,而不會常作移除或插入(Insert)的動作,則使用 ArrayList 效能上會比較好,如果您會經常從容器中作移除或插入物件的動作,則使用 java.util.LinkedList 會獲得較好的效能。

LinkedList 使用鏈結串列(Linked list)實作了 List 介面,在介紹 ArrayList 時,您大致已瞭解如何操作實作 List 介面的物件,這邊介紹 LinkedList 上增加的一些移除與插入物件的特定方法,像是 addFirst()、addLast()、getFirst()、getLast()、removeFirst( )、removeLast() 等,由於使用 LinkedList 使用鏈結串列,在進行插入與移除動作時有較好的效能,適合拿來實作堆疊(Stack)與佇列(Queue)。

範例 13.4 使用 LinkedList 實作一個簡單的先進後出(First-In, Last-Out)的堆疊類別,這個類別可以存入字串。

範例 13.4 StringStack.java

  1. package onlyfun.caterpillar;
  2. import java.util.*;
  3. public class StringStack {
  4. private LinkedList<String> linkedList;
  5. public StringStack() {
  6. linkedList = new LinkedList<String>();
  7. }
  8. public void push(String name) {
  9. // 將元素加入串列前端
  10. linkedList.addFirst(name);
  11. }
  12. public String top() {
  13. // 取得串列第一個元素
  14. return linkedList.getFirst();
  15. }
  16. public String pop() {
  17. // 移出第一個元素
  18. return linkedList.removeFirst();
  19. }
  20. public boolean isEmpty() {
  21. // 串列是否為空
  22. return linkedList.isEmpty();
  23. }
  24. }

由於 addFirst()、addLast()、getFirst()、getLast()、removeFirst( )、removeLast() 等方法是 LinkedList 類別本身定義的方法,無法透過 List 介面來操作,所以您要使用 LinkedList 型態的參考名稱來操作實例,範例 13.5 示範如何使用範例 13.4 這個簡單的堆疊類別。

範例 13.5 StringStackDemo.java

  1. package onlyfun.caterpillar;
  2. import java.util.Scanner;
  3. public class StringStackDemo {
  4. public static void main(String[] args) {
  5. Scanner scanner = new Scanner(System.in);
  6. StringStack stack = new StringStack();
  7. System.out.println("輸入名稱(使用quit結束)");
  8. while(true) {
  9. System.out.print("# ");
  10. String input = scanner.next();
  11. if(input.equals("quit"))
  12. break;
  13. stack.push(input);
  14. }
  15. System.out.print("顯示輸入: ");
  16. while(!stack.isEmpty()) {
  17. System.out.print(stack.pop() + " ");
  18. }
  19. System.out.println();
  20. }
  21. }

由於堆疊是先進後出,所以最後一個輸入的會最先顯示出來,一個執行的結果如下:

  1. 輸入名稱(使用quit結束)
  2. # caterpillar
  3. # momor
  4. # bush
  5. # quit
  6. 顯示輸入: bush momor caterpillar

對於先進先出(First-In, First-Out)的佇列,您也可以使用 LinkedList 來實作,範例 13.6 實作一個簡單的 StringQueue 類別。

範例 13.6 StringQueue.java

  1. package onlyfun.caterpillar;
  2. import java.util.*;
  3. public class StringQueue {
  4. private LinkedList<String> linkedList;
  5. public StringQueue() {
  6. linkedList = new LinkedList<String>();
  7. }
  8. public void put(String name) {
  9. linkedList.addFirst(name);
  10. }
  11. public String get() {
  12. return linkedList.removeLast();
  13. }
  14. public boolean isEmpty() {
  15. return linkedList.isEmpty();
  16. }
  17. }

來撰寫範例 13.7 示範一下如何使用範例 13.6 的 StringQueue 類別。

範例 13.7 StringQueueDemo.java

  1. package onlyfun.caterpillar;
  2. import java.util.Scanner;
  3. public class StringQueueDemo {
  4. public static void main(String[] args) {
  5. Scanner scanner = new Scanner(System.in);
  6. StringQueue queue = new StringQueue();
  7. System.out.println("輸入名稱(使用quit結束)");
  8. while(true) {
  9. System.out.print("# ");
  10. String input = scanner.next();
  11. if(input.equals("quit"))
  12. break;
  13. queue.put(input);
  14. }
  15. System.out.print("顯示輸入: ");
  16. while(!queue.isEmpty()) {
  17. System.out.print(queue.get() + " ");
  18. }
  19. System.out.println();
  20. }
  21. }

由於佇列是先進先出的結構,所以最先輸入的會最先顯示出來,一個執行結果如下:

  1. 輸入名稱(使用quit結束)
  2. # caterpillar
  3. # momor
  4. # bush
  5. # quit
  6. 顯示輸入: caterpillar momor bush

事實上,如果您要使用佇列的功能,您也不用親自實作,在 J2SE 5.0 中,LinkedList 也實作了 java.util.Queue 介面,所以您可以直接操作 LinkedList 的實例進行佇列操作,範例 13.8 是個簡單的示範。

範例 13.8 QueueDemo.java

  1. package onlyfun.caterpillar;
  2. import java.util.*;
  3. public class QueueDemo {
  4. public static void main(String[] args) {
  5. Scanner scanner = new Scanner(System.in);
  6. Queue<String> queue = new LinkedList<String>();
  7. System.out.println("輸入名稱(使用quit結束)");
  8. while(true) {
  9. System.out.print("# ");
  10. String input = scanner.next();
  11. if(input.equals("quit"))
  12. break;
  13. // offer():加入元素至佇列中
  14. queue.offer(input);
  15. }
  16. System.out.print("顯示輸入: ");
  17. String element = null;
  18. // poll():取得並移去佇列中的元素
  19. // 佇列為空時傳回null
  20. while((element = queue.poll()) != null) {
  21. System.out.print(element + " ");
  22. }
  23. System.out.println();
  24. }
  25. }

範例 13.8 的執行結果與範例 13.7 是相同的。Queue 有五個必須實作的方法,範例中示範了 offer() 與 poll() 的操作,另外還有:element() 可取得但不移除佇列第一個元件,佇列為空時會丟出例外;peek() 可取得但不移除佇列第一個元件,佇列為空時傳回 null;remove() 取得並移除佇列第一個元件。

13.1.4 HashSet

java.util.HashSet 實作了 java.util.Set 介面,Set 介面一樣繼承了 Collection 介面,List 容器中的物件允許重複,但 Set 容器中的物件都是唯一的,Set 容器有自己的一套排序規則。

HashSet 的排序規則是利用湊雜(Hash),所以加入 HashSet 容器的物件還必須重新定義 hashCode() 方法,HashSet 根據湊雜碼來確定物件於容器中儲存的位置,也可以根據雜湊碼來快速的找到容器中的物件。

在比較兩個加入 HashSet 容器中的物件是否相同時,會先比較 hashCode() 方法傳回的值是否相同,如果相同,則再使用 equals() 方法比較,如果兩者都相同,則視為相同的物件。

事實上在定義類別時,最好總是重新定義 equals() 與 hashCode() 方法,以符合 Java 的設計規範,您可以參考 8.1.5 的介紹瞭解如何重新定義 equals() 與 hashCode() 方法。

來看看如何使用 HashSet,範例 13.9 是個簡單的示範。

範例 13.9 HashSetDemo.java

  1. package onlyfun.caterpillar;
  2. import java.util.*;
  3. public class HashSetDemo {
  4. public static void main(String[] args) {
  5. Set<String> set = new HashSet<String>();
  6. set.add("caterpillar");
  7. set.add("momor");
  8. set.add("bush");
  9. // 故意加入重複的物件
  10. set.add("caterpillar");
  11. // 使用 Iterator 顯示物件
  12. Iterator iterator = set.iterator();
  13. while(iterator.hasNext()) {
  14. System.out.print(iterator.next() + " ");
  15. }
  16. System.out.println();
  17. set.remove("bush");
  18. // 也可使用 enhanced for loop 顯示物件
  19. for(String name : set) {
  20. System.out.print(name + " ");
  21. }
  22. System.out.println();
  23. }
  24. }

在範例 13.9 中可以看到,即使重複加入了 “caterpillar” 字串,HashSet 中仍只有一個 “caterpillar” 字串物件,這是 Set 的特性,另一個要注意的是,迭代 HashSet 中所有的值時,其順序與您加入容器的順序是不一樣的,迭代所有值時的順序是 HashSet 排序過後的順序,執行結果如下:

  1. bush momor caterpillar
  2. momor caterpillar

如果想要在迭代時按照加入的順序顯示,則可以使用 java.util.LinkedHashSet,LinkedHashSet是HashSet 的子類別,它在內部實作時使用雜湊碼(Hash Code)進行排序,然而允許您在迭代時行為像是 LinkedList,可以使用 LinkedHashSet 來改寫範例 13.9 即可瞭解。

範例 13.10 LinkedHashSetDemo.java

  1. package onlyfun.caterpillar;
  2. import java.util.*;
  3. public class LinkedHashSetDemo {
  4. public static void main(String[] args) {
  5. Set<String> set = new LinkedHashSet<String>();
  6. set.add("caterpillar");
  7. set.add("momor");
  8. set.add("bush");
  9. // 使用 enhanced for loop 顯示物件
  10. for(String name : set) {
  11. System.out.print(name + " ");
  12. }
  13. System.out.println();
  14. }
  15. }

執行時顯示物件的順序是您加入物件時的順序,結果如下:

  1. caterpillar momor bush

13.1.5 TreeSet

TreeSet 實作 Set 介面與 java.util.SortedSet 介面,SortedSet 提供相關的方法讓您有序的取出對應位置的物件,像是 first()、last() 等方法,TreeSet 是 Java SE 中唯一實作 SortedSet 介面的類別,它使用紅黑樹結構來對加入的物件進行排序。

來看看使用 TreeSet 的一個簡單例子。

範例 13.11 TreeSetDemo.java

  1. package onlyfun.caterpillar;
  2. import java.util.*;
  3. public class TreeSetDemo {
  4. public static void main(String[] args) {
  5. Set<String> set = new TreeSet<String>();
  6. set.add("justin");
  7. set.add("caterpillar");
  8. set.add("momor");
  9. // 使用 enhanced for loop 顯示物件
  10. for(String name : set) {
  11. System.out.print(name + " ");
  12. }
  13. System.out.println();
  14. }
  15. }

由於您加入的是 String 物件,執行結果會自動依字典順序進行排列的動作,如下所示:

  1. caterpillar justin momor

依字典順序來排列 String 物件是 TreeSet 預設的,如果您對物件有自己的一套排列順序,您要定義一個實作 java.util.Comparator 介面的物件,您要實作介面中的 compare() 方法,compare() 方法必須傳回整數值,如果物件順序相同則傳回 0,傳回正整數表示 compare () 方法中傳入的第一個物件大於第二個物件,反之則傳回負整數。

舉個實際的例子,假設您想要改變 TreeSet 依字典順序排列加入的物件為相反的順序,您可以如範例 13.12 自訂一個實作 Comparator 介面的類別。

範例 13.12 CustomComparator.java

  1. package onlyfun.caterpillar;
  2. import java.util.Comparator;
  3. public class CustomComparator<T> implements Comparator<T> {
  4. public int compare(T o1, T o2) {
  5. if (((T) o1).equals(o2))
  6. return 0;
  7. return ((Comparable<T>) o1).compareTo((T) o2) * -1;
  8. }
  9. }

在自訂的 Comparator 中,如果兩個物件的順序相同會傳回 0,而為了方便比較物件,範例 13.12 中要求傳入的物件必須實作 Comparable 介面(例如 String 物件就有實作 Comparable 介面),範例中只是簡單的將原來 compareTo() 傳回的值乘以負一,如此在 TreeSet 中就可以簡單的讓排列順序相反,可以重新改寫一下範例 13.11,在建構 TreeSet 實例時一併指定自訂的 Comparator,如範例 13.13 所示。

範例 13.13 TreeSetDemo2.java

  1. package onlyfun.caterpillar;
  2. import java.util.*;
  3. public class TreeSetDemo2 {
  4. public static void main(String[] args) {
  5. // 自訂Comparator
  6. Comparator<String> comparator =
  7. new CustomComparator<String>();
  8. Set<String> set =
  9. new TreeSet<String>(comparator);
  10. set.add("justin");
  11. set.add("caterpillar");
  12. set.add("momor");
  13. // 使用 enhanced for loop 顯示物件
  14. for(String name : set) {
  15. System.out.print(name + " ");
  16. }
  17. System.out.println();
  18. }
  19. }

這次的執行結果會依您所指定的 Comparator 來排列,如下所示,顯示的順序與範例 13.11 是相反的:

  1. momor justin caterpillar

13.1.6 EnumSet

java.util.EnumSet 是在 J2SE 5.0 後加入的新類別,可以協助您建立列舉值的集合,EnumSet 提供了一系列的靜態方法,可以讓您指定不同的集合建立方式,直接使用範例 13.14 作個簡單示範。

範例 13.14 EnumSetDemo.java

  1. package onlyfun.caterpillar;
  2. import java.util.*;
  3. // 定義列舉型態
  4. enum FontConstant { Plain, Bold, Italic }
  5. public class EnumSetDemo {
  6. public static void main(String[] args) {
  7. // 建立列舉值集合
  8. EnumSet<FontConstant> enumSet =
  9. EnumSet.of(FontConstant.Plain,
  10. FontConstant.Bold);
  11. // 顯示集合內容
  12. showEnumSet(enumSet);
  13. // 顯示補集合內容
  14. showEnumSet(EnumSet.complementOf(enumSet));
  15. }
  16. public static void showEnumSet(
  17. EnumSet<FontConstant> enumSet) {
  18. for(FontConstant constant : enumSet) {
  19. System.out.println(constant);
  20. }
  21. System.out.println();
  22. }
  23. }

如範例 13.14 所示範的,您可以指定列舉值來加入 EnumSet 中,使用 of() 方法會返回一個 EnumSet 的實例,當中包括您所指定的列舉值,您也可以使用 complementOf() 指定一個 EnumSet 的互補集,以下是執行的結果:

  1. Plain
  2. Bold
  3. Italic

EnumSet 實作了 Set 介面,所以您可以使用 Set 介面的所有方法來測試它所包括的列舉值,例如測試一個集合中是否包括 FontConstant.Bold:

  1. if(enumSet.contains(FontConstant.Bold)) {
  2. System.out.println("包括FontConstant.Bold");
  3. }

您也可以建立一個空的 EnumSet 實例,然後再逐個加入列舉值,例如範例 13.15 所示範的。

範例 13.15 EnumSetDemo2.java

  1. package onlyfun.caterpillar;
  2. import java.util.*;
  3. enum FontConstant { Plain, Bold, Italic }
  4. public class EnumSetDemo2 {
  5. public static void main(String[] args) {
  6. // 建立EnumSet實例,初始內容為空
  7. EnumSet<FontConstant> enumSet =
  8. EnumSet.noneOf(FontConstant.class);
  9. // 加入列舉
  10. enumSet.add(FontConstant.Bold);
  11. enumSet.add(FontConstant.Italic);
  12. showEnumSet(enumSet);
  13. }
  14. public static void showEnumSet(
  15. EnumSet<FontConstant> enumSet) {
  16. for(FontConstant constant : enumSet) {
  17. System.out.println(constant);
  18. }
  19. System.out.println();
  20. }
  21. }

執行結果:

  1. Bold
  2. Italic

想要知道更多EnumSet相關的方法,您可以參考 EnumSet 線上 API 文件。

13.2 Map 類

實作 java.util.Map 介面的物件會將「鍵」(Key)映射至「值」(Value),「值」指的是您要存入 Map 容器的物件。在將物件存入 Map 物件時,需要同時給定一個「鍵」,要取回物件時可以指定鍵,如此就可以取得與鍵對應的物件「值」,Map 中的每一個鍵都是唯一的,不能有重複的鍵,Map擁有自己的排序機制。

13.2.1 HashMap

Map 的特性即「鍵-值」(Key-Value)匹配,簡單的說,您可以將實作 Map 介面的容器物件當作一個有很多房間的房子,每個房間的門有唯一的鑰匙(Key),您將物件儲存至房間中時,要順便擁有一把鑰匙,下次要取回物件時,就是根據這把鑰匙取回物件。

java.util.HashMap 實作了 Map 介面,HashMap 在內部實作使用雜湊(Hash),讓您在很快的時間內可以尋得「鍵-值」(Key-Value)匹配,直接使用一個簡單的例子來說明。

範例 13.16 HashMapDemo.java

  1. package onlyfun.caterpillar;
  2. import java.util.*;
  3. public class HashMapDemo {
  4. public static void main(String[] args) {
  5. Map<String, String> map =
  6. new HashMap<String, String>();
  7. String key1 = "caterpillar";
  8. String key2 = "justin";
  9. map.put(key1, "caterpillar 的訊息");
  10. map.put(key2, "justin 的訊息");
  11. System.out.println(map.get(key1));
  12. System.out.println(map.get(key2));
  13. }
  14. }

在範例中宣告 Map 型態與新增 Map 實例時,您指定了「鍵-值」所要使用的型態,在範例中都是宣告為 String 型態,也就是您要以 String 物件作為「鍵」,而存入的「值」也會是 String 物件,使用 Map 的 put() 方法將物件存入時,必須同時指定「鍵」、「值」,而要取回物件時,則使用 get() 方法並指定「鍵」,傳回的會是對應於鍵的「值」,程式的執行結果如下:

  1. caterpillar 的訊息
  2. justin 的訊息

可以使用 values() 方法返回一個實作 Collection 的物件,當中包括所有的「值」物件,如果您需要一次迭代 Map 中所有的物件,這會很有用,範例 13.17 是個簡單示範。

範例 13.17 HashMapDemo2.java

  1. package onlyfun.caterpillar;
  2. import java.util.*;
  3. public class HashMapDemo2 {
  4. public static void main(String[] args) {
  5. Map<String, String> map =
  6. new HashMap<String, String>();
  7. map.put("justin", "justin 的訊息");
  8. map.put("momor", "momor 的訊息");
  9. map.put("caterpillar", "caterpillar 的訊息");
  10. Collection collection = map.values();
  11. Iterator iterator = collection.iterator();
  12. while(iterator.hasNext()) {
  13. System.out.println(iterator.next());
  14. }
  15. System.out.println();
  16. // 事實上也可以使用增強的 for 迴圈
  17. for(String value : map.values()) {
  18. System.out.println(value);
  19. }
  20. }
  21. }

範例中使用了 Iterator 物件來迭代所有的值,也使用 for 迴圈來走訪所有的值,如果您細心的話,也許發現了,只要是實作 Collection 介面的物件,都可以使用 J2SE 5.0 新的增強 for 迴圈功能來迭代所有的值,執行結果如下所示:

  1. momor 的訊息
  2. justin 的訊息
  3. caterpillar 的訊息
  4. momor 的訊息
  5. justin 的訊息
  6. caterpillar 的訊息

如果您想要在迭代所有的物件時,依照插入的順序來排列,則可以使用 java.util.LinkedHashMap,它是 HashMap 的子類,在使用 values() 所返回的 Collection 物件,其內含物件之順序即為當初您加入物件之順序,範例 13.18 是個簡單的示範。

範例 13.18 LinkedHashMapDemo.java

  1. package onlyfun.caterpillar;
  2. import java.util.*;
  3. public class LinkedHashMapDemo {
  4. public static void main(String[] args) {
  5. Map<String, String> map =
  6. new LinkedHashMap<String, String>();
  7. map.put("justin", "justin 的訊息");
  8. map.put("momor", "momor 的訊息");
  9. map.put("caterpillar", "caterpillar 的訊息");
  10. for(String value : map.values()) {
  11. System.out.println(value);
  12. }
  13. }
  14. }

執行結果:

  1. justin 的訊息
  2. momor 的訊息
  3. caterpillar 的訊息

良葛格的話匣子 HashMap 是個被經常使用的物件,您可以參考下面幾個例子中有關 HashMap 的應用:

13.2.2 TreeMap

java.util.TreeMap 實作 Map 介面與 java.util.SortedMap 介面,SortedMap 提供相關的方法讓您有序的取出對應位置的物件,像是 firstKey()、lastKey() 等方法,TreeMap 是 Java SE 中唯一實作 SortedMap 介面的類別,它使用紅黑樹結構來對加入的物件進行排序。

來看看使用 TreeMap 的一個簡單例子。

範例 13.19 TreeMapDemo.java

  1. package onlyfun.caterpillar;
  2. import java.util.*;
  3. public class TreeMapDemo {
  4. public static void main(String[] args) {
  5. Map<String, String> map =
  6. new TreeMap<String, String>();
  7. map.put("justin", "justin 的訊息");
  8. map.put("momor", "momor 的訊息");
  9. map.put("caterpillar", "caterpillar 的訊息");
  10. for(String value : map.values()) {
  11. System.out.println(value);
  12. }
  13. }
  14. }

由於範例中加入的是 String 物件,執行結果會自動依「鍵」的字典順序進行排序的動作,執行結果如下:

  1. caterpillar 的訊息
  2. justin 的訊息
  3. momor 的訊息

依「鍵」的字典順序來排列插入的物件是TreeMap預設的,如果您對物件有自己的一套排列順序,您要定義一個實作 java.util.Comparator 介面的物件,這在 13.1.5 中已經介紹過了,以下就直接使用範例 13.12 的 CustomComparator 類別來改寫範例 13.19,讓排列的順序為反向的字典順序。

範例 13.20 TreeMapDemo2.java

  1. package onlyfun.caterpillar;
  2. import java.util.*;
  3. public class TreeMapDemo2 {
  4. public static void main(String[] args) {
  5. CustomComparator<String> comparator =
  6. new CustomComparator<String>();
  7. Map<String, String> map =
  8. new TreeMap<String, String>(comparator);
  9. map.put("justin", "justin 的訊息");
  10. map.put("momor", "momor 的訊息");
  11. map.put("caterpillar", "caterpillar 的訊息");
  12. for(String value : map.values()) {
  13. System.out.println(value);
  14. }
  15. }
  16. }

執行結果如下:

  1. momor 的訊息
  2. justin 的訊息
  3. caterpillar 的訊息

13.2.3 EnumMap

java.util.EnumMap 是個專為列舉型態設計的 Map 類別,方便您使用列舉型態及 Map 物件,直接來舉個實例。

範例 13.21 EnumMapDemo.java

  1. package onlyfun.caterpillar;
  2. import java.util.*;
  3. enum Action {TURN_LEFT, TURN_RIGHT, SHOOT}
  4. public class EnumMapDemo {
  5. public static void main(String[] args) {
  6. Map<Action, String> map =
  7. new EnumMap<Action, String>(Action.class);
  8. map.put(Action.TURN_LEFT, "向左轉");
  9. map.put(Action.TURN_RIGHT, "向右轉");
  10. map.put(Action.SHOOT, "射擊");
  11. for(Action action : Action.values( ) ) {
  12. // 以Action列舉為鍵取出值
  13. System.out.println(map.get(action));
  14. }
  15. }
  16. }

執行結果:

  1. 向左轉
  2. 向右轉
  3. 射擊

與單純的使用 HashMap 比較起來的差別是,在範例 13.21 中,EnumMap 將根據列舉的順序來維護物件的排列順序,從範例 13.22 可以看出來。

範例 13.22 EnumMapDemo2.java

  1. package onlyfun.caterpillar;
  2. import java.util.*;
  3. enum Action {TURN_LEFT, TURN_RIGHT, SHOOT}
  4. public class EnumMapDemo2 {
  5. public static void main(String[] args) {
  6. Map<Action, String> map =
  7. new EnumMap<Action, String>(Action.class);
  8. map.put(Action.SHOOT, "射擊");
  9. map.put(Action.TURN_RIGHT, "向右轉");
  10. map.put(Action.TURN_LEFT, "向左轉");
  11. // 顯示Map內容
  12. for(String value : map.values( ) ) {
  13. System.out.println(value);
  14. }
  15. }
  16. }

執行結果:

  1. 向左轉
  2. 向右轉
  3. 射擊

從執行的結果可以看出,EnumMap 容器中的物件順序是根據列舉順序來排列的。

13.3 接下來的主題

每一個章節的內容由淺至深,初學者該掌握的深度要到哪呢?在這個章節中,對於初學者我建議至少掌握以下幾點內容:

  • ArrayList 的使用
  • LinkedList 的使用
  • HashMap 的使用

下一個章節將來介紹 Java 中的輸入輸出,主要會介紹檔案輸入輸出,以讓您瞭解如何將程式執行結果儲存下來,以利下一次程式再開啟時使用,藉由檔案的輸入輸出,也可以一同瞭解 Java 的輸出輸入處理方式。