每個 Java 範例都是獨立的 Main.java,可用 JDK 11 編譯執行;請一次複製一個範例。題庫使用 public class Solution 中的 public static solve(...) 方法,請保留題目指定的型別與參數,使用 return 交回答案,不必另外讀取輸入或撰寫 main。
01|先看資料需求,再挑集合
陣列長度固定,集合可依實作動態增減元素。Collection 是 List、Set、Queue 等介面的共同上層;Map 保存 key-value 配對,屬於集合架構,但沒有繼承 Collection。Collection 是介面;Collections 則是提供排序等靜態工具方法的類別。
- List:元素有索引與順序,允許重複;常用 ArrayList。
- Set:元素不重複;HashSet 無指定走訪順序,LinkedHashSet 保留插入順序,TreeSet 依比較規則排序。
- Map:key 不可重複,每個 key 對應一個 value;不同 key 可以保存相同 value。
- Queue:依規則取出待處理元素。LinkedList 可當 FIFO 佇列;PriorityQueue 依優先順序取出。
教材對照:PDF 第 2–5、10–11、26 頁
02|泛型與 Collection 常用操作
泛型把元素型別寫在角括號,例如 Collection<String>,讓編譯器檢查放入與取出的資料。集合保存物件,整數使用 Integer;自動裝箱讓 add(11) 可轉成 Integer。元素不會因放入集合就失去本身的執行時類別。
- add/addAll/remove/removeAll/retainAll 的 boolean 回傳值表示集合是否改變;不是通用的成功/失敗代碼。
- containsAll 檢查是否包含指定元素;retainAll 只留下交集元素,removeAll 移除指定集合中的元素。
- 某些集合不支援修改,可能拋出 UnsupportedOperationException,例如 List.of 建立的不可修改清單。
- List 的相等比較考慮元素與順序;Set 的相等比較考慮元素內容,不是只要同屬 Set 就相等。
import java.util.Collection;
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
Collection<String> topics = new ArrayList<>();
topics.add("Java");
topics.add("Python");
System.out.println(topics.contains("Java"));
System.out.println(topics.size());
System.out.println(topics.remove("Python"));
System.out.println(topics);
topics.clear();
System.out.println(topics.isEmpty());
}
}true 2 true [Java] true
教材對照:PDF 第 2、4–5、8 頁
03|List 與 ArrayList:索引操作
List 最像可增減的陣列。get 依索引取值,set 替換原位置,add(index, value) 插入。indexOf 找不到時回傳 -1。ArrayList 適合經常依索引讀取;中間插入或移除通常需要移動後面的元素。
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
List<Integer> scores = new ArrayList<>(Arrays.asList(70, 80, 70));
scores.add(1, 90);
scores.set(0, 75);
System.out.println(scores);
System.out.println(scores.remove(1));
System.out.println(scores.remove(Integer.valueOf(70)));
System.out.println(scores);
System.out.println(scores.indexOf(99));
}
}[75, 90, 80, 70] 90 true [75, 80] -1
教材對照:PDF 第 17–19 頁
04|Iterator 安全走訪與移除
Iterator 用 hasNext 檢查是否還有下一筆,next 取下一筆。需要邊走訪邊移除時,使用這個走訪器的 remove;每次 next 後最多移除一次。一般增強 for 中直接呼叫原清單 remove,可能引發 ConcurrentModificationException。
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
public class Main {
public static void main(String[] args) {
List<Integer> values = new ArrayList<>(Arrays.asList(1, 2, 3, 4));
Iterator<Integer> iterator = values.iterator();
while (iterator.hasNext()) {
int value = iterator.next();
if (value % 2 == 0) {
iterator.remove();
}
}
System.out.println(values);
}
}[1, 3]
教材對照:PDF 第 7–9 頁
05|ListIterator 與 Enumeration
ListIterator 支援 List 的雙向走訪,也可在允許修改的清單中 add、set、remove。nextIndex/previousIndex 只回報位置,不會移動游標。較早的 Enumeration 常見於 Vector、Hashtable,使用 hasMoreElements/nextElement,沒有移除方法;它也不是物件序列化。
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.ListIterator;
import java.util.Vector;
import java.util.Enumeration;
public class Main {
public static void main(String[] args) {
List<String> names = new ArrayList<>(Arrays.asList("A", "B"));
ListIterator<String> iterator = names.listIterator(names.size());
while (iterator.hasPrevious()) {
System.out.println(iterator.previous());
}
Vector<String> oldList = new Vector<>(names);
Enumeration<String> elements = oldList.elements();
while (elements.hasMoreElements()) {
System.out.println(elements.nextElement());
}
}
}B A A B
教材對照:PDF 第 6–9、19 頁
06|Set 的唯一性與插入順序
HashSet 依 hashCode 與 equals 協同處理唯一性,不保證走訪順序。LinkedHashSet 從一開始就保留插入順序,同一元素再次加入不會往後移動。若先放入 HashSet 再轉 LinkedHashSet,已經失去的原始插入順序不會自動恢復。
import java.util.Set;
import java.util.LinkedHashSet;
public class Main {
public static void main(String[] args) {
Set<String> topics = new LinkedHashSet<>();
System.out.println(topics.add("Java"));
topics.add("Python");
System.out.println(topics.add("Java"));
System.out.println(topics);
System.out.println(topics.size());
}
}true false [Java, Python] 2
教材對照:PDF 第 10–14 頁
07|TreeSet 與排序範圍
TreeSet 依元素的自然順序或建構時提供的 Comparator 排序;自然順序由 Comparable.compareTo 定義,不是 equals。比較結果為 0 的元素在 TreeSet 中視為同一元素;應讓比較規則與 equals 一致。數字按數值、字串按字串比較規則,不能一概視為字母排序。
import java.util.TreeSet;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
TreeSet<Integer> values = new TreeSet<>(Arrays.asList(8, 3, 5, 3));
System.out.println(values);
System.out.println(values.first());
System.out.println(values.last());
System.out.println(values.headSet(5));
System.out.println(values.subSet(3, 8));
System.out.println(values.tailSet(5));
}
}[3, 5, 8] 3 8 [3] [3, 5] [5, 8]
教材對照:PDF 第 14–16 頁
08|LinkedList、Queue、Stack 與 Deque
LinkedList 可作 List 或雙端佇列。它在已知節點位置的插入、移除有優勢,但依索引尋找位置仍要走訪,不能認為所有增刪都必定比 ArrayList 快。FIFO 先進先出;LIFO 後進先出。教材中的 Stack 繼承 Vector;Java 11 的新程式也可用 Deque 表達堆疊。
- Queue 的 peek 只看頭部、poll 取出並移除;空佇列都回傳 null。element/remove 在空佇列會拋出例外。
- offer 新增元素,以特殊回傳值表達容量限制;add 在無法新增時可能拋出例外,並非每次呼叫都會失敗。
- Stack 的 push 新增,pop 取出並移除,peek 只看頂端;空 Stack 的 pop/peek 會拋出 EmptyStackException。
- Vector、Hashtable 等舊集合有同步方法,但『先檢查再操作』等多步行為不因此自動成為不可分割的操作。
import java.util.Queue;
import java.util.LinkedList;
import java.util.Deque;
import java.util.ArrayDeque;
public class Main {
public static void main(String[] args) {
Queue<String> queue = new LinkedList<>();
queue.offer("A");
queue.offer("B");
System.out.println(queue.peek());
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
Deque<String> stack = new ArrayDeque<>();
stack.push("A");
stack.push("B");
System.out.println(stack.peek());
System.out.println(stack.pop());
}
}A A B null B B
教材對照:PDF 第 19–24 頁
09|PriorityQueue 與 Comparator
PriorityQueue 的頭部是依比較規則最優先的元素,因此它不是一般 FIFO。要取得完整排序結果,應反覆 poll;直接印整個 PriorityQueue 或使用 iterator,不保證所有元素依排序列出。Comparator.compare 回傳負數、0、正數表示前後關係。
import java.util.PriorityQueue;
import java.util.Comparator;
public class Main {
public static void main(String[] args) {
PriorityQueue<Integer> queue = new PriorityQueue<>(Comparator.reverseOrder());
queue.offer(3);
queue.offer(8);
queue.offer(5);
while (!queue.isEmpty()) {
System.out.println(queue.poll());
}
}
}8 5 3
教材對照:PDF 第 24–25 頁
10|Map:用 key 存取 value
Map 的 put(key, value) 新增配對;key 已存在時會取代 value,並回傳舊值。get 取得值,containsKey 檢查 key 是否存在。HashMap 不保證走訪順序;LinkedHashMap 預設保留插入順序,也可透過特定建構子改成存取順序。
- keySet 回傳 key 的 Set 檢視;values 回傳 value 的 Collection 檢視;entrySet 回傳配對的 Set 檢視。
- Map 本身沒有 iterator 方法,但可透過 entrySet 等檢視走訪。
- HashMap/LinkedHashMap 可有 null key 與 null value;若 get 回傳 null,可再用 containsKey 區分沒有 key 或值本來就是 null。
- Hashtable 不接受 null key 或 value。避免把容器的 null 規則套用到所有 Map。
import java.util.Map;
import java.util.LinkedHashMap;
public class Main {
public static void main(String[] args) {
Map<String, Integer> scores = new LinkedHashMap<>();
scores.put("Amy", 80);
scores.put("Ben", 70);
System.out.println(scores.put("Amy", 95));
System.out.println(scores.get("Amy"));
System.out.println(scores.get("Cindy"));
System.out.println(scores.containsKey("Cindy"));
for (Map.Entry<String, Integer> entry : scores.entrySet()) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
}
}80 95 null false Amy=95 Ben=70
教材對照:PDF 第 26–29 頁
11|TreeMap 依 key 排序與頻率統計
TreeMap 依 key 的自然順序或 Comparator 排序,value 不參與排序且可以重複。firstKey/lastKey 取得兩端,headMap/tailMap/subMap 依 key 範圍建立檢視,邊界概念與 TreeSet 相同。以下以字串為 key 記錄出現次數,再按 key 順序輸出。
import java.util.TreeMap;
public class Main {
public static void main(String[] args) {
TreeMap<String, Integer> counts = new TreeMap<>();
String[] words = {"java", "python", "java"};
for (String word : words) {
counts.put(word, counts.getOrDefault(word, 0) + 1);
}
System.out.println(counts);
System.out.println(counts.firstKey());
System.out.println(counts.headMap("python"));
counts.put("empty", null);
System.out.println(counts.containsKey("empty"));
}
}{java=2, python=1}
java
{java=2}
true教材對照:PDF 第 29–31 頁
WHEN SOMETHING GOES WRONG
遇到錯誤,先檢查這裡
假設 HashSet/HashMap 每次依加入順序輸出
這兩種實作不保證走訪順序;保留插入順序用 LinkedHashSet/LinkedHashMap,依值或 key 排序用 TreeSet/TreeMap。
把泛型寫成 List<int>
泛型參數是參考型別;整數集合使用 List<Integer>,注意 null 拆箱會產生 NullPointerException。
用 PriorityQueue 的 toString 當排序答案
堆積只保證頭部符合優先規則;應持續 poll 取出,才得到依比較規則排列的序列。
PAUSE AND TRY
先不要急著看別人的寫法
- 分別用 ArrayList、LinkedHashSet、TreeSet 放入 3、1、3、2,預測內容。
- 將佇列中的 peek 改為 poll,下一次取出結果會有什麼變化?
- 統計字詞次數時,為什麼需要讀取舊次數再加 1?
先用紙筆預測,再改動範例中的數字或文字。結果與預期不同時,找出最早開始不同的那一步。
把這幾件事帶走
- 先決定順序、重複、排序與 key 的需求。
- 集合用泛型清楚表示元素型別,操作前確認是否支援修改。
- 依正確走訪規則取得資料,別依賴未保證的容器順序。
FROM READING TO PRACTICE
用三道免費題,練習本章觀念
先做簡單題確認語法,再把幾個步驟組合起來。三題都來自本章主題的 Java 免費題庫,難度表示需要組合的基本概念。
教材來源與 Java 11 對照
本章依據《第二部分 05 Java集合架構.pdf》PDF 第 2–31 頁整理,將原有觀念重新編寫為可閱讀與執行的網站教學。頁碼為 PDF 檔案頁次;舊版語法說明與已知誤植依 Java 11 修正,補充內容另有標示。
需要查閱更完整的規則時,可參考以下官方文件。