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 排序(有狀態,會等待所有元素)
  • limitskip 用於分頁
  • peek 用於除錯,不應有副作用