Java Stream 終端操作

終端操作會觸發 Stream 的執行並產生結果,每個 Stream 只能有一個終端操作。

終端操作特性

  • 觸發執行:呼叫後才會執行整個 Stream pipeline
  • 消耗 Stream:執行後 Stream 無法再使用
  • 產生結果:回傳值或副作用

forEach - 遍歷

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

// 遍歷
names.stream().forEach(System.out::println);

// 簡化(直接使用集合的 forEach)
names.forEach(System.out::println);

// forEachOrdered(保證順序,用於平行 Stream)
names.parallelStream().forEachOrdered(System.out::println);

注意forEach 是為了副作用,應避免修改外部狀態

collect - 收集

最常用的終端操作,將結果收集到集合:

// 收集到 List
List<String> list = stream.collect(Collectors.toList());

// 收集到 Set
Set<String> set = stream.collect(Collectors.toSet());

// 收集到特定集合
ArrayList<String> arrayList = stream.collect(
    Collectors.toCollection(ArrayList::new));

// Java 16+
List<String> list = stream.toList();  // 回傳不可變 List

更多 Collectors 請參考 Java Collectors

toArray - 轉為陣列

// 轉為 Object[]
Object[] array = stream.toArray();

// 轉為特定型別陣列
String[] strings = stream.toArray(String[]::new);

// IntStream 轉為 int[]
int[] ints = IntStream.of(1, 2, 3).toArray();

reduce - 歸約

將所有元素組合成單一結果:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 有初始值
int sum = numbers.stream()
    .reduce(0, (a, b) -> a + b);  // 15

// 無初始值(回傳 Optional)
Optional<Integer> sum = numbers.stream()
    .reduce((a, b) -> a + b);

// 三參數版本(用於平行處理)
Integer sum = numbers.parallelStream()
    .reduce(0, 
            (partial, n) -> partial + n,   // accumulator
            (left, right) -> left + right); // combiner

// 常見用法
int max = numbers.stream().reduce(Integer.MIN_VALUE, Integer::max);
int min = numbers.stream().reduce(Integer.MAX_VALUE, Integer::min);
String concat = strings.stream().reduce("", (a, b) -> a + b);

count - 計數

long count = stream.count();

// 範例
long evenCount = numbers.stream()
    .filter(n -> n % 2 == 0)
    .count();

min / max - 最小/最大值

// 需要 Comparator
Optional<Integer> min = numbers.stream()
    .min(Integer::compareTo);

Optional<Integer> max = numbers.stream()
    .max(Integer::compareTo);

// 物件
Optional<User> youngest = users.stream()
    .min(Comparator.comparing(User::getAge));

Optional<User> oldest = users.stream()
    .max(Comparator.comparing(User::getAge));

// 基本型別 Stream 不需要 Comparator
int min = IntStream.of(1, 2, 3).min().orElse(0);
int max = IntStream.of(1, 2, 3).max().orElse(0);

findFirst / findAny - 找元素

// 找第一個
Optional<String> first = stream.findFirst();

// 找任意一個(平行時可能更快)
Optional<String> any = stream.findAny();

// 實際應用
Optional<User> user = users.stream()
    .filter(u -> u.getEmail().equals(email))
    .findFirst();

anyMatch / allMatch / noneMatch - 匹配

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 任一匹配
boolean hasEven = numbers.stream()
    .anyMatch(n -> n % 2 == 0);  // true

// 全部匹配
boolean allPositive = numbers.stream()
    .allMatch(n -> n > 0);  // true

// 沒有匹配
boolean noNegative = numbers.stream()
    .noneMatch(n -> n < 0);  // true

sum / average(基本型別 Stream)

// sum
int sum = IntStream.of(1, 2, 3, 4, 5).sum();  // 15

// average
OptionalDouble avg = IntStream.of(1, 2, 3, 4, 5).average();  // 3.0

// 物件需要先轉換
int totalAge = users.stream()
    .mapToInt(User::getAge)
    .sum();

double avgAge = users.stream()
    .mapToInt(User::getAge)
    .average()
    .orElse(0);

summaryStatistics - 統計

IntSummaryStatistics stats = numbers.stream()
    .mapToInt(Integer::intValue)
    .summaryStatistics();

long count = stats.getCount();
int sum = stats.getSum();
int min = stats.getMin();
int max = stats.getMax();
double avg = stats.getAverage();

iterator / spliterator

// 轉為 Iterator
Iterator<String> it = stream.iterator();
while (it.hasNext()) {
    System.out.println(it.next());
}

// Spliterator(用於自訂分割)
Spliterator<String> spliterator = stream.spliterator();

短路操作

某些操作不需要處理所有元素:

操作說明
limit達到數量就停止
findFirst找到就停止
findAny找到就停止
anyMatch找到匹配就停止
allMatch找到不匹配就停止
noneMatch找到匹配就停止
// 只處理需要的元素
Stream.iterate(1, n -> n + 1)
    .filter(n -> n % 2 == 0)
    .limit(5)
    .forEach(System.out::println);  // 2, 4, 6, 8, 10

實際範例

計算總價

double total = orders.stream()
    .flatMap(order -> order.getItems().stream())
    .mapToDouble(item -> item.getPrice() * item.getQuantity())
    .sum();

找出最高薪員工

Optional<Employee> topEarner = employees.stream()
    .max(Comparator.comparing(Employee::getSalary));

檢查是否存在

boolean hasAdmin = users.stream()
    .anyMatch(u -> u.getRole().equals("ADMIN"));

統計分析

DoubleSummaryStatistics priceStats = products.stream()
    .mapToDouble(Product::getPrice)
    .summaryStatistics();

System.out.println("商品數:" + priceStats.getCount());
System.out.println("最低價:" + priceStats.getMin());
System.out.println("最高價:" + priceStats.getMax());
System.out.println("平均價:" + priceStats.getAverage());
System.out.println("總價值:" + priceStats.getSum());

操作對照表

操作回傳型別說明
forEachvoid遍歷
collectR收集到集合
toArrayT[]轉為陣列
reduceT/Optional歸約
countlong計數
min/maxOptional最小/最大
findFirstOptional第一個
findAnyOptional任意一個
anyMatchboolean任一匹配
allMatchboolean全部匹配
noneMatchboolean沒有匹配
sumint/long/double求和
averageOptionalDouble平均值

重點整理

  • 終端操作觸發 Stream 執行
  • collect 是最常用的終端操作
  • reduce 用於將元素歸約為單一結果
  • 使用 Optional 處理可能為空的結果
  • 基本型別 Stream 有 sum()average() 等便利方法
  • 短路操作可以提早結束,提高效能