1. 前言
在程式設計中,我們經常需要從大量資料中只挑出需要的元素——這就是篩選。想要只保留偶數、找出包含 "Java" 一詞的字串,或是選出年齡大於 18 歲的使用者——這些都是篩選的任務。
在 Java 中,這類操作隨處可見,因此熟悉各種做法並理解其差異非常重要。
2. 用迴圈進行篩選
我們先從最基本的方法——一般的 for 迴圈。這種做法稱為命令式,因為你會明確地指示要做什麼以及怎麼做。它可讀性高,易於理解。
範例:只保留偶數
import java.util.*;
public class FilterExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evenNumbers = new ArrayList<>(); // 建立一個新的清單用來存放結果
for (Integer n : numbers) {
if (n % 2 == 0) { // 檢查條件:偶數
evenNumbers.add(n);
}
}
System.out.println("偶數: " + evenNumbers);
}
}
結果:
偶數: [2, 4, 6, 8, 10]
這裡要記住:原始集合(numbers)不會改變。我們是建立一個新的結果清單。
範例:依子字串篩選字串
List<String> words = Arrays.asList("java", "python", "javascript", "kotlin", "c++");
List<String> javaWords = new ArrayList<>();
for (String word : words) {
if (word.contains("java")) {
javaWords.add(word);
}
}
System.out.println(javaWords); // [java, javascript]
同樣的原理:遍歷字串清單,並用 contains 方法檢查是否包含子字串。
3. 從集合中刪除元素:為什麼沒那麼簡單?
陷阱在哪裡?
有時我們想刪除原始集合中所有不需要的元素。但如果在 for-each 迴圈遍歷時這麼做,你會得到例外 ConcurrentModificationException。
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, -2, 3, -4, 5));
for (Integer n : numbers) {
if (n < 0) {
numbers.remove(n); // 危險!ConcurrentModificationException!
}
}
結果:
Exception in thread "main" java.util.ConcurrentModificationException
集合在這種遍歷過程中被修改會導致問題——迭代會失效。
如何正確地從集合中刪除元素?
方法一:使用 Iterator.remove()
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, -2, 3, -4, 5));
Iterator<Integer> it = numbers.iterator();
while (it.hasNext()) {
Integer n = it.next();
if (n < 0) {
it.remove(); // 安全刪除!
}
}
System.out.println(numbers); // [1, 3, 5]
在這裡我們透過 iterator() 取得迭代器,使用 hasNext() 與 next() 在集合中移動,並用 it.remove() 刪除不需要的元素。
方法二:建立只包含需要元素的新清單
List<Integer> numbers = Arrays.asList(1, -2, 3, -4, 5);
List<Integer> positive = new ArrayList<>();
for (Integer n : numbers) {
if (n >= 0) {
positive.add(n);
}
}
System.out.println(positive); // [1, 3, 5]
我們不動原始集合,改為收集到新的清單。這種做法安全、直觀,且在條件變複雜時也容易擴充。
方法三:使用 removeIf(Java 8+)
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, -2, 3, -4, 5));
numbers.removeIf(n -> n < 0);
System.out.println(numbers); // [1, 3, 5]
只用一行就能傳入條件:「刪掉所有小於 0 的元素」。至於如何安全地修改,集合會在內部替你處理。
總結:要刪除元素時不要使用 for-each。請在 Iterator.remove()、建立新清單,或精煉的 removeIf 之間做選擇。
4. 什麼時候用哪種篩選方式?
透過一般迴圈的命令式做法很適合在你不僅要篩選,還要立即對元素做些處理時(例如輸出或轉換)。它簡單且透明。
若是要從原始集合中刪除元素,請避免在 for-each 中刪除。需要逐步控制時使用 Iterator.remove(),或者採用現代的 removeIf()——最簡短且表意清楚的方式。
範例:有一個字詞清單,需要刪除所有少於四個字元的字詞:
List<String> words = new ArrayList<>(Arrays.asList("Java", "is", "fun", "awesome", "code"));
words.removeIf(word -> word.length() < 4);
System.out.println(words); // [Java, awesome, code]
方法 removeIf 接收一個 predicate(篩選規則),並刪除所有符合該規則的元素。
5. 集合篩選的常見錯誤
錯誤 №1:在 for-each 迴圈中嘗試從集合刪除元素。
這樣的程式會導致 ConcurrentModificationException:
for (Integer n : numbers) {
if (n < 0) {
numbers.remove(n); // 砰!ConcurrentModificationException
}
}
正確作法:使用 Iterator.remove() 或 removeIf。
錯誤 №2:篩選條件寫錯。
需求是只刪除負數:
List<Integer> numbers = new ArrayList<>(Arrays.asList(-3, -1, 0, 2, 4));
numbers.removeIf(n -> n < 0);
System.out.println(numbers); // [0, 2, 4]
但如果誤寫為「非正數」並使用 <=,那麼 0 也會被刪掉:
numbers.removeIf(n -> n <= 0); // 結果: [2, 4] — 0 被錯誤地刪除了
請務必精確撰寫條件:只要一個比較寫錯,結果就會完全不同。
GO TO FULL VERSION