Java Stream 中間操作
中間操作回傳新的 Stream,可以連續串接,直到遇到終端操作才會執行。
中間操作特性
- 延遲執行:直到終端操作才會執行
- 可串接:回傳 Stream,可以連續呼叫
- 無狀態/有狀態:有狀態操作需要記住之前的元素
filter - 過濾
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// 過濾偶數
List<Integer> evens = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList()); // [2, 4, 6]
// 多重條件
List<String> result = names.stream()
.filter(s -> !s.isEmpty())
.filter(s -> s.length() > 3)
.collect(Collectors.toList());
// 使用方法參考
List<String> nonEmpty = names.stream()
.filter(Predicate.not(String::isEmpty))
.collect(Collectors.toList());
map - 轉換
List<String> names = Arrays.asList("alice", "bob", "charlie");
// 轉換為大寫
List<String> upper = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList()); // ["ALICE", "BOB", "CHARLIE"]
// 取得長度
List<Integer> lengths = names.stream()
.map(String::length)
.collect(Collectors.toList()); // [5, 3, 7]
// 物件屬性
List<String> emails = users.stream()
.map(User::getEmail)
.collect(Collectors.toList());
mapToInt / mapToLong / mapToDouble
// 避免裝箱
int sum = names.stream()
.mapToInt(String::length)
.sum();
// 轉換為基本型別 Stream
IntStream lengths = names.stream()
.mapToInt(String::length);
flatMap - 扁平化
將巢狀結構展開:
List<List<Integer>> nested = Arrays.asList(
Arrays.asList(1, 2),
Arrays.asList(3, 4),
Arrays.asList(5, 6)
);
// 展開為單一 Stream
List<Integer> flat = nested.stream()
.flatMap(List::stream)
.collect(Collectors.toList()); // [1, 2, 3, 4, 5, 6]
// 實際應用:取得所有訂單的商品
List<Product> allProducts = orders.stream()
.flatMap(order -> order.getProducts().stream())
.collect(Collectors.toList());
// 分割字串
List<String> words = lines.stream()
.flatMap(line -> Arrays.stream(line.split(" ")))
.collect(Collectors.toList());
flatMapToInt / flatMapToLong / flatMapToDouble
int[][] matrix = {{1, 2}, {3, 4}, {5, 6}};
int sum = Arrays.stream(matrix)
.flatMapToInt(Arrays::stream)
.sum(); // 21
distinct - 去重
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3);
List<Integer> unique = numbers.stream()
.distinct()
.collect(Collectors.toList()); // [1, 2, 3]
// 物件去重(需要正確實作 equals 和 hashCode)
List<User> uniqueUsers = users.stream()
.distinct()
.collect(Collectors.toList());
sorted - 排序
List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9);
// 自然排序
List<Integer> sorted = numbers.stream()
.sorted()
.collect(Collectors.toList()); // [1, 1, 3, 4, 5, 9]
// 反向排序
List<Integer> reversed = numbers.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList()); // [9, 5, 4, 3, 1, 1]
// 自訂排序
List<String> byLength = names.stream()
.sorted(Comparator.comparing(String::length))
.collect(Collectors.toList());
// 複合排序
List<User> sortedUsers = users.stream()
.sorted(Comparator.comparing(User::getAge)
.thenComparing(User::getName))
.collect(Collectors.toList());
limit - 限制數量
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 取前 3 個
List<Integer> first3 = numbers.stream()
.limit(3)
.collect(Collectors.toList()); // [1, 2, 3]
// 搭配無限 Stream
List<Integer> randoms = Stream.generate(() -> (int)(Math.random() * 100))
.limit(10)
.collect(Collectors.toList());
skip - 跳過
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 跳過前 2 個
List<Integer> skipped = numbers.stream()
.skip(2)
.collect(Collectors.toList()); // [3, 4, 5]
// 分頁
int page = 2;
int pageSize = 10;
List<User> pageUsers = users.stream()
.skip((page - 1) * pageSize)
.limit(pageSize)
.collect(Collectors.toList());
peek - 查看
不改變元素,用於除錯:
List<Integer> result = numbers.stream()
.peek(n -> System.out.println("Before filter: " + n))
.filter(n -> n > 2)
.peek(n -> System.out.println("After filter: " + n))
.map(n -> n * 2)
.peek(n -> System.out.println("After map: " + n))
.collect(Collectors.toList());
takeWhile / dropWhile(Java 9+)
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 4, 3, 2, 1);
// 取元素直到條件不滿足
List<Integer> taken = numbers.stream()
.takeWhile(n -> n < 4)
.collect(Collectors.toList()); // [1, 2, 3]
// 跳過元素直到條件不滿足
List<Integer> dropped = numbers.stream()
.dropWhile(n -> n < 4)
.collect(Collectors.toList()); // [4, 5, 4, 3, 2, 1]
mapMulti(Java 16+)
展開和轉換的結合:
List<Integer> numbers = Arrays.asList(1, 2, 3);
List<Integer> result = numbers.stream()
.<Integer>mapMulti((n, consumer) -> {
consumer.accept(n);
consumer.accept(n * 2);
})
.collect(Collectors.toList()); // [1, 2, 2, 4, 3, 6]
組合範例
// 找出年齡大於 18 的使用者名稱,按年齡排序,取前 10 個
List<String> result = users.stream()
.filter(u -> u.getAge() > 18)
.sorted(Comparator.comparing(User::getAge))
.limit(10)
.map(User::getName)
.collect(Collectors.toList());
// 處理巢狀結構
List<String> allTags = articles.stream()
.flatMap(article -> article.getTags().stream())
.distinct()
.sorted()
.collect(Collectors.toList());
// 統計單字
Map<String, Long> wordCount = lines.stream()
.flatMap(line -> Arrays.stream(line.split("\\s+")))
.map(String::toLowerCase)
.filter(word -> !word.isEmpty())
.collect(Collectors.groupingBy(
Function.identity(),
Collectors.counting()
));
操作分類
| 操作 | 說明 | 有狀態 |
|---|---|---|
| filter | 過濾 | 無 |
| map | 轉換 | 無 |
| flatMap | 扁平化轉換 | 無 |
| peek | 查看(除錯) | 無 |
| distinct | 去重 | 有 |
| sorted | 排序 | 有 |
| limit | 限制數量 | 有 |
| skip | 跳過 | 有 |
| takeWhile | 條件取值 | 無 |
| dropWhile | 條件跳過 | 無 |
重點整理
- 中間操作是延遲執行的
filter過濾元素map一對一轉換flatMap一對多轉換並展開distinct去重(需要正確的 equals/hashCode)sorted排序(有狀態,會等待所有元素)limit和skip用於分頁peek用於除錯,不應有副作用