CodeGym /课程 /JAVA 25 SELF /Stream API 中的 sum、count、average、max、min 方法

Stream API 中的 sum、count、average、max、min 方法

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("Milk", 80),
    new Product("Cheese", 250),
    new Product("Bread", 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. 数值数据的总和、平均值、最小值和最大值

原始类型流:IntStreamDoubleStreamLongStream

对于数值数据,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

程序员小笑话:空列表并不是错误,只是你没有数据而已。但如果你在空的 Optional 上调用 getAsInt()——就会捕获到 NoSuchElementException。请始终使用 orElseisPresent()

3. 对象的聚合:Collectors.summingIntaveragingIntmaxByminBy

当你有一个对象流,并希望按某个属性进行聚合时,请使用 Collectors 类中的专用收集器。

summingIntaveragingInt

int totalSalary = employees.stream()
    .collect(Collectors.summingInt(Employee::getSalary));

double avgSalary = employees.stream()
    .collect(Collectors.averagingInt(Employee::getSalary));

minBymaxBy

要根据某个字段找到最大/最小的对象:

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("Milk", 80),
    new Product("Cheese", 250),
    new Product("Bread", 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);

结果:

{Milk=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("Alisa", 90),
    new Student("Bob", 75),
    new Student("Basil", 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. 使用聚合方法时的常见错误

错误 №1:未处理 Optional。 新手常常忘记 minmaxaverage 会返回 Optional。结果是在空容器上取值时抛出 NoSuchElementException。务必使用 orElseorElseThrowifPresent

错误 №2:在需要 mapToInt/mapToDouble 的地方使用了普通 stream。 如果你写的是 stream().sum(),编译器会说:“没有这样的方法!”。对于总和、平均、最小与最大,需要使用原始类型流。

错误 №3:比较时类型不匹配。 当在对象中查找最大/最小时,请使用正确的比较器:Comparator.comparingInt(Employee::getSalary)。否则可能会得到编译错误或不正确的结果。

错误 №4:与 null 的对象比较。 如果集合中可能包含 null 元素,聚合操作可能抛出 NullPointerException。最好先过滤此类元素,或使用 Comparator.nullsLast(...)/nullsFirst(...)

错误 №5:在处理空集合时,对 sum/average/min/max 的结果缺乏考虑。 如果集合为空,方法 sum() 会返回 0,而 average()/min()/max() 会返回空的 Optional。不要忘记处理这种情况。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION