Java通用集合分组实现方案详解:从基础到高级实践

在Java开发中,对集合中的元素按照特定属性进行分组是一项常见而重要的操作。本文将全面介绍Java中实现集合分组的多种方案,从基础实现到高级用法,并通过丰富的示例展示每种方案的实际效果。

一、基础分组实现

1.1 单属性分组

最基本的集合分组方式是按照对象的单个属性进行分组:

// 通用单属性分组方法
public static <T, K> Map<K, List<T>> groupBySingleProperty(
Collection<T> collection,
Function<T, K> classifier) {
return collection.stream()
.collect(Collectors.groupingBy(classifier));
} // 使用示例:按姓名分组
Map<String, List<Person>> byName = groupBySingleProperty(people, Person::getName); // 结果输出
System.out.println("按姓名分组结果:");
byName.forEach((name, list) ->
System.out.println(" " + name + ": " + list));

执行结果

按姓名分组结果:
Bob: [Bob(30,Chicago), Bob(25,New York)]
Alice: [Alice(25,New York), Alice(25,Chicago), Alice(30,New York)]

1.2 多属性分组(使用List作为键)

当需要按照多个属性组合作为分组依据时:

// 通用多属性分组方法
public static <T, K> Map<List<K>, List<T>> groupByMultipleProperties(
Collection<T> collection,
Function<T, K>... classifiers) { return collection.stream()
.collect(Collectors.groupingBy(
item -> Arrays.stream(classifiers)
.map(fn -> fn.apply(item))
.collect(Collectors.toList())
));
} // 使用示例:按姓名和年龄分组
Map<List<Object>, List<Person>> byNameAndAge =
groupByMultipleProperties(people, Person::getName, Person::getAge); // 结果输出
System.out.println("\n按姓名和年龄分组结果:");
byNameAndAge.forEach((key, list) ->
System.out.println(" " + key + ": " + list));

执行结果

按姓名和年龄分组结果:
[Alice, 25]: [Alice(25,New York), Alice(25,Chicago)]
[Bob, 30]: [Bob(30,Chicago)]
[Alice, 30]: [Alice(30,New York)]
[Bob, 25]: [Bob(25,New York)]

二、增强型分组实现

2.1 使用GroupKey分组

为避免使用List作为Map键可能带来的问题,我们可以引入专门的GroupKey类:

// GroupKey定义
public static class GroupKey {
private final Object[] keys; public GroupKey(Object... keys) {
this.keys = keys;
} @Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof GroupKey)) return false;
GroupKey groupKey = (GroupKey) o;
return Arrays.equals(keys, groupKey.keys);
} @Override
public int hashCode() {
return Arrays.hashCode(keys);
} @Override
public String toString() {
return Arrays.toString(keys);
}
} // 使用GroupKey的分组方法
public static <T> Map<GroupKey, List<T>> groupByWithGroupKey(
Collection<T> collection,
Function<T, ?>... classifiers) { return collection.stream()
.collect(Collectors.groupingBy(
item -> new GroupKey(
Arrays.stream(classifiers)
.map(fn -> fn.apply(item))
.toArray()
)
));
} // 使用示例:按年龄和城市分组
Map<GroupKey, List<Person>> byAgeAndCity =
groupByWithGroupKey(people, Person::getAge, Person::getCity); // 结果输出
System.out.println("\n使用GroupKey按年龄和城市分组结果:");
byAgeAndCity.forEach((key, list) ->
System.out.println(" " + key + ": " + list));

执行结果

使用GroupKey按年龄和城市分组结果:
[25, New York]: [Alice(25,New York), Bob(25,New York)]
[30, Chicago]: [Bob(30,Chicago)]
[25, Chicago]: [Alice(25,Chicago)]
[30, New York]: [Alice(30,New York)]

三、基于枚举的高级分组方案

3.1 枚举分组基础架构

// 分组字段枚举接口
public interface GroupFieldEnum<T> {
Function<T, Object> getExtractor();
String getFieldName();
} // Person类的分组字段枚举
public enum PersonGroupField implements GroupFieldEnum<Person> {
NAME("姓名", Person::getName),
AGE("年龄", Person::getAge),
CITY("城市", Person::getCity); private final String fieldName;
private final Function<Person, Object> extractor; PersonGroupField(String fieldName, Function<Person, Object> extractor) {
this.fieldName = fieldName;
this.extractor = extractor;
} @Override
public Function<Person, Object> getExtractor() {
return extractor;
} @Override
public String getFieldName() {
return fieldName;
}
} // 枚举分组工具类
public class EnumGroupingUtils {
public static <T, E extends Enum<E> & GroupFieldEnum<T>>
Map<GroupKey, List<T>> groupByEnumFields(
Collection<T> collection,
E... groupFields) { return collection.stream()
.collect(Collectors.groupingBy(
item -> new GroupKey(
Arrays.stream(groupFields)
.map(field -> field.getExtractor().apply(item))
.toArray()
)
));
}
}

3.2 枚举分组使用示例

// 按枚举字段分组示例
System.out.println("\n枚举分组方案演示:"); // 按姓名分组
Map<GroupKey, List<Person>> byNameEnum =
EnumGroupingUtils.groupByEnumFields(people, PersonGroupField.NAME);
System.out.println("1. 按姓名分组结果:");
byNameEnum.forEach((key, list) ->
System.out.println(" " + key + ": " + list)); // 按姓名和年龄分组
Map<GroupKey, List<Person>> byNameAndAgeEnum =
EnumGroupingUtils.groupByEnumFields(people,
PersonGroupField.NAME, PersonGroupField.AGE);
System.out.println("\n2. 按姓名和年龄分组结果:");
byNameAndAgeEnum.forEach((key, list) ->
System.out.println(" " + key + ": " + list)); // 按所有字段分组
Map<GroupKey, List<Person>> byAllFieldsEnum =
EnumGroupingUtils.groupByEnumFields(people,
PersonGroupField.values());
System.out.println("\n3. 按所有字段分组结果:");
byAllFieldsEnum.forEach((key, list) ->
System.out.println(" " + key + ": " + list)); // 动态选择分组字段
List<PersonGroupField> dynamicFields = new ArrayList<>();
dynamicFields.add(PersonGroupField.CITY);
dynamicFields.add(PersonGroupField.AGE);
Map<GroupKey, List<Person>> dynamicResult =
EnumGroupingUtils.groupByEnumFields(people,
dynamicFields.toArray(new PersonGroupField[0]));
System.out.println("\n4. 动态选择字段(城市+年龄)分组结果:");
dynamicResult.forEach((key, list) ->
System.out.println(" " + key + ": " + list));

执行结果

枚举分组方案演示:
1. 按姓名分组结果:
[Alice]: [Alice(25,New York), Alice(25,Chicago), Alice(30,New York)]
[Bob]: [Bob(30,Chicago), Bob(25,New York)] 2. 按姓名和年龄分组结果:
[Alice, 25]: [Alice(25,New York), Alice(25,Chicago)]
[Bob, 30]: [Bob(30,Chicago)]
[Alice, 30]: [Alice(30,New York)]
[Bob, 25]: [Bob(25,New York)] 3. 按所有字段分组结果:
[Alice, 25, New York]: [Alice(25,New York)]
[Bob, 30, Chicago]: [Bob(30,Chicago)]
[Alice, 25, Chicago]: [Alice(25,Chicago)]
[Alice, 30, New York]: [Alice(30,New York)]
[Bob, 25, New York]: [Bob(25,New York)] 4. 动态选择字段(城市+年龄)分组结果:
[New York, 25]: [Alice(25,New York), Bob(25,New York)]
[Chicago, 30]: [Bob(30,Chicago)]
[Chicago, 25]: [Alice(25,Chicago)]
[New York, 30]: [Alice(30,New York)]

四、技术深度解析

4.1 toArray(new PersonGroupField[0])原理

在动态字段分组中使用的这种写法是Java集合转数组的惯用模式:

dynamicFields.toArray(new PersonGroupField[0])
  • 作用:将List转换为PersonGroupField[]数组
  • 原理
    1. 传入空数组作为类型模板
    2. JVM根据运行时类型信息创建正确类型和大小的新数组
    3. 比直接指定大小更简洁高效(无需先调用size())
  • Java 11+优化:可使用toArray(PersonGroupField[]::new)替代

4.2 枚举分组的优势

  1. 类型安全:编译器会检查枚举值的有效性
  2. 可维护性:所有分组字段集中管理,修改方便
  3. 自描述性:枚举可包含字段描述信息
  4. IDE支持:代码自动补全和提示更完善
  5. 可扩展性:新增分组字段只需添加枚举项

五、方案对比与选型建议

方案 适用场景 优点 缺点
单属性分组 简单分组需求 实现简单 功能有限
多属性List分组 临时性多字段分组 无需额外类 List作为键不够直观
GroupKey分组 需要清晰键定义的分组 键表达明确 需维护GroupKey类
枚举分组 企业级应用、复杂分组需求 类型安全、可维护 需要前期设计

选型建议

  1. 简单工具类:使用基础分组方案
  2. 中型项目:推荐GroupKey方案
  3. 大型复杂系统:采用枚举分组架构
  4. 需要最大灵活性:结合动态字段选择

六、性能优化建议

  1. 大数据集处理

    // 使用并行流提高处理速度
    Map<GroupKey, List<Person>> result = people.parallelStream()
    .collect(Collectors.groupingBy(...));
  2. 内存优化

    • 对于不可变数据集,考虑使用Guava的ImmutableListMultimap
    • 分组结果如果不需要修改,返回不可变集合
  3. 缓存优化

    • 频繁使用的分组结果可以考虑缓存
    • 对于相同分组条件的多次操作,可以复用分组结果

七、总结

本文详细介绍了Java中实现集合分组的四种主要方案,从基础的Collectors.groupingBy()使用到基于枚举的高级分组架构。每种方案都附带了完整的代码示例和实际执行结果展示,帮助开发者深入理解其实现原理和应用场景。

对于大多数项目,推荐从GroupKey方案开始,它在复杂度和功能性之间取得了良好的平衡。随着项目规模扩大,可以平滑过渡到枚举分组方案,获得更好的类型安全性和可维护性。

无论选择哪种方案,理解分组操作背后的原理和各个方案的优缺点,都能帮助开发者写出更高效、更易维护的集合处理代码。

【集合分组利器】Java通用集合分组方案的更多相关文章

  1. Mysql高手系列 - 第9篇:详解分组查询,mysql分组有大坑!

    这是Mysql系列第9篇. 环境:mysql5.7.25,cmd命令中进行演示. 本篇内容 分组查询语法 聚合函数 单字段分组 多字段分组 分组前筛选数据 分组后筛选数据 where和having的区 ...

  2. android系统联系人分组特效实现(1)---分组导航和挤压动画

    1.打开activity_main.xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/andr ...

  3. java集合框架之java HashMap代码解析

     java集合框架之java HashMap代码解析 文章Java集合框架综述后,具体集合类的代码,首先以既熟悉又陌生的HashMap开始. 源自http://www.codeceo.com/arti ...

  4. mysql按字段分组并获取每个分组按照某个字段排序的前三条

    这是原始数据 想按照brand_id分组 并获取每个分组total_num最高的前3位 SQL语句为: > (select count(*) from data where brand_id = ...

  5. ThinkPHP第七天(F函数使用,项目分组配置,项目分组模板文件放置规则配置)

    1.F(文件名称,写入数据,写入地址),用于将数据写入至磁盘文件中,如F('Data',$arr,'./Data/'),作用是将$arr写入至网站根目录的Data文件夹中的Data.php中. 2.读 ...

  6. java集合框架容器 java框架层级 继承图结构 集合框架的抽象类 集合框架主要实现类

    本文关键词: java集合框架  框架设计理念  容器 继承层级结构 继承图 集合框架中的抽象类  主要的实现类 实现类特性   集合框架分类 集合框架并发包 并发实现类 什么是容器? 由一个或多个确 ...

  7. split与re.split/捕获分组和非捕获分组/startswith和endswith和fnmatch/finditer 笔记

    split()对字符串进行划分: >>> a = 'a b c d' >>> a.split(' ') ['a', 'b', 'c', 'd'] 复杂一些可以使用r ...

  8. Oracle分组函数以及数据分组

    简单总结一下对于数据的分组和分组函数. 本文所举实例,数据来源oracle用户scott下的emp,dept ,salgrade 3表:数据如下: 一.分组函数 1.sum()求和函数.max()求最 ...

  9. Mysql:实现分组查询拼接未分组同一字段字符group_concat()

    Mysql:实现分组查询拼接未分组同一字段字符group_concat() MySQL中,如果想实现将分组之后的多个数据合并到一列,可以使用group_concat函数,如下图所示: 在oralce中 ...

  10. SQL Server 根据日期分组、 根据时间段分组(每三个小时一组)

    所用数据表: 一.根据日期分组 1. 使用convert() 函数方式 --根据年月 ),CreatTime,)日期,COUNT(*) 次数,sum(Money)总数 from Orders ),Cr ...

随机推荐

  1. Solution Set - “说选个晴日,露能滴出彩虹”

    目录 0.「BZOJ #3457」Ring 1.「CF 1824C」LuoTianyi and XOR-Tree 2.「CF 1824D」LuoTianyi and the Function 3.「C ...

  2. 推荐一款 .NET 智慧水务物联网系统

    前言 本文将介绍如何通过智能水表(如 NB-IoT 水表).智能消火栓.智能阀门.数据采集终端(RTU 或 PLC)及其他前置传感器和设备,开发一个高效.智能的供水管理系统.该系统能够实时采集和分析供 ...

  3. Ubuntu20.04配置CuckooSandbox环境

    Ubuntu20.04配置CuckooSandbox环境 因为最近要做恶意软件分析,阅读论文发现动态分析的效果普遍比静态分析的效果要好一些,所以需要搭建一个动态分析的环境,查阅资料发现Cuckoo S ...

  4. java中属性和变量的区别与final和抽象类

    属性和变量的区别 变量必须要有初始值: 属性可以没有初始值: 变量的范围只在当前大括号内有效: 属性是对象在哪里使用就在哪里有效: 属性的作用范围要比变量的大一些 属性和变量 public class ...

  5. dart中所有的循环详解

    List MyList = ['苹果', '栗子', '小苹果']; for (var i = 0; i < MyList.length; i++) { print(MyList[i]); } ...

  6. manim边学边做--局部变换

    本次介绍的两个用于变换的动画类:TransformMatchingShapes和TransformMatchingTex. 它们的主要特点是对一组对象或一段文本进行局部变换,适用于复杂的图形或者文本的 ...

  7. 如何在M芯片的Mac上爽玩原神

    [热点速递]苹果震撼发布全新M4 Mac mini,国补福利下惊喜价仅约3500元!这不仅是一次办公体验的全新升级,更是对高效能与性价比完美融合的一次致敬.想象一下,以如此亲民的价格,拥抱苹果标志性的 ...

  8. C#实现文件的压缩和解压缩

    原文链接:https://www.cnblogs.com/sunyaling/archive/2009/04/13/1434602.html 在C#中实现文件的压缩和解压缩,需要使用第三方的组建完成. ...

  9. 甲壳虫ADB助手-让你轻松不用电脑就能卸载电视自带软件

    甲壳虫ADB助手是一款非常使用的安卓ADB调试工具,它适用于各种安卓系统设备,包括手机.平板.手表和电视等等,可以帮助用户直接在手机上对设备进行ADB调试,而且不需要ROOT,支持无线配对连接,让用户 ...

  10. Joe主题 更换评论框样式,填写QQ自动获取昵称邮箱

    前言: 由于为了美观感去除了画图模式,至于怎么加上画图模式会在文章最后给出详细教程. 同时也去除了填写网址选项,这个暂时无添加教程,关键没有合适的添加位子. 话不多说,直接开始教程! 下载文件包,解压 ...