在SQL中经常会用到分组,我们也常常遇到一些组合分组的场景。

有下面的一个User类

import lombok.Builder;
import lombok.Data;
import java.time.LocalDateTime;@Data
@Builder
public class User {private String name;private int id;private String city;private String sex;private LocalDateTime birthDay;
}

java8分组 传统写法(单个字段分组)

场景:根据 城市 进行分组

使用的是方法引用:User::getCity 来完成分组

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;public class Demo2 {public static void main(String[] args) {DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");// data listList<User> userList = Arrays.asList(User.builder().id(123456).name("Zhang, San").city("ShangHai").sex("man").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build(),User.builder().id(777777).name("Zhang, San").city("ShangHai").sex("woman").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build(),User.builder().id(888888).name("Li, Si").city("ShangHai").sex("man").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build(),User.builder().id(999999).name("Zhan, San").city("HangZhou").sex("woman").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build(),User.builder().id(555555).name("Li, Si").city("NaJin").sex("man").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build());Map<String, List<User>> groupMap = userList.stream().collect(Collectors.groupingBy(User::getCity));groupMap.forEach((k, v) -> {System.out.println(k);System.out.println(v);});}
}

java8分组 传统写法(多个字段分组)

场景:根据 城市,性别进行分组

一般的写法会是下面的这种写法,通过lambda表达式将key的生成逻辑传入进去:u -> u.getCity() + "|" + u.getSex() 来实现分组的效果。

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;public class Demo2 {public static void main(String[] args) {DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");// data listList<User> userList = Arrays.asList(User.builder().id(123456).name("Zhang, San").city("ShangHai").sex("man").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build(),User.builder().id(777777).name("Zhang, San").city("ShangHai").sex("woman").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build(),User.builder().id(888888).name("Li, Si").city("ShangHai").sex("man").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build(),User.builder().id(999999).name("Zhan, San").city("HangZhou").sex("woman").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build(),User.builder().id(555555).name("Li, Si").city("NaJin").sex("man").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build());Map<String, List<User>> groupMap = userList.stream().collect(Collectors.groupingBy(u -> u.getCity() + "|" + u.getSex()));groupMap.forEach((k, v) -> {System.out.println(k);System.out.println(v);});}
}

分析:多个分组条件 与 单个分组条件 两种写法

单个条件的分组用的比较多,userList.stream().collect(Collectors.groupingBy(User::getCity));
这种方法引用的方式看起来很清爽。
在我们遇到多个字段的分组的时候,我并不太想使用前面那种传统的写法①。

我在想,既然单个字段的分组写法是:
userList.stream().collect(Collectors.groupingBy(User::getCity));
那么多个字段的写法可否是下面这种( 类推 ),传入多个方法引用!
userList.stream().collect(Collectors.groupingBy(User::getCity,User::getSex));

很可惜 jdk 类库中Collectors 没有提供这种写法


多个字段的优雅写法

因为jdk没有提供这种写法,于是自己就想写了一个Util来帮助我们使用多个方法引用的方式完成组合分组

MyBeanUtil groupingBy(userList, User::getSex, User::getCity);

Demo:

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;public class MyBeanUtil {public static void main(String[] args) {DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");// data listList<User> userList = Arrays.asList(User.builder().id(123456).name("Zhang, San").city("ShangHai").sex("man").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build(),User.builder().id(777777).name("Zhang, San").city("ShangHai").sex("woman").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build(),User.builder().id(888888).name("Li, Si").city("ShangHai").sex("man").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build(),User.builder().id(999999).name("Zhan, San").city("HangZhou").sex("woman").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build(),User.builder().id(555555).name("Li, Si").city("NaJin").sex("man").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build());// 进行分组,根据名字和城市分组Map<String, List<User>> groupMap = groupingBy(userList, User::getSex, User::getCity);//打印分组结果groupMap.forEach((k, v) -> {System.out.println(k);System.out.println(v);});}/*** 将数据分组,根据方法引用(bean的get方法)** @param list      为分组的数据* @param functions get方法数组*/@SafeVarargspublic static <T, R> Map<String, List<T>> groupingBy(List<T> list, Function<T, R>... functions) {return list.stream().collect(Collectors.groupingBy(t -> groupingBy(t, functions)));}/*** 分组工具根据函数式接口使用分组,将数据根据分组结果进行拆分*/@SafeVarargspublic static <T, R> String groupingBy(T t, Function<T, R>... functions) {if (functions == null || functions.length == 0) {throw new NullPointerException("functions数组不可以为空");} else if (functions.length == 1) {return functions[0].apply(t).toString();} else {return Arrays.stream(functions).map(fun -> fun.apply(t).toString()).reduce((str1, str2) -> str1 + "|" + str2).get();}}
}

再度优化

依然不是很满足这种写法,因为这种写法需要借助 Util 类,不够接地气!
我更希望是下面这种接地气的写法:能够完全集成在jdk类库中

userList.stream().collect(Collectors.groupingBy(User::getCity,User::getSex));

为了达到上述的效果,那么显然我们是需要修改jdk源代码的;
于是我就将java.util.stream.Collectors源码完整copy出来,然后加入下面3个方法

    public static <T, K> Collector<T, ?, HashMap<K, List<T>>>groupingBy(Function<? super T, ? extends K>... classifier) {return groupingBy("|", classifier);}public static <T, K> Collector<T, ?, HashMap<K, List<T>>>groupingBy(String split, Function<? super T, ? extends K>... classifier) {return groupingBy(split, classifier, HashMap::new, toList());}public static <T, K, D, A, M extends Map<? super K, D>>Collector<T, ?, M> groupingBy(String split,Function<? super T, ? extends K>[] classifierArr,Supplier<M> mapFactory,Collector<? super T, A, D> downstream) {Supplier<A> downstreamSupplier = downstream.supplier();BiConsumer<A, ? super T> downstreamAccumulator = downstream.accumulator();BiConsumer<Map<K, A>, T> accumulator = (m, t) -> {String key = Arrays.stream(classifierArr).map(classifier -> Objects.requireNonNull(classifier.apply(t))).map(String::valueOf).reduce((s1, s2) -> s1 + split + s2).get();A container = m.computeIfAbsent((K) key, k -> downstreamSupplier.get());downstreamAccumulator.accept(container, t);};BinaryOperator<Map<K, A>> merger = Collectors.<K, A, Map<K, A>>mapMerger(downstream.combiner());@SuppressWarnings("unchecked")Supplier<Map<K, A>> mangledFactory = (Supplier<Map<K, A>>) mapFactory;if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) {return new CollectorImpl<>(mangledFactory, accumulator, merger, CH_ID);} else {@SuppressWarnings("unchecked")Function<A, A> downstreamFinisher = (Function<A, A>) downstream.finisher();Function<Map<K, A>, M> finisher = intermediate -> {intermediate.replaceAll((k, v) -> downstreamFinisher.apply(v));@SuppressWarnings("unchecked")M castResult = (M) intermediate;return castResult;};return new CollectorImpl<>(mangledFactory, accumulator, merger, finisher, CH_NOID);}}

就达到了我们预期的效果,为了方便大家也一起体验一下,我已经将demo完整的放到了github上

源码地址:https://github.com/1015770492/CollectorsDemo
下载好源码后,找到下面这个类

Demo:

import java.io.Serializable;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;public class MultiGroupByDemo {public static void main(String[] args) {DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");// data listList<User> userList = Arrays.asList(User.builder().id(123456).name("Zhang, San").city("ShangHai").sex("man").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build(),User.builder().id(777777).name("Zhang, San").city("ShangHai").sex("woman").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build(),User.builder().id(888888).name("Li, Si").city("ShangHai").sex("man").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build(),User.builder().id(999999).name("Zhan, San").city("HangZhou").sex("woman").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build(),User.builder().id(555555).name("Li, Si").city("NaJin").sex("man").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build());/** maybe we can*/// 1.Use the default vertical separatorSystem.out.println("Use the default vertical separator:");HashMap<String, List<User>> defaultSpilt = userList.stream().collect(Collectors.groupingBy(User::getName, User::getCity));printMap(defaultSpilt);System.out.println();// 2.Use custom delimitersSystem.out.println("Use custom delimiters:");userList.stream().collect(Collectors.groupingBy("--", User::getName, User::getCity, User::getId));HashMap<? extends Serializable, List<User>> collect = userList.stream().collect(Collectors.groupingBy("--", User::getName, User::getCity, User::getId));printMap(collect);System.out.println();// 3.Use custom delimitersSystem.out.println("Use custom delimiters:");userList.stream().collect(Collectors.groupingBy("--", User::getName, User::getCity, User::getId));HashMap<? extends Serializable, List<User>> collect2 = userList.stream().collect(Collectors.groupingBy(User::getName, User::getCity, User::getBirthDay));printMap(collect2);}public static <T> void printMap(Map<? extends Serializable, List<T>> map){map.forEach((k, v) -> {System.out.println(k);System.out.println(v);});}}


最后我希望这个特性能被JDK所吸收,这样可以方便大家更好的使用这些好用的特性

如何更优雅的使用 Java8 进行分组(尤其是多个字段的组合分组)。相关推荐

  1. 9条消除if...else的锦囊妙计,助你写出更优雅的代码

    点击上方蓝色"方志朋",选择"设为星标"回复"666"获取独家整理的学习资料! 前言 最近在做代码重构,发现了很多代码的烂味道.其他的不多说 ...

  2. 教你如何更优雅的设计Java异常

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 来源:lrwinx https://lrwinx.github.i ...

  3. 如何写出更优雅的代码——编程范式简述

    <如何写出更优雅的代码--编程范式简述>源站链接,阅读体检更佳! 什么是程序? 1976年,瑞士计算机科学家,Algol W,Modula,Oberon 和 Pascal 语言的设计师 N ...

  4. android string.join java8_Java8 - 更优雅的字符串连接(join)收集器 Collectors.joining

    Java8 - 更优雅的字符串连接(join)收集器 Collectors.joining Zebe 2018-10-15 38 0 Java,Java8 StringBuilder,Collecto ...

  5. 6种更优雅书写Python代码!

    1 简介 一些比较熟悉pandas的读者朋友应该经常会使用query().eval().pipe().assign()等pandas的常用方法,书写可读性很高的「链式」数据分析处理代码,从而更加丝滑流 ...

  6. 如何更优雅地对接第三方API

    如何更优雅地对接第三方API 本文所有示例完整代码地址:https://github.com/yu-linfeng/BlogRepositories/tree/master/repositories/ ...

  7. Effective java 系列之更优雅的关闭资源-try-with-resources

    背景: 在Java编程过程中,如果打开了外部资源(文件.数据库连接.网络连接等),我们必须在这些外部资源使用完毕后,手动关闭它们.因为外部资源不由JVM管理,无法享用JVM的垃圾回收机制,如果我们不在 ...

  8. c语言c2182是什么错误,C语言中一种更优雅的异常处理机制

    上一篇文章对C语言中的goto语句进行了较深入的阐述,实际上goto语句是面向过程与面向结构化程序语言中,进行异常处理编程的最原始的支持形式.后来为了更好地.更方便地支持异常处理编程机制,使得程序员在 ...

  9. python写出的程序如何给别人使用-涨姿势!这些小技巧让小白也可以写出更优雅的Python代码!...

    原标题:涨姿势!这些小技巧让小白也可以写出更优雅的Python代码! 一.前言 我前两天回答了两个Python相关的问题,收到了很多赞,从答案被收藏的情况来看,确实对不少人都很有帮助,所以我也很开心. ...

最新文章

  1. 【Udacity】异常值检测/删除
  2. OpenGL shapedpoints异形点的实例
  3. Apache Flink 零基础入门(十二)Flink sink
  4. String类中的常用方法
  5. 转载:8个让程序员追悔莫及的职业建议
  6. await 单步调试 c#_GDB调试指南-单步调试
  7. VS2012中WebAPI项目中的IHttpActionResult不识别的问题----解决办法
  8. 据说每年有四分一的房地产项目烂尾,房子都是预售的钱哪里去了?
  9. html 强制占据一行,html – 如何强制内联div保持在同一行?
  10. 干掉visio,这个画图神器太香了
  11. java读取gpx文件,读取GPX文件+optpars的简单python脚本
  12. 整流3-前级三相PWM整流器软件层面理解
  13. 网站使用微软雅黑需要版权吗
  14. L2-016 愿天下有情人都是失散多年的兄妹(25 分)
  15. PHP快手直播弹幕采集,获取斗鱼弹幕php版(原创)
  16. barcode4j生成条形码及打印条码开发介绍
  17. 关于程序可移植性的问题
  18. win7中USB音箱没有声音解决的方法
  19. c语言编程led数码管,数码管显示函数
  20. 【Web技术】1154- 现代 Web 研发体系中的新一代低/零码搭建

热门文章

  1. SpringBoot 系列教程(八十五):Spring Boot使用MD5加盐验签Api接口之前后端分离架构设计
  2. 设置交换时间同步(NTP)Internet
  3. 大整数相乘java_大整数乘法—java实现
  4. 笔记——zgrep命令(2)
  5. matlab学习笔记11_2高维数组操作 squeeze,ind2sub, sub2ind
  6. hwc android,11.2 Android显示系统框架_android源码禁用hwc和GPU
  7. 芯洲科技SCT53600理想二极管控制器与反向电流保护完全替代德州仪器LM74700
  8. 数据分析和数据挖掘有啥关系?
  9. 浏览器自定义滚动条样式
  10. css超出滚动时隐藏滚动条