1. 計算元素數量: count()
當你處理數字或物件的集合時,幾乎總會遇到要計算某些東西的需求:元素數量、總和、平均值、最大值或最小值。例如,公司中的員工數、所有薪資總和、學生的平均年齡、最昂貴的商品、最大的訂單。隨著 Stream API 的出現,一切變得更簡潔且更具表達力。
運作方式
方法 count() 是 Stream API 的終端運算子,會回傳串流中的元素數量。
long count = employees.stream().count();
如果只想計算符合條件的元素 — 使用過濾器 filter(...):
long richCount = employees.stream()
.filter(e -> e.getSalary() > 100_000)
.count();
我們的應用示例:
假設有一個商品清單:
List<Product> products = List.of(
new Product("牛奶", 80),
new Product("起司", 250),
new Product("麵包", 40)
);
來計算有多少商品價格超過 100:
long expensiveCount = products.stream()
.filter(p -> p.getPrice() > 100)
.count();
System.out.println("高價商品數量: " + expensiveCount);
什麼是 Optional
在 Java 中有一種特殊的容器 — Optional。它是一個包裹物件,裡面可以有值,也可以為空。當計算結果可能不存在且需要明確表達時會使用它。比如,方法 min()、max() 和 average() 在集合為空時可能回傳空結果。此時不是回傳 null,而是回傳 Optional。
Optional 的主要方法
- isPresent() / isEmpty() — 檢查是否有值。
- orElse(預設值) — 回傳值;若為空則回傳預設值。
- orElseThrow() — 若為空則丟出例外。
- ifPresent(...) — 只有在有值時才執行動作。
範例:
OptionalInt min = products.stream()
.mapToInt(Product::getPrice)
.min();
if (min.isPresent()) {
System.out.println("最低價格: " + min.getAsInt());
} else {
System.out.println("清單為空");
}
或更簡潔:
int minValue = min.orElse(-1);
System.out.println("最低價格: " + minValue);
這種做法能避免 NullPointerException,讓程式碼更安全。
2. 數值資料的總和、平均、最小值與最大值
基本型別串流: IntStream, DoubleStream, LongStream
針對數值資料,Stream API 提供了專門的串流:IntStream、DoubleStream、LongStream。它們讓你能輕鬆快速計算總和、平均、最小值與最大值。
轉成數值串流
若要從物件串流取得數值串流,請使用方法 mapToInt、mapToDouble、mapToLong。
int sum = products.stream()
.mapToInt(Product::getPrice)
.sum();
System.out.println("價格總和: " + sum);
方法
- sum() — 所有元素的總和。
- average() — 算術平均(回傳 OptionalDouble)。
- min(), max() — 最小值與最大值(回傳 OptionalInt/OptionalDouble/OptionalLong)。
範例:
// 所有價格的總和
int total = products.stream()
.mapToInt(Product::getPrice)
.sum();
// 平均價格
OptionalDouble avg = products.stream()
.mapToInt(Product::getPrice)
.average();
// 最低與最高價格
OptionalInt min = products.stream()
.mapToInt(Product::getPrice)
.min();
OptionalInt max = products.stream()
.mapToInt(Product::getPrice)
.max();
如何從 Optional 取得值
double average = avg.orElse(0.0); // 如果集合為空則為 0.0
int minValue = min.orElse(-1); // 如果集合為空則為 -1
int maxValue = max.orElse(-1); // 如果集合為空則為 -1
工程師冷笑話:空清單不是錯誤,只是你目前沒有資料而已。但如果你嘗試用 getAsInt() 從空的 Optional 取值 — 就會捕捉到 NoSuchElementException。務必使用 orElse 或 isPresent()!
3. 物件的彙總: Collectors.summingInt, averagingInt, maxBy, minBy
如果你有物件串流,並想依某個屬性做彙總,請使用 Collectors 類別中的專用收集器。
summingInt, averagingInt
int totalSalary = employees.stream()
.collect(Collectors.summingInt(Employee::getSalary));
double avgSalary = employees.stream()
.collect(Collectors.averagingInt(Employee::getSalary));
minBy, maxBy
若要找出某欄位值的最大/最小物件:
Optional<Employee> richest = employees.stream()
.collect(Collectors.maxBy(Comparator.comparingInt(Employee::getSalary)));
Optional<Employee> poorest = employees.stream()
.collect(Collectors.minBy(Comparator.comparingInt(Employee::getSalary)));
重要:結果是 Optional,因為集合可能為空。
範例
建立類別 Product:
public class Product {
private final String name;
private final int price;
public Product(String name, int price) {
this.name = name;
this.price = price;
}
public String getName() { return name; }
public int getPrice() { return price; }
}
建立商品清單:
List<Product> products = List.of(
new Product("牛奶", 80),
new Product("起司", 250),
new Product("麵包", 40)
);
找出最昂貴的商品:
Optional<Product> maxProduct = products.stream()
.collect(Collectors.maxBy(Comparator.comparingInt(Product::getPrice)));
maxProduct.ifPresent(p -> System.out.println("最昂貴的商品: " + p.getName()));
4. 與分組結合
彙總方法常與分組一起使用。比如可以依商品名稱的第一個字來計算平均價格:
Map<Character, Double> avgPriceByLetter = products.stream()
.collect(Collectors.groupingBy(
p -> p.getName().charAt(0),
Collectors.averagingInt(Product::getPrice)
));
System.out.println(avgPriceByLetter);
結果:
{牛=80.0, 起=250.0, 麵=40.0}
5. 真實情境範例
範例 1: 學生與成績
學生清單與其分數:
public class Student {
private final String name;
private final int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public int getScore() {
return score;
}
}
List<Student> students = List.of(
new Student("愛麗莎", 90),
new Student("鮑伯", 75),
new Student("瓦夏", 100)
);
分數高於 80 的學生有多少?
long count = students.stream()
.filter(s -> s.getScore() > 80)
.count();
System.out.println("分數 > 80 的學生數量: " + count);
平均分數:
double avg = students.stream()
.mapToInt(Student::getScore)
.average()
.orElse(0.0);
System.out.println("平均分數: " + avg);
表現最好的學生:
students.stream()
.max(Comparator.comparingInt(Student::getScore))
.ifPresent(s -> System.out.println("最佳學生: " + s.getName()));
範例 2: 唯一商品的數量
long uniqueCount = products.stream()
.map(Product::getName)
.distinct()
.count();
System.out.println("唯一商品數量: " + uniqueCount);
6. 實用細節
視覺化流程: 彙總方法如何運作
物件集合
│
▼
stream()
│
▼
mapToInt/Double/Long(如有需要)
│
▼
sum() / average() / min() / max() / count()
│
▼
結果(int、double、long、Optional)
處理 Optional: 如何安全地取值
為什麼這些方法會回傳 Optional?
如果集合為空(例如你在空清單中找最大值),就會回傳空的 Optional。如果立刻呼叫 getAsInt()/get(),會拋出例外。
正確做法:
- 使用 orElse(預設值)
- 使用 ifPresent(...) 在有值時才執行動作
- 使用 orElseThrow(...) 以明確拋出例外
範例:
OptionalDouble avg = products.stream()
.mapToInt(Product::getPrice)
.average();
System.out.println("平均價格: " + avg.orElse(0.0));
何時使用基本型別串流,何時 — Collectors
- 如果只是要依某個數值欄位計算總和/平均/最小/最大 — 使用 mapToInt 與對應的方法(sum、average、min、max)。
- 如果要彙總物件(例如找出某欄位最大的物件),請使用收集器 maxBy/minBy。
- 如果需要分組並對每組計算彙總 — 使用 groupingBy 與彙總型收集器的組合。
7. 使用彙總方法時的常見錯誤
錯誤一:沒有處理 Optional。 初學者常忽略 min、max、average 會回傳 Optional。結果在嘗試從空容器取值時拋出 NoSuchElementException。務必使用 orElse、orElseThrow 或 ifPresent。
錯誤二:用一般的 stream 而非 mapToInt/mapToDouble。 如果你寫 stream().sum(),編譯器會說:「沒有這個方法!」要做總和、平均、最小、最大需要基本型別串流。
錯誤三:比較時型別不匹配。 在尋找物件的最大/最小時,請使用正確的比較子:Comparator.comparingInt(Employee::getSalary)。否則可能導致編譯錯誤或不正確的結果。
錯誤四:與 null 的物件比較。 若集合中可能包含 null 元素,彙總可能會拋出 NullPointerException。最好先過濾掉這些元素,或使用 Comparator.nullsLast(...)/nullsFirst(...)。
錯誤五:在空集合上使用 sum/average/min/max 而沒有考慮回傳結果。 如果集合為空,方法 sum() 會回傳 0,而 average()/min()/max() — 空的 Optional。別忘了處理這種情況。
GO TO FULL VERSION