使用Java8进行分组(多个字段的组合分组)

 更新时间:2022年07月18日 10:47:59   作者:诗水人间  
本文主要介绍了使用Java8进行分组(多个字段的组合分组),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

在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 list
        List<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 list
        List<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 list
        List<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方法数组
     */
    @SafeVarargs
    public 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)));
    }

    /**
     * 分组工具根据函数式接口使用分组,将数据根据分组结果进行拆分
     */
    @SafeVarargs
    public 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 list
        List<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 separator
        System.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 delimiters
        System.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 delimiters
        System.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进行分组(多个字段的组合分组)的文章就介绍到这了,更多相关Java8 分组内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • spring源码下载、编译、debug的详细教程

    spring源码下载、编译、debug的详细教程

    这篇文章主要介绍了spring源码下载、编译、debug的详细教程,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-10-10
  • 关于SpringCloud Ribbon替换轮询算法问题

    关于SpringCloud Ribbon替换轮询算法问题

    Spring Cloud Ribbon是基于Netlix Ribbon实现的一套客户端负载均衡的工具。接下来通过本文给大家介绍SpringCloud Ribbon替换轮询算法问题,需要的朋友可以参考下
    2022-01-01
  • java报错之springboot3+vue2项目web服务层报错总结

    java报错之springboot3+vue2项目web服务层报错总结

    java入门学习,随手记录一下开发过程中产生的报错,有些错误是网上搜索再加上自己尝试,随手引用了一些其他人的记录,也是留给自己看的,或是希望能对其他初学者有帮助

    2023-06-06
  • SpringBoot Web请求响应详细代码示例

    SpringBoot Web请求响应详细代码示例

    在Web开发中请求和响应是必不可少的环节,Spring Boot Web应用中请求响应的分层解耦是构建高效、可维护系统的关键实践,下面这篇文章主要介绍了SpringBoot Web请求响应的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-09-09
  • 微服务eureka和nacos案例详解

    微服务eureka和nacos案例详解

    这篇文章主要介绍了微服务eureka和nacos,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-06-06
  • idea maven依赖引入失效无法正常导入依赖问题的解决方法

    idea maven依赖引入失效无法正常导入依赖问题的解决方法

    有时候idea导入一个新项目,或者pom文件修改(新增)了依赖,pom文件和代码会报红,提示依赖包不存在,下面这篇文章主要给大家介绍了关于idea maven依赖引入失效无法正常导入依赖问题的解决方法,需要的朋友可以参考下
    2023-04-04
  • Java中怎样处理空指针异常

    Java中怎样处理空指针异常

    程序中的变量是 null,就意味着它没有引用指向或者说没有指针。这时,我们对这个变量进行任何操作,都必然会引发空指针异常,本文主要介绍了Java中怎样处理空指针异常,感兴趣的可以了解一下
    2022-05-05
  • 关于Java中ArrayList的源码分析

    关于Java中ArrayList的源码分析

    这篇文章主要从源码角度带大家深入了解一下Java中ArrayList的构造方法和属性等知识,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一下
    2023-05-05
  • 通过springboot+mybatis+druid配置动态数据源

    通过springboot+mybatis+druid配置动态数据源

    这篇文章主要介绍了通过springboot+mybatis+druid配置动态数据源,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,,需要的朋友可以参考下
    2019-06-06
  • 实例讲解Java并发编程之变量

    实例讲解Java并发编程之变量

    这篇文章主要介绍了实例讲解Java并发编程之变量,本文讲解了编写线程安全需要关心的共享变量和可变变量,需要的朋友可以参考下
    2015-04-04

最新评论