Java 8 引入了 Stream API,使得處理集合數據變得更加簡潔和高效。Stream API 允許開發者以聲明式編程風格操作數據集合,而不是使用傳統的迭代和條件語句。
一、基本概念
1.1 什么是 Stream
Stream 是 Java 8 中的一個新抽象,它允許對集合數據執行各種復雜的操作,例如過濾、映射、規約、收集等。Stream 不存儲數據,而是從集合或其他數據源(如數組、I/O channel 等)中獲取數據并進行操作。
Stream 的主要特點包括:
- 無存儲:Stream 不存儲數據,只是對數據進行操作。
- 函數式編程:使用 lambda 表達式進行操作,使代碼更簡潔。
- 延遲執行:Stream 操作是懶加載的,只有在需要結果時才會執行。
- 可組合性:多個 Stream 操作可以連成一串操作鏈,形成一系列的轉換。
1.2 Stream 的生命周期
Stream 的操作可以分為三類:
- 源:創建 Stream 的數據源,例如集合、數組或 I/O channel。
- 中間操作:返回新的 Stream 的操作,例如過濾、映射。
- 終端操作:產生結果或副作用的操作,例如收集、計算。
一個 Stream 的生命周期可以簡單描述為:
- 創建 Stream。
- 中間操作。
- 終端操作。
二、Stream API 的基本操作
2.1 創建 Stream
Stream 可以通過以下幾種方式創建:
- 從集合:
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
- 從數組:
String[] array = {"a", "b", "c"};
Stream<String> stream = Arrays.stream(array);
- 從值:
Stream<String> stream = Stream.of("a", "b", "c");
- 從文件:
Stream<String> stream = Files.lines(Paths.get("path/to/file.txt"));
2.2 中間操作
中間操作返回一個新的 Stream,它們是延遲執行的,只有在終端操作執行時才會實際進行計算。常用的中間操作包括:
2.2.1 filter
filter
用于對 Stream 中的元素進行過濾,只保留滿足條件的元素。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> evenNumbers = numbers.stream().filter(n -> n % 2 == 0);
2.2.2 map
map
用于將 Stream 中的每個元素映射到另一個元素。
List<String> words = Arrays.asList("Java", "Stream", "API");
Stream<Integer> wordLengths = words.stream().map(String::length);
2.2.3 flatMap
flatMap
用于將 Stream 中的每個元素映射到一個新的 Stream,并將這些新 Stream 合并成一個 Stream。
List<List<String>> listOfLists = Arrays.asList(Arrays.asList("a", "b"), Arrays.asList("c", "d"));
Stream<String> flatStream = listOfLists.stream().flatMap(Collection::stream);
2.2.4 distinct
distinct
用于去除 Stream 中的重復元素。
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 5);
Stream<Integer> distinctNumbers = numbers.stream().distinct();
2.2.5 sorted
sorted
用于對 Stream 中的元素進行排序,可以傳遞一個比較器。
List<String> words = Arrays.asList("Java", "Stream", "API");
Stream<String> sortedWords = words.stream().sorted();
2.3 終端操作
終端操作會觸發 Stream 的計算,并生成結果或副作用。常用的終端操作包括:
2.3.1 forEach
forEach
用于對 Stream 中的每個元素執行一個動作。
List<String> words = Arrays.asList("Java", "Stream", "API");
words.stream().forEach(System.out::println);
2.3.2 toArray
toArray
用于將 Stream 中的元素收集到一個數組中。
List<String> words = Arrays.asList("Java", "Stream", "API");
String[] array = words.stream().toArray(String[]::new);
2.3.3 reduce
reduce
用于將 Stream 中的元素通過一個關聯函數組合起來,生成一個值。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream().reduce(0, Integer::sum);
2.3.4 collect
collect
用于將 Stream 中的元素收集到一個容器中,例如 List、Set 或 Map。
List<String> words = Arrays.asList("Java", "Stream", "API");
List<String> upperCaseWords = words.stream().map(String::toUpperCase).collect(Collectors.toList());
2.3.5 count
count
用于返回 Stream 中的元素數量。
List<String> words = Arrays.asList("Java", "Stream", "API");
long count = words.stream().count();
2.3.6 findFirst
和 findAny
findFirst
用于返回 Stream 中的第一個元素(如果存在)。
List<String> words = Arrays.asList("Java", "Stream", "API");
Optional<String> first = words.stream().findFirst();
findAny
用于返回 Stream 中的任意一個元素(如果存在),常用于并行流。
List<String> words = Arrays.asList("Java", "Stream", "API");
Optional<String> any = words.stream().findAny();
2.3.7 anyMatch
、allMatch
和 noneMatch
這三個操作用于檢查 Stream 中是否有任意、所有或沒有元素滿足指定的條件。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
boolean anyEven = numbers.stream().anyMatch(n -> n % 2 == 0);
boolean allEven = numbers.stream().allMatch(n -> n % 2 == 0);
boolean noneNegative = numbers.stream().noneMatch(n -> n < 0);
三、并行流
Java 8 提供了并行流,可以充分利用多核處理器的優勢。只需調用 parallelStream
方法即可創建一個并行流。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.parallelStream().reduce(0, Integer::sum);
并行流通過將數據分成多個子流,并在不同的 CPU 核心上并行處理這些子流,然后再合并結果,來提高處理速度。需要注意的是,并行流適合于無狀態和無副作用的操作,使用時需小心處理共享變量和同步問題。
四、Stream API 的最佳實踐
4.1 使用 Lambda 表達式
Stream API 通常與 lambda 表達式一起使用,使代碼更加簡潔和易讀。例如:
List<String> words = Arrays.asList("Java", "Stream", "API");
List<String> upperCaseWords = words.stream().map(word -> word.toUpperCase()).collect(Collectors.toList());
4.2 避免使用修改狀態的中間操作
Stream 操作應該是無副作用的,即不應修改外部狀態。以下示例展示了一個錯誤的用法:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> results = new ArrayList<>();
numbers.stream().forEach(n -> results.add(n * 2)); // 這樣做是錯誤的
正確的做法是使用終端操作 collect
:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> results = numbers.stream().map(n -> n * 2).collect(Collectors.toList());
4.3 利用方法引用
方法引用可以使代碼更加簡潔。例如,使用方法引用替代 lambda 表達式:
List<String> words = Arrays.asList("Java", "Stream", "API");
List<String> upperCaseWords = words.stream().map(String::toUpperCase).collect(Collectors.toList());
4.4 避免使用并行流進行小任務
并行流在處理大量數據或復雜計算時非常高效,但對于小任務,啟動并行計算的開銷可能會大于收益。因此,在數據量較小或計算較簡單的情況下,優先使用順序流。
4.5 避免在終端操作之前調用 findAny
在終端操作之前調用 findAny
會導致流的中間操作鏈被截斷,進而無法正確執行后續的操作。例如:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> result = numbers.stream().filter(n -> n % 2 == 0).findAny(); // 這樣做會中斷流
應將 findAny
用作終端操作:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> result = numbers.stream().filter(n -> n % 2 == 0).findAny();
4.6 使用 collect
進行結果收集
collect
是一個強大的終端操作,可以將流中的元素收集到各種容器中。例如,收集到 List:
List<String> words = Arrays.asList("Java", "Stream", "API");
List<String> wordList = words.stream().collect(Collectors.toList());
4.7 使用 Collectors
進行復雜收集操作
Collectors
提供了多種收集器,可以進行復雜的結果收集。例如,收集到 Map:
List<String> words = Arrays.asList("Java", "Stream", "API");
Map<Integer, List<String>> wordLengthMap = words.stream().collect(Collectors.groupingBy(String::length));
4.8 使用 Optional
處理可能的空值
Stream API 中的某些終端操作會返回 Optional
,例如 findFirst
、findAny
。使用 Optional
可以避免空指針異常:
List<String> words = Arrays.asList("Java", "Stream", "API");
Optional<String> firstWord = words.stream().findFirst();
firstWord.ifPresent(System.out::println);
Java 8 的 Stream API 為集合數據的處理提供了一種高效、簡潔的方式。通過理解和掌握 Stream 的基本概念、常用操作以及最佳實踐,可以大大提高 Java 開發的生產力和代碼質量。
Stream API 不僅支持順序流,還支持并行流,使得在多核環境下處理大量數據變得更加高效。在實際開發中,合理使用 Stream API 可以顯著提升代碼的可讀性和穩定性。
黑馬程序員免費預約咨詢