Java 8 Stream如何按类别、颜色、城市多属性分组列表?
Java Stream 实现多属性分组
需求说明
拥有一个Items列表,希望按照Category、Color、City三个属性对该列表进行分组。
示例数据
ID;Category;Color;City;Name 1;1;Red;Rome;Shoe A 2;1;Red;Paris;Shoe C 3;1;Green;Rome;Scarf 4;1;Red;Rome;Shoe B 5;2;Red;Rome;Scarf 6;1;Red;Rome;Scarf 7;1;Green;Rome;Shoe
期望分组结果
1;Red;Rome => ID (1,4,6) 1;Red;Paris => ID(2) 1;Green;Rome => ID(3,7) 2;Red;Rome => ID(5)
已掌握的单属性分组代码
Map<Category, List<Items>> _items = items.stream() .collect(Collectors.groupingBy(item -> item.getCategory()));
多属性分组实现方案
要实现多属性分组,核心是定义一个能唯一标识分组维度的键对象,确保该对象的equals()和hashCode()方法能正确判断分组是否相同。下面提供几种常用实现方式:
方式1:自定义分组键类
自行编写包含目标分组属性的类,重写equals()和hashCode()方法保证分组逻辑正确:
import java.util.Objects; // 自定义分组键类 class GroupKey { private final Integer category; private final String color; private final String city; public GroupKey(Integer category, String color, String city) { this.category = category; this.color = color; this.city = city; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; GroupKey groupKey = (GroupKey) o; return Objects.equals(category, groupKey.category) && Objects.equals(color, groupKey.color) && Objects.equals(city, groupKey.city); } @Override public int hashCode() { return Objects.hash(category, color, city); } // 重写toString,直接输出期望的键格式 @Override public String toString() { return category + ";" + color + ";" + city; } }
基于该类完成分组,并收集对应ID列表:
Map<GroupKey, List<Integer>> groupedById = items.stream() .collect(Collectors.groupingBy( item -> new GroupKey(item.getCategory(), item.getColor(), item.getCity()), Collectors.mapping(Item::getId, Collectors.toList()) )); // 输出符合要求的结果格式 groupedById.forEach((key, ids) -> { System.out.println(key + " => ID (" + String.join(",", ids.stream().map(String::valueOf).toList()) + ")"); });
方式2:使用Java 16+ Record(推荐)
Java 16引入的Record会自动生成equals()、hashCode()和toString()方法,代码更简洁:
// 用Record定义分组键,自动实现核心方法 record GroupKey(Integer category, String color, String city) {}
分组逻辑与方式1一致,仅键的创建更简洁:
Map<GroupKey, List<Integer>> groupedById = items.stream() .collect(Collectors.groupingBy( item -> new GroupKey(item.getCategory(), item.getColor(), item.getCity()), Collectors.mapping(Item::getId, Collectors.toList()) )); // 输出时拼接成期望的键格式 groupedById.forEach((key, ids) -> { String keyStr = key.category() + ";" + key.color() + ";" + key.city(); System.out.println(keyStr + " => ID (" + String.join(",", ids.stream().map(String::valueOf).toList()) + ")"); });
方式3:字符串拼接作为键(简易但有风险)
如果属性值不会包含分隔符(如;),可以直接拼接属性值作为分组键,这种方式最简便但容错性差:
Map<String, List<Integer>> groupedById = items.stream() .collect(Collectors.groupingBy( item -> item.getCategory() + ";" + item.getColor() + ";" + item.getCity(), Collectors.mapping(Item::getId, Collectors.toList()) )); // 直接输出结果 groupedById.forEach((key, ids) -> { System.out.println(key + " => ID (" + String.join(",", ids.stream().map(String::valueOf).toList()) + ")"); });
内容的提问来源于stack exchange,提问作者mkproject
相关产品推荐
相关产品推荐

