CodeGym /課程 /JAVA 25 SELF /Stream API 中的 sum、 <...

Stream API 中的 sumcountaveragemaxmin 方法

JAVA 25 SELF
等級 31 , 課堂 0
開放

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 提供了專門的串流:IntStreamDoubleStreamLongStream。它們讓你能輕鬆快速計算總和、平均、最小值與最大值。

轉成數值串流

若要從物件串流取得數值串流,請使用方法 mapToIntmapToDoublemapToLong

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。務必使用 orElseisPresent()

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 與對應的方法(sumaverageminmax)。
  • 如果要彙總物件(例如找出某欄位最大的物件),請使用收集器 maxBy/minBy
  • 如果需要分組並對每組計算彙總 — 使用 groupingBy 與彙總型收集器的組合。

7. 使用彙總方法時的常見錯誤

錯誤一:沒有處理 Optional。 初學者常忽略 minmaxaverage 會回傳 Optional。結果在嘗試從空容器取值時拋出 NoSuchElementException。務必使用 orElseorElseThrowifPresent

錯誤二:用一般的 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。別忘了處理這種情況。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION