Java 8 引入了一個功能強大的工具——Stream API,極大地簡化了對集合的操作。傳統上,Java 程序員習慣使用 for 循環來遍歷集合並進行過濾、映射等操作,這種方式雖然直觀但代碼冗長且難以維護。Stream API 通過流式編程的方式,使得我們能夠以更簡潔和優雅的方式操作集合。
本文將介紹 Java Stream API 的基本概念及其常見的使用場景,幫助你更好地掌握這一工具。
一、什麼是 Stream?
Stream 是一種用於處理集合的高級抽象。它並不是數據結構,而是一種可以在元素上執行聚合操作的流,比如 filter(過濾)、map(映射)、reduce(歸約)等操作。Stream API 的核心思想是聲明式編程,通過鏈式調用來描述數據處理過程。
需要注意的是,Stream 本身並不會存儲數據,它是一個“流”,可以從集合、數組或 I/O 資源中獲取數據,並通過一系列中間操作和終結操作來處理數據。
二、Stream 的基本操作
Stream API 的操作可以分為兩類:
- 中間操作(Intermediate Operations):返回新的 Stream,可以被鏈式調用,但不會觸發實際計算。例如:
filter、map、sorted等。 - 終結操作(Terminal Operations):觸發 Stream 計算並返回結果,比如
forEach、collect、reduce等。一旦執行終結操作,Stream 就會關閉。
示例集合
我們使用一個簡單的整數列表來演示 Stream 的基本操作:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
三、常用的 Stream 操作
1. filter:過濾
filter 方法用於對 Stream 中的元素進行篩選,保留符合條件的元素。它接受一個謂詞(返回 boolean 的 lambda 表達式)作為參數。
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println(evenNumbers); // 輸出:[2, 4, 6, 8, 10]
在上面的代碼中,filter 方法過濾掉了所有的奇數,保留了偶數。
2. map:映射
map 方法將每個元素映射為另一個值,常用於將集合中的元素轉換成其他類型。
List<Integer> squares = numbers.stream()
.map(n -> n * n)
.collect(Collectors.toList());
System.out.println(squares); // 輸出:[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
在這裏,map 方法將每個元素都平方並返回一個新的 Stream。
3. sorted:排序
sorted 方法用於對 Stream 中的元素進行排序。默認是升序排序,也可以傳入自定義比較器來指定排序規則。
List<Integer> sortedNumbers = numbers.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
System.out.println(sortedNumbers); // 輸出:[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
在這個例子中,我們使用 Comparator.reverseOrder() 實現了降序排序。
4. collect:收集
collect 是一個終結操作,用於將 Stream 中的元素收集到某種結果中,比如列表、集合或字符串。Collectors 類提供了一系列工廠方法來方便地執行這種收集操作。
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println(evenNumbers); // 輸出:[2, 4, 6, 8, 10]
上例中,collect 方法將流的元素收集到一個 List 中。
5. reduce:歸約
reduce 操作可以將 Stream 中的所有元素組合成一個結果,它經常用於求和、求積等聚合操作。
int sum = numbers.stream()
.reduce(0, Integer::sum);
System.out.println(sum); // 輸出:55
在這裏,reduce 方法將所有元素相加並返回總和。
四、Stream 的惰性求值
Stream 的中間操作是惰性求值的。這意味着即使鏈式調用了多箇中間操作,也不會立即執行,只有在遇到終結操作時才會執行計算。這種特性使得 Stream 可以進行延遲加載,避免不必要的計算,提高性能。
例如:
List<Integer> processedNumbers = numbers.stream()
.filter(n -> {
System.out.println("Filter: " + n);
return n % 2 == 0;
})
.map(n -> {
System.out.println("Map: " + n);
return n * n;
})
.collect(Collectors.toList());
System.out.println(processedNumbers);
在上面的代碼中,只有在 collect 方法被調用時,filter 和 map 操作才會真正執行。每個元素會依次經過 filter 和 map 處理,輸出如下:
Filter: 1
Filter: 2
Map: 2
Filter: 3
Filter: 4
Map: 4
...
這種惰性求值的特性使得 Stream 能夠避免重複遍歷,提高效率。
五、Stream 的並行處理
Java 8 引入了 parallelStream 方法,允許我們輕鬆地將 Stream 轉換為並行流。在並行流中,多個線程會並行地處理 Stream 中的元素,利用多核 CPU 的優勢來提高性能。
int sumOfSquares = numbers.parallelStream()
.map(n -> n * n)
.reduce(0, Integer::sum);
System.out.println(sumOfSquares); // 輸出:385
在並行流中,map 和 reduce 操作會並行執行,可以顯著縮短處理時間。然而,並行流並不總是能提高性能,在數據量較小或者有複雜依賴的情況下,可能會增加不必要的開銷。
六、Stream API 的應用場景
Stream API 尤其適用於以下場景:
- 數據過濾:快速篩選集合中的元素。
- 數據轉換:將一個類型的數據轉換為另一種類型。
- 數據聚合:求和、求平均數、最值等操作。
- 並行處理:在大量數據處理時,提高處理速度。
例如,假設我們有一個 Person 對象的列表,想要找出所有年齡大於 18 歲的名字,並按年齡排序:
List<Person> people = Arrays.asList(
new Person("Alice", 23),
new Person("Bob", 17),
new Person("Charlie", 19)
);
List<String> adultNames = people.stream()
.filter(person -> person.getAge() > 18)
.sorted(Comparator.comparingInt(Person::getAge))
.map(Person::getName)
.collect(Collectors.toList());
System.out.println(adultNames); // 輸出:[Charlie, Alice]
這種流式處理的方式使代碼更加簡潔明瞭。
七、總結
Java Stream API 提供了一種聲明式、簡潔、高效的方式來處理集合數據。通過 filter、map、sorted 等操作,開發者可以輕鬆地完成複雜的數據處理任務,而不用編寫冗長的 for 循環。Stream API 的惰性求值和並行處理特性也為性能優化提供了支持。
掌握 Stream API 不僅能提升代碼的可讀性,也能顯著提高 Java 應用的開發效率。