JDK8对List进行分组操作(stream的groupby)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JDK8对List进行分组操作(stream的groupby)相关的知识,希望对你有一定的参考价值。

参考技术A 对List进行分组,也可以理解为将List转换为Map集合。

group by生成一个拥有分组功能的Collector,有三个重载方法。

2.1 可以对结果进行映射

2.2 可以对结果进行求和

2.3 对结果的统计

如果kv是唯一对应的,可以使用Collectors.toMap来实现。
推荐阅读:JDK8中List转换为Map

JDK1.8新特性——Collector接口和Collectors工具类

分组:Stream不得不知的操作

场景

假如现在存在两个列表List<A> 和List<B> 。

其中A包含 B的集合。

代码如下。

A对象

@Data
public void A
    
    private Integer id;

    private String name;
    
    private List<B> bList;

B 对象

@Data
public void B
    private Integer AId;

    private String name;
    
    private String storeName;

    private Long startTime;
    
    private BigDecimal amount;

...

现在需要将符合条件的List<B>的内容set到List<A>中

用来组合出一个新的VO返回出去。

 

常规解决方案

一般解决方法都是直接使用for循环操作。

先讲List<A>循环,然后根据条件,找到对应的List<B>的对象List,

一条条set到A中去。

常规伪代码

List<A> listA;
List<B> listB;
listA.foreach(a->
    List<B> newListB = new ArrayList<>();
    for(int i=0;i<listB.size();i++)
        if(a.id==listB[i].AId)
          newListB.add(listB[i]);
        
    
    a.setBList(newListB);
);

我们可以看到,在常规操作里面,可以看到当前会有大量的循环。

循环影响性能,这个事情我们就不作过多讨论了。

而且代码看起来的话,大量的嵌套和判断,导致代码的自读性差。

Stream常规操作

如果熟悉stream的话,

一般都会把里面嵌套的那层for使用stream来操作。

例如

List<A> listA;
List<B> listB;
listA.foreach(a->
    
    List<B> newListB = listB.stream.filter(e->e.getAId==a.getId).collect(Collectors.toList()); 

    a.setBList(newListB);
    
);

上面的stream操作也会产生很多的对象

同时,每次循环都会将listB全部比对一次。

在性能上来讲,还是有待提高。

那么有没有一种比较优雅的方案呢?

在这里,推荐一种新的方案。

新解决方案

Stream的groupingBy方法

使用这个方法的源码在Collectors中。

groupingBy源码

public final class Collectors 
 ...
    /* @param <T> the type of the input elements
     * @param <K> the type of the keys
     * @param classifier the classifier function mapping input elements to keys
     * @return a @code Collector implementing the group-by operation
     *
     * @see #groupingBy(Function, Collector)
     * @see #groupingBy(Function, Supplier, Collector)
     * @see #groupingByConcurrent(Function)
     */
    public static <T, K> Collector<T, ?, Map<K, List<T>>>
    groupingBy(Function<? super T, ? extends K> classifier) 
        return groupingBy(classifier, toList());
    
 ...

通过groupingBy的分类器,可以直接将当前List<B>中的数据,进行分类。

这样得到的数据就可以直接赋值了。

示例

我们还是拿原来的代码来操作


List<A> listA;
List<B> listB;
//跳过赋值操作
Map<Integer,List<B>> bMap = listB.stream.collect(Collectors.groupingBy(B::getAId));

listA.foreach(a->
    
    a.setBList(bMap.get(a.getId()));
    
);

我们将listB直接进行分组,使用map进行存储。map对应的key就是groupingBy中的分类器B::getAId。value就是对应的分组的数据。

这样一看,我们的代码非常简洁。

总结

groupingBy在分类操作的时候非常有用,我们可以直接将数据分好组。在后续需要调用的时候,直接get就好,就不需要进行二次计算操作了。在执行速度和代码可读性方面都有不错的提升。

顺便再上面的中间,再加点料,如果需要根据AId来对分组的amount属性求和。这样的话,你会怎样做呢?

以上是关于JDK8对List进行分组操作(stream的groupby)的主要内容,如果未能解决你的问题,请参考以下文章

利用stream对list集合中的bigdecimal进行分组求和,均值,最大值,最小值

JDK8集合的便捷操作

Java8使用Stream流实现List列表的查询统计排序分组

java8 stream对数组按条件进行分组

工作中使用过与stream流相关的API操作

工作中使用过与stream流相关的API操作