java8专栏目录:

  1. java8实战读书笔记:Lambda表达式语法与函数式编程接口
  2. java8实战读书笔记:复合Lambda表达式
  3. java8实战读书笔记:初识Stream、流的基本操作(流计算)
  4. java8实战读书笔记:数值流、Stream创建与Optional类的使用
  5. java8读书笔记:探究java8流收集数据原理

本文将从Collectos中构建收集器入手,详细介绍java8提供了哪些收集器,重点介绍:toList、toSet、toCollection、joining、groupBy(包含多级分组)、reducing的核心实现原理与使用示例。

本节目录

  • 1、toList、toSet、toCollection
  • 2、joining
    • 2.1 joining
    • 2.2 joining(CharSequence delimiter)
  • 3、聚合相关收集器
  • 4 分组
    • 4.1 从示例入手
    • 4.2 源码分析groupingBy方法
  • 5、 partitioningBy
  • 6、 reducing

1、toList、toSet、toCollection

首先对流中的数据进行计算,最终返回的数据类型为集合。Collectors中定义了如下3集合类收集器,其声明如下:

public static <T> Collector<T, ?, List<T>> toList()
public static <T> Collector<T, ?, Set<T>> toSet()
public static <T, C extends Collection<T>> Collector<T, ?, C> toCollection(Supplier<C> collectionFactory)

温馨提示:建议根据上篇的理论,再来反推一下这些Collector中的核心属性的值,例如supplier、accumulator、combiner、characteristics。不过特别注意,toList、toCollection是不支持并行运行的,但toSet()方法支持并行运行。

我们首先来看一个一直使用的示例,返回菜单中所有菜品的名称:

public static void test_toList(List<Dish> menu) {List<String> names = menu.stream().map(Dish::getName).collect(Collectors.toList());
}

由于toList方法的实现原理已经在 java8读书笔记:探究java8流收集数据原理中也详细介绍,故本篇不再重点介绍。

2、joining

Collectors定义了如下3个重载方法。

public static Collector<CharSequence, ?, String> joining()
public static Collector<CharSequence, ?, String> joining(CharSequence delimiter)
public static Collector<CharSequence, ?, String> joining(CharSequence delimiter,CharSequence prefix, CharSequence suffix)

2.1 joining

public static Collector<CharSequence, ?, String> joining() {return new CollectorImpl<CharSequence, StringBuilder, String>(StringBuilder::new, StringBuilder::append,(r1, r2) -> { r1.append(r2); return r1; },StringBuilder::toString, CH_NOID);
}
  • Supplier< A> supplier()
    其函数为StringBuilder::new,即通过该方法创建一个StringBuilder方法,作为累积器的初始值。
  • BiConsumer<A, T> accumulator
    累积器:StringBuilder::append,即会对流中的元素执行追加。
  • BinaryOperator< A> combiner
    组合器,也是调用append方法,进行字符串的规约。
  • Function<A,R> finisher
    转换器:由于累积器返回的最终对象为StringBuilder,并不是目标String类型,故需要调用StringBuilder#toString方法进行转换
  • Set< Characteristics> characteristics
    无任何行为。

从上面的函数定义我们可以得出该方法的作用:针对字符串流,会对流中的元素执行字符的追加动作,流元素之间没有分隔符号,示例如下:

2.2 joining(CharSequence delimiter)

public static Collector<CharSequence, ?, String> joining(CharSequence delimiter) {return joining(delimiter, "", "");
}
public static Collector<CharSequence, ?, String> joining(CharSequence delimiter,CharSequence prefix,CharSequence suffix) {return new CollectorImpl<>(() -> new StringJoiner(delimiter, prefix, suffix),StringJoiner::add, StringJoiner::merge,StringJoiner::toString, CH_NOID);
}
  • Supplier< A> supplier()
    其函数为() -> new StringJoiner(delimiter, prefix, suffix),累积器的初始值为StringJoiner。
  • BiConsumer<A, T> accumulator
    累积器:StringJoiner::append,即会对流中的元素执行追加。
  • BinaryOperator< A> combiner
    组合器,StringJoiner::merge。
  • Function<A,R> finisher
    转换器:由于累积器返回的最终对象为StringBuilder,并不是目标String类型,故需要调用StringBuilder#toString方法进行转换
  • Set< Characteristics> characteristics
    无任何行为。

其示例如下:

3、聚合相关收集器

聚合相关收集器,主要包括minBy、maxBy、sum、avg等相关函数,其主要方法声明如下:

public static <T> Collector<T, ?, Optional<T>> minBy(Comparator<? super T> comparator)
public static <T> Collector<T, ?, Optional<T>> maxBy(Comparator<? super T> comparator)
public static <T> Collector<T, ?, Integer> summingInt(ToIntFunction<? super T> mapper)
public static <T> Collector<T, ?, Long> summingLong(ToLongFunction<? super T> mapper)
public static <T> Collector<T, ?, Double> summingDouble(ToDoubleFunction<? super T> mapper)
public static <T> Collector<T, ?, Double> averagingInt(ToIntFunction<? super T> mapper)
public static <T> Collector<T, ?, Double> averagingLong(ToLongFunction<? super T> mapper)
public static <T> Collector<T, ?, Double> averagingDouble(ToDoubleFunction<? super T> mapper)

上面这些方法比较简单,下面举个简单的例子介绍其使用:

4 分组

Collectors提供了3个groupingBy重载方法,我们一个一个来理解。

4.1 从示例入手

我们从其中一个最简单的函数说起,从而慢慢引出

public static <T, K> Collector<T, ?, Map<K, List<T>>> groupingBy(Function<? super T, ? extends K> classifier)
  • Collector<T, ?, Map<K, List< T>>>
    首先我们先来关注该方法的返回值Collector<T, ?, Map<K,List< T>>,其最终返回的数据类型为:Map<K, List< T>>
  • Function<? super T, ? extends K> classifier
    分类函数。

示例如下:例如如下是购物车实体类,并且初始化数据如下:

public class ShopCar {private int id;private int sellerId;private String sellerName;private String goodsName;private int buyerId;private String buyerName;private int num;
}
// 初始化数据如下:
public static List<ShopCar> initShopCar() {return Arrays.asList(new ShopCar(1, 1, "天猫" , "华为手机", 1 , "dingw", 5),new ShopCar(1, 2, "京东" , "华为手机", 2 , "ly", 2),new ShopCar(1, 1, "京东" , "小米手机", 3 , "zhl", 3),new ShopCar(1, 2, "1号店" , "华为手机", 1 , "dingw", 5),new ShopCar(1, 2, "天猫" , "苹果手机", 1 , "dingw", 2));
}

首先我们看一下java8之前的写法:

public static void test_group_jdk7(List<ShopCar> shopCars) {Map<String, List<ShopCar>> shopBySellerNameMap = new HashMap<>();for(ShopCar c : shopCars ) {if(shopBySellerNameMap.containsKey( c.getSellerName() )) {shopBySellerNameMap.get(c.getSellerName()).add(c);} else {List<ShopCar> aList = new ArrayList<>();shopBySellerNameMap.put(c.getSellerName(), aList);aList.add(c);}}print(shopBySellerNameMap);
}

上面的代码应该很容易理解,根据商家名称进行分组,拥有相同商家的名称的购物车项组成一个集合,最终返回Map<String, List< ShopCar >>类型的数据。

那如何使用java8的流分组特性来编写对应的代码呢?下面的思考过程非常关键,经过前面的学习,我想大家应该也具备了如下分析与编写的能力?

首先其声明如下:public static <T, K> Collector<T, ?, Map<K, List< T>>> groupingBy(Function<? super T, ? extends K> classifier),那在本例中,T,K这两个参数代表什么意思呢?

  • T : ShopCar
  • K : String (sellerName的类型)
    其判断的主要依据为groupingBy方法返回的参数Collector<T, ?, Map<K, List< T>>>,代表<T, A, R>,其中最后一个泛型参数R对应的就是本例需要返回的Map<K, List< T>>,故分析出T,K代表的含义。

然后再看其参数:Function<? super T, ? extends K> classifier,即接受的函数式编程接口为T -> K,即通过ShopCar 返回一个String,又根据其名称可知,该函数为一个分类函数,故基本可以写成如下代码:

public static void test_group_jdk8(List<ShopCar> shopCars) {Map<String, List<ShopCar>> shopBySellerNameMap =  shopCars.stream().collect(Collectors.groupingBy(ShopCar::getSellerName));//.collect(Collectors.groupingBy( (ShopCar c) -> c.getSellerName() ))print(shopBySellerNameMap);
}

其运行效果如下:
为了加深对groupingBy方法的理解,接下来我们重点分析一下其源码的实现。

4.2 源码分析groupingBy方法

public static <T, K> Collector<T, ?, Map<K, List<T>>> groupingBy(Function<? super T, ? extends K> classifier) {  // @1return groupingBy(classifier, toList());                                                                     // @2
}

代码@1:分类参数,已经在上文中详细介绍。
代码@2:调用groupingBy重载方法,传入的参数为toList(),有点意思,传入的参数为Collectors.toList(),结合上文中的示例,需要返回值类型为:Map<String, List< ShopCar>>,与这里的List对应起来了。

public static <T, K, A, D> Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream) {return groupingBy(classifier, HashMap::new, downstream);
}

该重载方法,再次调用3个参数的groupingBy方法,其中第二个参数为HashMap::new,即创建一个Map对象,我们重点关注3个参数的groupingBy。

public static <T, K, D, A, M extends Map<K, D>> Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,
Supplier<M> mapFactory,
Collector<? super T, A, D> downstream) { // @1Supplier<A> downstreamSupplier = downstream.supplier();        // @2 startBiConsumer<A, ? super T> downstreamAccumulator = downstream.accumulator();BiConsumer<Map<K, A>, T> accumulator = (m, t) -> {K key = Objects.requireNonNull(classifier.apply(t), "element cannot be mapped to a null key");A container = m.computeIfAbsent(key, k -> downstreamSupplier.get());downstreamAccumulator.accept(container, t);}; // @2 endBinaryOperator<Map<K, A>> merger = Collectors.<K, A, Map<K, A>>mapMerger(downstream.combiner());   // @3@SuppressWarnings("unchecked")Supplier<Map<K, A>> mangledFactory = (Supplier<Map<K, A>>) mapFactory;                            if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) {           // @4return new CollectorImpl<>(mangledFactory, accumulator, merger, CH_ID);}else {                                                                                            // @5@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);}
}

代码@1:参数介绍:

  • Function<? super T, ? extends K> classifier
    分类函数。
  • Supplier< M> mapFactory
    map构建函数。()-> Map
  • Collector<? super T, A, D> downstream
    下游收集器,在上面的示例中,该参数为Collectos.toList()。

代码@2:构建最终的累积器。其实现要点如下:

  • 对流中的元素,使用Function<? super T, ? extends K> classifier,获取对应的分类键值。
  • 使用mangledFactory创建累积初始值,并调用Map#computeIfAbsent方法,放入的值为:downstreamSupplier.get()。可以类比上例中Map<String, List< T>>,请结合如下代码进行理解:

代码@3:构建最终的组合器,这里使用的是Collectos.mapMerger,其内部的实现就是对每个元素,执行map#merge方法。

代码@4:如果收集器的行为为IDENTITY_FINISH,直接根据上面已创建的累积器、组合器,创建一个最终的收集器。

代码@5:如果收集器的行为不包含IDENTITY_FINISH,则需要最终调用原收集器的finisher方法。才能最终需要返回的类型。

groupingBy的原理就讲解到这里,我们接下来思考如下场景:
还是上面的购物车场景,现在要求先按照供应商名称分组,然后按照购买人分组(即多级分组),类似于SQL group by sellerId,buyerId。

思考过程:首先二级分类需要返回的数据类型为Map<String /** sellerName*/, Map<String、/** buyerId*/,List< ShopCar>> >,而只有一个参数的groupingBy(Function<? super T, ? extends K> classifier),只接受一个分类参数,其内部会调用两个参数的groupingBy(Function<? super T, ? extends K> classifier,Collector<? super T, A, D> downstream),默认第二个参数为Collectors.toList(),故我们可以做的文章是改变这个默认值,传入符合业务场景的收集器,结合目前的需求,很显然,该参数应该是支持分组的收集器,即应该可以通过嵌套groupingBy方法,实现二级分组,其具体代码如下:

/*** 二级分组示例* @param shopCars*/
public static void test_level_group(List<ShopCar> shopCars) {Map<String, Map<String, List<ShopCar>>>  result = shopCars.stream().collect(Collectors.groupingBy(ShopCar::getSellerName,Collectors.groupingBy(ShopCar::getBuyerName)));System.out.println(result);
}

温馨提示:上面介绍的分组,主要的Map存储结构为HashMap,java8为ConcurrentMap对应类继承体系提供了对应的分组函数:groupingByConcurrent,其使用方法与groupingBy方法类型,故不重复介绍。

5、 partitioningBy

分区,分区可以看出是分组的特殊化,接受的分类函数返回boolean类型,即是谓词Predicate<? super T> predicate。其声明如下:

public static <T> Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate)
public static <T, D, A> Collector<T, ?, Map<Boolean, D>> partitioningBy(Predicate<? super T> predicate, Collector<? super T, A, D> downstream)

由于其用法与分组类似,故这里就一笔带过了。

6、 reducing

规约。其函数声明如下:

public static <T, U> Collector<T, ?, U> reducing(U identity, Function<? super T, ? extends U> mapper, BinaryOperator<U> op)

其参数如下:

  • U identity
    规约初始值。
  • Function<? super T, ? extends U> mapper
    累加器函数。
  • BinaryOperator op
    组合器函数。
    关于Collectors.reducing,建议可以直接使用Stream自身提供的reducing方法,具体请参考博文:java8实战读书笔记:初识Stream、流的基本操作(流计算)

欢迎加笔者微信号(dingwpmz),加群探讨,笔者优质专栏目录:
1、源码分析RocketMQ专栏(40篇+)
2、源码分析Sentinel专栏(12篇+)
3、源码分析Dubbo专栏(28篇+)
4、源码分析Mybatis专栏
5、源码分析Netty专栏(18篇+)
6、源码分析JUC专栏
7、源码分析Elasticjob专栏
8、Elasticsearch专栏(20篇+)
9、源码分析MyCat专栏

java8实战:使用流收集数据之toList、joining、groupBy(多字段分组)相关推荐

  1. java8流实战-用流收集数据实践简记

    文章目录 收集器简介 什么是流收集器 一个get start告诉你流收集器的强大之处 流收集器工作原理简析 规约和汇总 查找流中的最大值和最小值 汇总和和一次性获取所有规约结果 连接字符串 关于规约汇 ...

  2. java8实战:使用流收集数据之toList、joining、groupBy(多字段分组)(1)

    return new CollectorImpl<CharSequence, StringBuilder, String>( StringBuilder::new, StringBuild ...

  3. 《Java8实战》-第六章读书笔记(用流收集数据-01)

    用流收集数据 我们在前一章中学到,流可以用类似于数据库的操作帮助你处理集合.你可以把Java 8的流看作花哨又懒惰的数据集迭代器.它们支持两种类型的操作:中间操作(如 filter 或 map )和终 ...

  4. Java8种Stream流相关操作——集合的筛选、归约、分组、聚合

    过滤.筛选   filter skip /*** 过滤 筛选*/@Testpublic void test2(){List<String> list = Arrays.asList(&qu ...

  5. 《Java8实战》笔记(06):用流收集数据

    文章目录 收集器简介 收集器用作高级归约 预定义收集器 归约和汇总 查找流中的最大值和最小值 汇总 连接字符串 广义的归约汇总 Stream接口的collect和reduce有何不同 收集框架的灵活性 ...

  6. java8学习:用流收集数据

    内容来自< java8实战 >,本篇文章内容均为非盈利,旨为方便自己查询.总结备份.开源分享.如有侵权请告知,马上删除. 书籍购买地址:java8实战 下面我们将采用这样一个实体类 @Da ...

  7. 用流收集数据Collectors的用法介绍分组groupingBy、分区partitioningBy(一)

    文章目录 一.收集器简介 二.归约和汇总 1.查找流中最大值和最小值Collectors.maxBy和,Collectors.minBy 2.汇总 3.连接字符串 4.广义归约汇总 三.分组 1.多级 ...

  8. Java 8 (5) Stream 流 - 收集数据

    在前面已经使用过collect终端操作了,主要是用来把Stream中的所有元素结合成一个List,在本章中,你会发现collect是一个归约操作,就像reduce一样可以接受各种做法作为参数,将流中的 ...

  9. Java8实战学习笔记(三)——函数式数据处理

    一.引入流 (一).引言 1.流是什么 流是Java API的新成员,它允许你以声明性方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现).可以看成遍历数据集的高级迭代器. 流可以透明地并行 ...

  10. 《Java8实战》笔记汇总

    <Java8实战>笔记(01):为什么要关心Java8 <Java8实战>笔记(02):通过行为参数传递代码 <Java8实战>笔记(03):Lambda表达式 & ...

最新文章

  1. 用C#生成随机中文汉字验证码的基本原理
  2. qtp9.2测试java_QTP的使用举例说明
  3. mysql的学习笔记(四)
  4. HNOI2012永无乡
  5. 超实用!VLAN、TRUNK、VLAN间路由基础
  6. C# 结构体 简明介绍
  7. 信息系统项目管理师论文基础知识
  8. 二叉排序树(完整案例与完整C语言代码)
  9. spring+quartz 完整例子
  10. 国际最具潜力IT专业认证
  11. php刷新父页面,layui: 子iframe关闭/传值/刷新父页面
  12. 11_less中的条件判断
  13. OAuth2.0_授权服务配置_客户端详情配置_Spring Security OAuth2.0认证授权---springcloud工作笔记142
  14. java基础——java学习心得
  15. 安装windows系统到移动硬盘
  16. PowerVR 6系列架构分析
  17. html5图片格式有什么,jpeg是什么?
  18. C++实现与电脑进行石头剪刀布的游戏
  19. AttacKG: Constructing Technique Knowledge Graph from Cyber Threat Intelligence Reports 源码复现
  20. extra argument in call

热门文章

  1. 无人机——凤凰模拟器篇(四)图文安装教程(附软件下载)
  2. 十三届蓝桥杯国赛 内存空间 python 满分答案
  3. Oracle学习笔记
  4. 在点光源的基础上利用光域网来分布光的传播范围及方向_daiding
  5. intel网卡驱动下载linux,intel网卡驱动下载
  6. 如何打开屏幕坏的手机_手机屏幕坏了怎么打开usb调试
  7. 搜索引擎技术揭密:网络蜘蛛
  8. QQ音乐2009去广告方法
  9. 《模式分类》第二版 第二章课后编程题
  10. matlab数字图像处理课程设计报告,数字图像处理实验报告范文