文章目錄
- **一、Stream 流簡介**
- **二、Stream 流核心操作**
- **1. 創建 Stream**
- **2. 中間操作(Intermediate Operations)**
- **filter(Predicate<T>):過濾數據**
- **1. 簡單條件過濾**
- **2. 多條件組合**
- **3. 過濾對象集合**
- **4. 過濾 `null` 值**
- **2. map(Function<T, R>):轉換元素**
- **一、`map` 的核心特性**
- **2. 提取對象屬性**
- **3. 復雜轉換邏輯**
- **3. flatMap(Function<T, Stream<R>>):扁平化嵌套結構**
- **4. distinct():去重**
- **5. sorted():排序**
- **6. limit(n) 和 skip(n):分頁控制**
- **三、高級用法**
- **1. 分組與分區**
- **2. 并行流處理**
- **3. 原始類型流**
- **四、實際應用場景示例**
- **1. 數據轉換與過濾**
- **2. 統計與匯總**
- **3. 復雜集合處理**
- **4. 分組統計**
- **五、注意事項與最佳實踐**
- **六、與傳統循環的對比**
一、Stream 流簡介
Java 8 引入的 Stream
提供了一種高效、聲明式處理集合數據的方式,支持順序和并行操作,核心特點包括:
- 鏈式調用:通過組合中間操作(如
filter
,map
)和終端操作(如collect
,forEach
)實現復雜邏輯。 - 延遲執行:只有終端操作觸發時才會執行中間操作。
- 不可重用:每個流只能被消費一次。
二、Stream 流核心操作
1. 創建 Stream
-
集合創建:
Collection.stream()
或parallelStream()
。List<String> list = Arrays.asList("a", "b", "c"); Stream<String> stream = list.stream();
-
數組創建:
Arrays.stream(array)
。String[] arr = {"a", "b", "c"}; Stream<String> stream = Arrays.stream(arr);
-
靜態方法:
Stream.of()
或生成無限流Stream.iterate()
,Stream.generate()
。Stream<Integer> numbers = Stream.of(1, 2, 3); Stream<Integer> infiniteStream = Stream.iterate(0, n -> n + 2); // 0, 2, 4, ...
2. 中間操作(Intermediate Operations)
filter(Predicate):過濾數據
作用:根據條件篩選元素,保留滿足條件的元素。
示例:
List<String> filtered = list.stream().filter(s -> s.startsWith("a")) // 保留以"a"開頭的字符串.collect(Collectors.toList());
最佳實踐:
1. 簡單條件過濾
篩選出符合條件的元素。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);// 篩選所有偶數
List<Integer> evenNumbers = numbers.stream().filter(n -> n % 2 == 0).collect(Collectors.toList()); // [2, 4, 6]
2. 多條件組合
使用邏輯運算符 &&
(與)、||
(或)組合條件。
List<String> words = Arrays.asList("apple", "banana", "cherry", "date");// 篩選長度大于5 且 以字母a開頭的單詞
List<String> result = words.stream().filter(s -> s.length() > 5 && s.startsWith("b")).collect(Collectors.toList()); // ["banana"]
3. 過濾對象集合
根據對象屬性篩選數據。
class User {String name;int age;// 構造方法、getter/setter 省略
}List<User> users = Arrays.asList(new User("Alice", 25),new User("Bob", 17),new User("Charlie", 30)
);// 篩選年齡 >= 18 的用戶
List<User> adults = users.stream().filter(user -> user.getAge() >= 18).collect(Collectors.toList()); // [Alice, Charlie]
4. 過濾 null
值
使用 Objects::nonNull
過濾掉 null
元素。
List<String> listWithNulls = Arrays.asList("a", null, "b", null, "c");// 過濾掉所有 null 值
List<String> nonNullList = listWithNulls.stream().filter(Objects::nonNull).collect(Collectors.toList()); // ["a", "b", "c"]
2. map(Function<T, R>):轉換元素
map
是 Java Stream 中最核心的中間操作之一,用于將流中的元素一對一轉換為另一種形式。它的本質是通過一個函數(Function<T, R>
)對每個元素進行映射,生成新的元素流。以下是 map
的深入解析,涵蓋使用場景、底層機制、最佳實踐與常見問題。
一、map
的核心特性
- 一對一轉換:每個輸入元素對應一個輸出元素,元素數量不變。
- 類型轉換:輸入類型
T
可轉換為任意輸出類型R
(如String
→Integer
)。 - 惰性求值:只有終端操作觸發時才會執行映射邏輯。
- 無副作用:理想情況下,映射函數不修改外部狀態(符合函數式編程原則)。
作用:將元素轉換為另一種類型或提取特定屬性。
1. 簡單類型轉換
// 將字符串轉換為大寫
List<String> upperCaseList = Arrays.asList("apple", "banana", "cherry").stream().map(String::toUpperCase).collect(Collectors.toList()); // ["APPLE", "BANANA", "CHERRY"]
2. 提取對象屬性
// 從User對象中提取name屬性
List<String> names = users.stream().map(User::getName).collect(Collectors.toList());
3. 復雜轉換邏輯
// 將字符串轉換為自定義DTO對象
List<DataDTO> dtos = strings.stream().map(s -> {DataDTO dto = new DataDTO();dto.setValue(s.length());dto.setLabel(s.toUpperCase());return dto;}).collect(Collectors.toList());
3. flatMap(Function<T, Stream>):扁平化嵌套結構
作用:將嵌套集合(如 List<List<T>>
)展開為單一流。
示例:
List<List<String>> nestedList = Arrays.asList(Arrays.asList("a", "b"),Arrays.asList("c", "d")
);
List<String> flatList = nestedList.stream().flatMap(Collection::stream) // 將每個List<String>轉換為Stream<String>.collect(Collectors.toList()); // ["a", "b", "c", "d"]
典型場景:
- 處理數據庫查詢的多表關聯結果。
- 合并多個API響應的數據列表。
4. distinct():去重
作用:基于 equals()
和 hashCode()
去重。
示例:
List<Integer> unique = numbers.stream().distinct().collect(Collectors.toList()); // [1, 2, 3]
關鍵點:
-
自定義對象去重:需重寫
equals()
和hashCode()
。class User {private Long id;@Overridepublic boolean equals(Object o) { /* 基于id比較 */ }@Overridepublic int hashCode() { /* 基于id生成 */ } }
-
性能注意:對大數據集去重可能消耗內存,可結合
limit
分批次處理。
5. sorted():排序
作用:按自然順序或自定義比較器排序。
示例:
List<String> sorted = list.stream().sorted(Comparator.reverseOrder()) // 逆序排序.collect(Collectors.toList());
優化建議:
- 盡早過濾:先
filter
減少待排序數據量。 - 避免頻繁排序:對需要多次排序的場景,考慮轉換為有序集合(如
TreeSet
)。
6. limit(n) 和 skip(n):分頁控制
作用:skip
跳過前N個元素,limit
限制返回數量。
示例:
List<Integer> result = Stream.iterate(0, n -> n + 1).skip(5) // 跳過0-4,從5開始.limit(10) // 取5-14.collect(Collectors.toList());
應用場景:
-
分頁查詢:模擬數據庫分頁。
int page = 2, size = 10; List<User> users = allUsers.stream().skip((page - 1) * size).limit(size).collect(Collectors.toList());
-
性能注意:對非順序流(如并行流),
skip
和limit
可能無法保證預期結果。 -
3. 終端操作(Terminal Operations)
-
遍歷元素:
forEach(Consumer<T>)
list.stream().forEach(System.out::println);
-
收集結果:
collect(Collector)
List<String> list = stream.collect(Collectors.toList()); Set<String> set = stream.collect(Collectors.toSet()); String joined = stream.collect(Collectors.joining(", "));
-
統計數量:
count()
long count = list.stream().filter(s -> s.length() > 3).count();
-
匹配檢查:
anyMatch(Predicate<T>)
:至少一個元素匹配。allMatch(Predicate<T>)
:所有元素匹配。noneMatch(Predicate<T>)
:沒有元素匹配。
boolean hasA = list.stream().anyMatch(s -> s.contains("a"));
-
查找元素:
findFirst()
:返回第一個元素(Optional<T>
)。findAny()
:適用于并行流,返回任意元素。
Optional<String> first = list.stream().findFirst();
-
歸約操作:
reduce(BinaryOperator<T>)
Optional<Integer> sum = Stream.of(1, 2, 3).reduce(Integer::sum); // 6
三、高級用法
1. 分組與分區
-
分組:
Collectors.groupingBy()
Map<Integer, List<String>> groupByLength = list.stream().collect(Collectors.groupingBy(String::length)); // 按字符串長度分組
-
分區:
Collectors.partitioningBy()
Map<Boolean, List<String>> partition = list.stream().collect(Collectors.partitioningBy(s -> s.length() > 3)); // 按條件分為兩組
2. 并行流處理
-
創建并行流:
.parallel()
或parallelStream()
。List<String> result = list.parallelStream().filter(s -> s.length() > 3).collect(Collectors.toList());
-
注意事項:
- 確保操作線程安全(如避免修改共享變量)。
- 并行流可能不適用于小數據量或復雜中間操作。
3. 原始類型流
-
避免裝箱開銷:使用
IntStream
,LongStream
,DoubleStream
。IntStream.range(1, 5).forEach(System.out::println); // 1, 2, 3, 4 LongStream.of(10L, 20L).sum();
四、實際應用場景示例
1. 數據轉換與過濾
// 從用戶列表中提取成年用戶的姓名
List<String> adultNames = users.stream().filter(user -> user.getAge() >= 18).map(User::getName).collect(Collectors.toList());
2. 統計與匯總
// 計算訂單總金額
double totalAmount = orders.stream().mapToDouble(Order::getAmount).sum();
3. 復雜集合處理
// 將多個訂單的商品列表合并并去重
Set<String> allProducts = orders.stream().flatMap(order -> order.getProducts().stream()).collect(Collectors.toSet());
4. 分組統計
// 按部門分組統計員工平均工資
Map<String, Double> avgSalaryByDept = employees.stream().collect(Collectors.groupingBy(Employee::getDepartment,Collectors.averagingDouble(Employee::getSalary));
五、注意事項與最佳實踐
- 避免副作用:在流操作中不要修改外部變量,尤其是在并行流中。
- 優先使用無狀態操作:如
filter
,map
比sorted
,distinct
更高效。 - 謹慎使用并行流:僅在數據量大且操作耗時的情況下考慮并行化。
- 減少裝箱開銷:對數值操作使用原始類型流(
IntStream
等)。 - 合理使用短路操作:如
findFirst()
,limit()
可提前終止流處理。
六、與傳統循環的對比
場景 | 傳統循環 | Stream 流 |
---|---|---|
簡單遍歷 | 直接易讀 | 代碼更簡潔,但可能略微性能開銷 |
復雜數據處理 | 需多層嵌套循環,代碼冗長 | 鏈式調用,邏輯清晰 |
并行處理 | 需手動管理線程和同步 | 通過 .parallel() 自動并行化 |
函數式編程支持 | 需額外工具類配合 | 原生支持 Lambda 和方法引用 |
通過掌握 Stream 流的常見用法,可以顯著提升代碼的可讀性和開發效率,尤其在處理集合數據時,能夠以更簡潔的方式實現復雜的數據操作。