【集合分组利器】Java通用集合分组方案

news/2025/3/31 9:56:37/文章来源:https://www.cnblogs.com/sun-10387834/p/18797359

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;}@Overridepublic 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);}@Overridepublic int hashCode() {return Arrays.hashCode(keys);}@Overridepublic 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;}@Overridepublic Function<Person, Object> getExtractor() {return extractor;}@Overridepublic 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方案开始,它在复杂度和功能性之间取得了良好的平衡。随着项目规模扩大,可以平滑过渡到枚举分组方案,获得更好的类型安全性和可维护性。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/906997.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VMware Workstation不支持的硬件版本,模块Upgrade启动失败

1、我是从高版本的VMware Workstation降级后,再打开之前的虚拟机报错如下 2、打开虚拟机文件目录,用文本打开虚拟机的 .vmx文件,搜索定位到 virtualHW 字段,修改该字段为自己当前VMware版本,然后保存并开机

Next.js中间件权限绕过漏洞分析(CVE-2025-29927)

本文代码版本为next.js-15.2.2 本篇文章首发在先知社区:https://xz.aliyun.com/news/17403 一、漏洞概述 CVE-2025-29927是Next.js框架中存在的一个高危中间件逻辑绕过漏洞,允许攻击者通过构造特定HTTP请求头,绕过中间件的安全控制逻辑(如身份验证、路径重写、CSP防护等)。…

vue+openlayers示例:线水流效果(附源码下载)

demo源码运行环境以及配置运行环境:依赖Node安装环境,demo本地Node版本:推荐v16+。 运行工具:vscode或者其他工具。 配置方式:下载demo源码,vscode打开,然后顺序执行以下命令: (1)下载demo环境依赖包命令:npm i (2)启动demo命令:npm run dev (3)打包demo命令: …

蓝屏STOP:0x0000007B

生产线需要,得准备一台Windows XP的操作系统电脑。在安装时,出现下面蓝屏,A problem has been detected and windows has been shut down to prevent damage to your computer. If this is the first time youve seen this stop error screen, restart your computer. If th…

必看!2025 年颠覆测试行业的 10 大 AI 自动化测试工具/平台(上篇)

大家好,我是狂师。 上周小孩子生病,住院照顾,停更了几天。 各位看官,等着急了吧,之前有粉丝后台留言,想了解学习一下,AI这么火爆,那市面上AI与自动化测试结合起来的有哪些推荐的工具/平台。 今天就这个话题来聊一聊。 前言 在软件迭代以“天”为单位的今天,随着软件迭…

2025年企业必读指南:文件摆渡系统的5大核心功能

在数字化转型的浪潮中,企业面临着前所未有的挑战与机遇。优化信息流转、提升协作效率、确保数据安全及合规性,已成为企业持续发展的关键要素。在这一背景下,文件摆渡系统作为连接不同网络、网域之间的桥梁,承担着文件传输、共享、管理和归档等多重任务,其重要性日益凸显。…

利用AI增强VS Code TypeScript插件:AnyToTS带来编程新体验

Any to TS: VSCode 扩展插件 概述 "Any to TS" 是一个强大的 VSCode 扩展插件,旨在将任何对象转换为 TypeScript 类型或接口。该工具基于 vscode-json-to-ts 进行功能扩展,提供了一系列便捷的功能,帮助开发者更高效地处理 TypeScript 类型定义。 核心功能 传统功能…

虚函数表里有什么?(一)——从一个普通类开始

本系列文章,旨在探究C++虚函数表中除函数地址以外的条目,以及这些条目的设计意图和作用,并介绍与此相关的C++类对象内存布局,最后将两者用图解的形式结合起来,给读者带来全局性的视角。让我们从一个简单的类出发,开启我们的探索之旅。前言 本系列文章,旨在探究C++虚函数…

《HelloGitHub》第 108 期

兴趣是最好的老师,HelloGitHub 让你对开源感兴趣!简介 HelloGitHub 分享 GitHub 上有趣、入门级的开源项目。github.com/521xueweihan/HelloGitHub这里有实战项目、入门教程、黑科技、开源书籍、大厂开源项目等,涵盖多种编程语言 Python、Java、Go、C/C++、Swift...让你在短…

读DAMA数据管理知识体系指南33参考数据和主数据活动

读DAMA数据管理知识体系指南33参考数据和主数据活动1. 主数据管理活动 1.1. 识别驱动因素和需求1.1.1. 每个组织都有不同的主数据管理驱动因素和障碍,受系统的数量和类型、使用年限、支持的业务流程以及交易和分析中数据使用方式的影响1.2. 评估和评价数据源1.2.1. 现有应用中…

C++内存序

C++的3种memory model: + sequentially consistent + relexed + acquire-release 对应5.3章《C++ concurrency in action》5.3 Synchronizing operations and enforcing ordering 首先就是对于程序来说都是通过 "happens-before" 和 "synchronizes-with" …

如何快速解决单细胞拟时序分析旧版本Monocle包导致的orderCells过程的报错

当我们整理好一版本的单细胞转录组数据分析代码,并完成一个项目的单细胞转录组数据分析项目。当我们再进行另外一个单细胞转录组数据的项目分析时,突然发现原来跑通的代谢,突然一直报错那是一件很苦恼的问题,想必各位科研党遇到这种问题是不是也有砸电脑的心情。今天主要写…