0Pricing
Java Academy · 课时

groupingBy:元素分类

通过分类函数将流元素分组到映射中,并与下游收集器组合使用

groupingBy:元素分类 是 CoddyKit 上的免费 Java Academy 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Java Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Java Academy 课程共包含 4 节课。

什么是 groupingBy?

Collectors.groupingBy() 会将流元素分组到一个 Map 中,其中每个键都映射到一个匹配元素列表。它相当于流操作中的 SQL GROUP BY。

import java.util.*;
import java.util.stream.*;

record Person(String name, String city) {}
List<Person> people = List.of(
    new Person("Alice","NYC"), new Person("Bob","LA"),
    new Person("Carol","NYC"), new Person("Dave","LA"),
    new Person("Eve","Chicago")
);

Map<String, List<Person>> byCity =
    people.stream().collect(Collectors.groupingBy(Person::city));
byCity.forEach((city, ps) -> System.out.println(city + ": " + ps.size()));

下游收集器:counting

将 groupingBy 与下游收集器组合使用,以执行聚合,而不是列出元素:

Map<String, Long> countByCity =
    people.stream().collect(
        Collectors.groupingBy(Person::city, Collectors.counting())
    );
countByCity.forEach((city, n) -> System.out.println(city + " -> " + n));
// NYC -> 2, LA -> 2, Chicago -> 1

下游收集器:mapping

使用 Collectors.mapping() 作为下游收集器,在收集元素之前对其进行转换:

Map<String, List<String>> namesByCity =
    people.stream().collect(
        Collectors.groupingBy(
            Person::city,
            Collectors.mapping(Person::name, Collectors.toList())
        )
    );
namesByCity.forEach((city, names) -> System.out.println(city + ": " + names));

下游收集器:joining

将每个分组中的名称连接成一个逗号分隔的字符串:

Map<String, String> joinedByCity =
    people.stream().collect(
        Collectors.groupingBy(
            Person::city,
            Collectors.mapping(Person::name, Collectors.joining(", "))
        )
    );
joinedByCity.forEach((city, s) -> System.out.println(city + ": " + s));
// NYC: Alice, Carol

多级 groupingBy

嵌套调用 groupingBy,进行多维分类:

record Employee(String name, String dept, String level) {}
List<Employee> emps = List.of(
    new Employee("A","Eng","Junior"), new Employee("B","Eng","Senior"),
    new Employee("C","HR","Junior"), new Employee("D","HR","Senior")
);

Map<String, Map<String, List<Employee>>> grouped =
    emps.stream().collect(
        Collectors.groupingBy(Employee::dept,
            Collectors.groupingBy(Employee::level))
    );
grouped.forEach((dept, levels) -> levels.forEach((level, list) ->
    System.out.println(dept+"/"+level+": "+list.size())));

控制映射类型

groupingBy 默认生成 HashMap。使用三参数重载指定其他映射类型,例如使用 TreeMap 生成已排序的键:

Map<String, Long> sorted =
    people.stream().collect(
        Collectors.groupingBy(Person::city, TreeMap::new, Collectors.counting())
    );
System.out.println(sorted); // keys in alphabetical order

下游收集器:averagingInt

计算每个分组的平均值:

record Product(String category, int price) {}
List<Product> products = List.of(
    new Product("A",10), new Product("A",20),
    new Product("B",15), new Product("B",25)
);

Map<String, Double> avgPrice =
    products.stream().collect(
        Collectors.groupingBy(Product::category,
            Collectors.averagingInt(Product::price))
    );
avgPrice.forEach((cat, avg) -> System.out.printf("%s: %.1f%n", cat, avg));

下游收集器:toUnmodifiableList

使用 Collectors.toUnmodifiableList() 作为下游收集器,生成不可变分组:

Map<String, List<String>> immutable =
    people.stream().collect(
        Collectors.groupingBy(Person::city,
            Collectors.mapping(Person::name, Collectors.toUnmodifiableList()))
    );
// Attempting to modify throws UnsupportedOperationException

实际应用:订单汇总

按状态对订单分组,并计算每组金额的总和:

record Order(String status, double amount) {}
List<Order> orders = List.of(
    new Order("PAID", 50.0), new Order("PENDING", 30.0),
    new Order("PAID", 75.0), new Order("CANCELLED", 20.0)
);

Map<String, Double> totalByStatus =
    orders.stream().collect(
        Collectors.groupingBy(Order::status,
            Collectors.summingDouble(Order::amount))
    );
totalByStatus.forEach((s, t) -> System.out.printf("%s: $%.2f%n", s, t));

性能注意事项

groupingBy 会将所有元素收集到内存中。对于非常大的数据集,请考虑分块处理,或使用数据库级别的 GROUP BY。下游收集器会针对每个元素运行——应保持其轻量,避免出现 O(n²) 的行为。

与 filter 和 sorted 组合使用

在收集之前链式调用流操作,以预先筛选或预先排序数据:

Map<String, Long> activeCities =
    people.stream()
          .filter(p -> !p.city().equals("Chicago"))
          .collect(Collectors.groupingBy(Person::city, Collectors.counting()));
System.out.println(activeCities); // {NYC=2, LA=2}

快速检查

使用 collect() 调用 Collectors.groupingBy(Person::city) 时,会生成什么类型?

回顾:groupingBy

要点回顾:

  • groupingBy(分类器) → 映射<K, 列表<T>>
  • 与下游收集器组合:counting、mapping、joining、summarizing
  • 嵌套 groupingBy,进行多维分类
  • 使用三参数重载控制生成的映射类型(例如 TreeMap)

常见问题解答

「groupingBy:元素分类」课时是免费的吗?

是的 — 「groupingBy:元素分类」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Java Academy 课程的其余内容,请升级到 CoddyKit PRO。 Java Academy 课程共包含 4 节课。

「groupingBy:元素分类」这节课中我会学到什么?

通过分类函数将流元素分组到映射中,并与下游收集器组合使用 你通过在浏览器中直接运行的动手代码来练习 Java Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Java Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Java Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。

「groupingBy:元素分类」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Java Academy 课中编写并运行代码吗?

能。每节 Java Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. groupingBy:元素分类
  2. partitioningBy 与计数
  3. toMap、joining 与 summarizing
  4. 构建自定义收集器
← 返回 Java Academy