使用Collectors.groupingBy配合Collectors.counting()可一行统计元素频次,支持按元素自身或对象字段分组,返回Map,注意null处理、Long返回类型及并发安全。

用 Collectors.groupingBy 配合 Collectors.counting(),可以一行代码统计集合中各元素出现的次数,是 Java 8 Stream 中最常用、最简洁的重复度分析方式。
基础用法:统计字符串列表中每个单词的出现频次
这是最典型的场景。假设你有一组单词,想快速知道每个词重复了多少次:
List<String> words = Arrays.asList("apple", "banana", "apple", "cherry", "banana", "apple");
Map<String, Long> frequency = words.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
// 结果:{apple=3, banana=2, cherry=1}
说明:
-
Function.identity()表示按元素自身分组(即以字符串值为 key) -
Collectors.counting()是一个下游收集器,负责对每个分组内的元素计数,返回Long - 最终得到的是
Map<t long></t>,key 是原始元素,value 是其出现次数
按对象字段分组计数:统计用户所属城市的人数
当处理自定义对象时,可传入方法引用作为分组依据:
record User(String name, String city) {}
List<User> users = List.of(
new User("Alice", "Beijing"),
new User("Bob", "Shanghai"),
new User("Charlie", "Beijing"),
new User("Diana", "Guangzhou")
);
Map<String, Long> cityCount = users.stream()
.collect(Collectors.groupingBy(User::city, Collectors.counting()));
// 结果:{Beijing=2, Shanghai=1, Guangzhou=1}
关键点:
- 用
User::city替代Function.identity(),表示按city字段分组 - 仍使用
counting()统计每组数量,逻辑不变 - 注意字段值为
null时会抛出NullPointerException,如需容错,可先过滤或用Objects.toString(user.city, "unknown")
进阶技巧:结合 filtering 或 mapping 做条件计数
如果只想统计满足某条件的元素频次(比如只统计长度大于 4 的单词),不能直接在 groupingBy 外过滤——那样会丢失原始分组结构。正确做法是用下游收集器组合:
Map<String, Long> longWordFreq = words.stream()
.filter(word -> word.length() > 4)
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
或者更灵活地,在下游使用 Collectors.collectingAndThen 或 Collectors.filtering(Java 9+):
// Java 9+ 示例:只对满足条件的元素计数,但保留所有 key(未匹配的为 0)
Map<String, Long> withZero = words.stream()
.collect(Collectors.groupingBy(
Function.identity(),
Collectors.collectingAndThen(
Collectors.filtering(word -> word.length() > 4, Collectors.counting()),
count -> count
)
));
不过要注意:标准 counting() 本身不支持条件筛选,必须配合 filtering 或前置 filter 流操作。
常见问题与避坑提醒
实际使用中容易忽略几个细节:
-
返回类型是
Long,不是Integer:因为counting()内部用long累加,避免溢出;若需Integer,可用Collectors.summingInt(e -> 1) -
空集合返回空 Map:不会为不存在的 key 补 0,如需默认值,后续用
map.getOrDefault(key, 0L) -
并发流慎用:虽然
groupingByConcurrent存在,但counting()是无状态的,线程安全;不过若 key 本身非线程安全(如可变对象),仍可能出错 - 性能考虑:对大数据量,Stream 分组比传统 for-loop 略慢;但代码清晰度和可维护性优势明显,一般场景无需过早优化
这种组合写法简洁有力,覆盖了绝大多数重复度分析需求,核心就是理解“分组”和“下游聚合”的协作关系。

















