collectors.tomap 本身不支持直接统计占比,需先用 groupingby 统计频次,再基于总数归一化;例如对 categories 列表先得 {a=3, b=2, c=1},再计算得 {a=0.5, b=0.333..., c=0.166...}。

使用 Collectors.toMap 本身不直接支持“统计占比”,它只负责将流元素转为 Map<k v></k>。但你可以结合流的预处理(如先统计总数、再计算比例)来实现分类变量的占比映射。关键在于:先用 Collectors.groupingBy 或 toMap 统计频次,再基于总数量做归一化。
步骤一:统计各类别的原始频次
假设你有一个字符串列表,代表样本的分类标签:
List<string> categories = Arrays.asList("A", "B", "A", "C", "B", "A");</string>
先统计每类出现次数,得到 Map<string long></string>:
Map<string long> countMap = categories.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));</string>
结果是:{A=3, B=2, C=1}。
步骤二:计算总样本数,并生成占比 Map
有了频次映射后,用 Collectors.toMap 将其转换为占比(Double):
long total = categories.size();
Map<string double> ratioMap = countMap.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> (double) e.getValue() / total
));</string>
结果是:{A=0.5, B=0.333..., C=0.166...}。
一步到位写法(推荐)
如果希望更紧凑,可先用 groupingBy 统计,再在 toMap 中引用外部变量或用双流处理。注意:不能在 toMap 的 value mapper 中直接调用 stream().count()(会重复执行且逻辑错误)。稳妥做法仍是分两步,或封装为工具方法:
- 确保
total是 final 或 effectively final 变量 - 若需排序输出,可在最后对
ratioMap调用new LinkedHashMap(...)保持插入顺序 - 如需保留两位小数,value mapper 改为:
e -> Math.round(100.0 * e.getValue() / total) / 100.0
扩展:处理对象属性分类(如 List 按 gender 统计占比)
假设有 Person 类,含 gender: String 字段:
Map<string double> genderRatio = persons.stream()
.collect(Collectors.groupingBy(Person::getGender, Collectors.counting()))
.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> (double) e.getValue() / persons.size()
));</string>
逻辑一致,只是分类键从 Function.identity() 变为 Person::getGender。










