前言

在 Java 12 里面有个非常好用但在官方 JEP 没有公布的功能,因为它只是 Collector 中的一个小改动,它的作用是 merge 两个 collector 的结果,这句话显得很抽象,老规矩,我们先来看个图:

管道改造经常会用这个小东西,通常我们叫它「三通」,它的主要作用就是将 downstream1 和 downstream2 的流入合并,然后从 merger 流出

有了这个形象的说明我们就进入正题吧

Collectors.teeing

上面提到的小功能就是 Collectors.teeing API, 先来看一下 JDK 关于该 API 的说明,看着觉得难受的直接忽略,继续向下看例子就好了:

/**
* Returns a {@code Collector} that is a composite of two downstream collectors.
* Every element passed to the resulting collector is processed by both downstream
* collectors, then their results are merged using the specified merge function
* into the final result.
*
* <p>The resulting collector functions do the following:
*
* <ul>
* <li>supplier: creates a result container that contains result containers
* obtained by calling each collector's supplier
* <li>accumulator: calls each collector's accumulator with its result container
* and the input element
* <li>combiner: calls each collector's combiner with two result containers
* <li>finisher: calls each collector's finisher with its result container,
* then calls the supplied merger and returns its result.
* </ul>
*
* <p>The resulting collector is {@link Collector.Characteristics#UNORDERED} if both downstream
* collectors are unordered and {@link Collector.Characteristics#CONCURRENT} if both downstream
* collectors are concurrent.
*
* @param <T> the type of the input elements
* @param <R1> the result type of the first collector
* @param <R2> the result type of the second collector
* @param <R> the final result type
* @param downstream1 the first downstream collector
* @param downstream2 the second downstream collector
* @param merger the function which merges two results into the single one
* @return a {@code Collector} which aggregates the results of two supplied collectors.
* @since 12
*/
public static <T, R1, R2, R>
Collector<T, ?, R> teeing(Collector<? super T, ?, R1> downstream1,
Collector<? super T, ?, R2> downstream2,
BiFunction<? super R1, ? super R2, R> merger) {
return teeing0(downstream1, downstream2, merger);
}

API 描述重的一句话非常关键:

Every element passed to the resulting collector is processed by both downstream collectors

结合「三通图」来说明就是,集合中每一个要被传入 merger 的元素都会经过 downstream1 和 downstream2 的加工处理

其中 merger 类型是 BiFunction,也就是说接收两个参数,并输出一个值,请看它的 apply 方法

@FunctionalInterface
public interface BiFunction<T, U, R> { /**
* Applies this function to the given arguments.
*
* @param t the first function argument
* @param u the second function argument
* @return the function result
*/
R apply(T t, U u);
}

至于可以如何处理,我们来看一些例子吧

例子

为了更好的说明 teeing 的使用,列举了四个例子,看过这四个例子再回看上面的 API 说明,相信你会柳暗花明了

计数和累加

先来看一个经典的问题,给定的数字集合,需要映射整数流中的元素数量和它们的和

class CountSum {
private final Long count;
private final Integer sum;
public CountSum(Long count, Integer sum) {
this.count = count;
this.sum = sum;
} @Override
public String toString() {
return "CountSum{" +
"count=" + count +
", sum=" + sum +
'}';
}
}

通过 Collectors.teeing 处理

CountSum countsum = Stream.of(2, 11, 1, 5, 7, 8, 12)
.collect(Collectors.teeing(
counting(),
summingInt(e -> e),
CountSum::new)); System.out.println(countsum.toString());
  • downstream1 通过 Collectors 的静态方法 counting 进行集合计数
  • downstream2 通过 Collectors 的静态方法 summingInt 进行集合元素值的累加
  • merger 通过 CountSum 构造器收集结果

运行结果:

CountSum{count=7, sum=46}

我们通过 teeing 一次性得到我们想要的结果,继续向下看其他例子:

最大值与最小值

通过给定的集合, 一次性计算出集合的最大值与最小值,同样新建一个类 MinMax,并创建构造器用于 merger 收集结果

class MinMax {
private final Integer min;
private final Integer max;
public MinMax(Integer min, Integer max) {
this.min = min;
this.max = max;
} @Override
public String toString() {
return "MinMax{" +
"min=" + min +
", max=" + max +
'}';
}
}

通过 teeing API 计算结果:

MinMax minmax = Stream.of(2, 11, 1, 5, 7, 8, 12)
.collect(Collectors.teeing(
minBy(Comparator.naturalOrder()),
maxBy(Comparator.naturalOrder()),
(Optional<Integer> a, Optional<Integer> b) -> new MinMax(a.orElse(Integer.MIN_VALUE), b.orElse(Integer.MAX_VALUE)))); System.out.println(minmax.toString());
  • downstream1 通过 Collectors 的静态方法 minBy,通过 Comparator 比较器按照自然排序找到最小值
  • downstream2 通过 Collectors 的静态方法 maxBy,通过 Comparator 比较器按照自然排序找到最大值
  • merger 通过 MinMax 构造器收集结果,只不过为了应对 NPE,将 BiFunction 的两个入参经过 Optional 处理

运行结果:

MinMax{min=1, max=12}

为了验证一下 Optional,我们将集合中添加一个 null 元素,并修改一下排序规则来看一下排序结果:

MinMax minmax = Stream.of(null, 2, 11, 1, 5, 7, 8, 12)
.collect(Collectors.teeing(
minBy(Comparator.nullsFirst(Comparator.naturalOrder())),
maxBy(Comparator.nullsLast(Comparator.naturalOrder())),
(Optional<Integer> a, Optional<Integer> b) -> new MinMax(a.orElse(Integer.MIN_VALUE), b.orElse(Integer.MAX_VALUE))));
  • downstream1 处理规则是将 null 放在排序的最前面
  • downstream2 处理规则是将 null 放在排序的最后面
  • merger 处理时,都会执行 optional.orElse 方法,分别输出最小值与最大值

运行结果:

MinMax{min=-2147483648, max=2147483647}

瓜的总重和单个重量

接下来举一个更贴合实际的操作对象的例子

// 定义瓜的类型和重量
class Melon {
private final String type;
private final int weight;
public Melon(String type, int weight) {
this.type = type;
this.weight = weight;
} public String getType() {
return type;
} public int getWeight() {
return weight;
}
} // 总重和单个重量列表
class WeightsAndTotal {
private final int totalWeight;
private final List<Integer> weights;
public WeightsAndTotal(int totalWeight, List<Integer> weights) {
this.totalWeight = totalWeight;
this.weights = weights;
} @Override
public String toString() {
return "WeightsAndTotal{" +
"totalWeight=" + totalWeight +
", weights=" + weights +
'}';
}
}

通过 teeing API 计算总重量和单个列表重量

List<Melon> melons = Arrays.asList(new Melon("Crenshaw", 1200),
new Melon("Gac", 3000), new Melon("Hemi", 2600),
new Melon("Hemi", 1600), new Melon("Gac", 1200),
new Melon("Apollo", 2600), new Melon("Horned", 1700),
new Melon("Gac", 3000), new Melon("Hemi", 2600)
); WeightsAndTotal weightsAndTotal = melons.stream()
.collect(Collectors.teeing(
summingInt(Melon::getWeight),
mapping(m -> m.getWeight(), toList()),
WeightsAndTotal::new)); System.out.println(weightsAndTotal.toString());
  • downstream1 通过 Collectors 的静态方法 summingInt 做重量累加
  • downstream2 通过 Collectors 的静态方法 mapping 提取出瓜的重量,并通过流的终结操作 toList() 获取结果
  • merger 通过 WeightsAndTotal 构造器获取结果

运行结果:

WeightsAndTotal{totalWeight=19500, weights=[1200, 3000, 2600, 1600, 1200, 2600, 1700, 3000, 2600]}

继续一个更贴合实际的例子吧:

预约人员列表和预约人数

class Guest {
private String name;
private boolean participating;
private Integer participantsNumber; public Guest(String name, boolean participating, Integer participantsNumber) {
this.name = name;
this.participating = participating;
this.participantsNumber = participantsNumber;
}
public boolean isParticipating() {
return participating;
} public Integer getParticipantsNumber() {
return participantsNumber;
} public String getName() {
return name;
}
} class EventParticipation {
private List<String> guestNameList;
private Integer totalNumberOfParticipants; public EventParticipation(List<String> guestNameList, Integer totalNumberOfParticipants) {
this.guestNameList = guestNameList;
this.totalNumberOfParticipants = totalNumberOfParticipants;
} @Override
public String toString() {
return "EventParticipation { " +
"guests = " + guestNameList +
", total number of participants = " + totalNumberOfParticipants +
" }";
}
}

通过 teeing API 处理

var result = Stream.of(
new Guest("Marco", true, 3),
new Guest("David", false, 2),
new Guest("Roger",true, 6))
.collect(Collectors.teeing(
Collectors.filtering(Guest::isParticipating, Collectors.mapping(Guest::getName, Collectors.toList())),
Collectors.summingInt(Guest::getParticipantsNumber),
EventParticipation::new
));
System.out.println(result);
  • downstream1 通过 filtering 方法过滤出确定参加的人,并 mapping 出他们的姓名,最终放到 toList 集合中
  • downstream2 通过 summingInt 方法计数累加
  • merger 通过 EventParticipation 构造器收集结果

其中我们定义了 var result 来收集结果,并没有指定类型,这个语法糖也加速了我们编程的效率

运行结果:

EventParticipation { guests = [Marco, Roger], total number of participants = 11 }

总结

其实 teeing API 就是灵活应用 Collectors 里面定义的静态方法,将集合元素通过 downstream1 和 downstream2 进行处理,最终通过 merger 收集起来,当项目中有同时获取两个收集结果时,是时候应用我们的 teeing API 了

灵魂追问

  1. Collectors 里面的静态方法你应用的熟练吗?
  2. 项目中你们在用 JDK 的版本是多少?
  3. Lambda 的使用熟练吗?
  4. 你的灯还亮着吗?

  1. Java并发系列文章,持续更新中
  2. Maven 依赖传递性透彻理解
  3. 读取Excel还用POI? 试试这个开源框架
  4. 基础面试,为什么面试官总喜欢问Java String

欢迎持续关注公众号:「日拱一兵」

  • 前沿 Java 技术干货分享
  • 高效工具汇总 | 回复「工具」
  • 面试问题分析与解答
  • 技术资料领取 | 回复「资料」

以读侦探小说思维轻松趣味学习 Java 技术栈相关知识,本着将复杂问题简单化,抽象问题具体化和图形化原则逐步分解技术问题,技术持续更新,请持续关注......


Java12 Collectors.teeing 你需要了解一下的更多相关文章

  1. Java升级那么快,多个版本如何灵活切换和管理?

    前言 近两年,Java 版本升级频繁,感觉刚刚掌握 Java8,写本文时,已听到 java14 的消息,无论是尝鲜新特性(Java12 中 Collectors.teeing 超强功能使用),还是由于 ...

  2. [转帖]Java升级那么快,多个版本如何灵活切换和管理?

    Java升级那么快,多个版本如何灵活切换和管理? https://segmentfault.com/a/1190000021037771 前言 近两年,Java 版本升级频繁,感觉刚刚掌握 Java8 ...

  3. 从Java 9 到 Java 17 新特性梳理

    Java 9 新的创建集合的方法  // [1, 2, 3, 4]  List<Integer> integers = List.of(1, 2, 3, 4);  // {1,2,3}   ...

  4. Java程序员必备基础:JDK 5-15都有哪些经典新特性

    前言 JDK 15发布啦~ 我们一起回顾JDK 5-15 的新特性吧,大家一起学习哈~ 本文已经收录到github ❝ https://github.com/whx123/JavaHome ❞ 「公众 ...

  5. springcloud starter(一)

    Spring Cloud - Getting Started Example, 转载自:https://www.logicbig.com/tutorials/spring-framework/spri ...

  6. Java9至17的新特性总结

    总览 讲讲Java 9-17 的一些语法糖和一些新发布的jeps, 重点讲讲JVM的垃圾回收器 时间线 SpringBoot 为什么选择Java17这个版本.我估计跟下面这个图有关系. Java 8 ...

  7. Java SE 12 新增特性

    Java SE 12 新增特性 作者:Grey 原文地址:Java SE 12 新增特性 源码 源仓库: Github:java_new_features 镜像仓库: GitCode:java_new ...

  8. Java12新特性 -- 其他新增,移除,废弃项

    支持unicode 11 JDK 12版本包括对Unicode 11.0.0的支持.在发布支持Unicode 10.0.0的JDK 11之后,Unicode 11.0.0引 入了以下JDK 12中包含 ...

  9. [转]深入理解Java 8 Lambda(类库篇——Streams API,Collectors和并行)

    以下内容转自: 作者:Lucida 微博:@peng_gong 豆瓣:@figure9 原文链接:http://zh.lucida.me/blog/java-8-lambdas-insideout-l ...

随机推荐

  1. 并发编程的模型分类(转载于https://link.zhihu.com/?target=http%3A//www.54tianzhisheng.cn/2018/02/28/Java-Memory-Model/)强烈推荐!

    在并发编程需要处理的两个关键问题是:线程之间如何通信 和 线程之间如何同步. 通信 通信 是指线程之间以何种机制来交换信息.在命令式编程中,线程之间的通信机制有两种:共享内存 和 消息传递. 在共享内 ...

  2. Javascript中Promise的简单使用

    // 函数功能:1秒以后创建一个10以内的随机整数,并判断这个数是否为偶数:如果是偶数则做一件事情,如果是奇数则做另一件事情 function doSomthing() { var promise = ...

  3. 【TencentOS tiny】深度源码分析(7)——事件

    引言 大家在裸机编程中很可能经常用到flag这种变量,用来标志一下某个事件的发生,然后在循环中判断这些标志是否发生,如果是等待多个事件的话,还可能会if((xxx_flag)&&(xx ...

  4. 移动端Rem布局注意事项

    1.布局的总体结构框架:      2.注意事项: (a):如果是左右两栏的布局方式,须在article的同级加一个aside: 因为是同级,所以必须设置同样的样式:而且他俩的父级,也就是sectio ...

  5. RF作用与目的

    robotframework自动化原理:通过ride工具编写脚本,加载指定的UI测试库,再通过pybot程序去运行指定脚本,调用浏览器驱动,打开浏览器,操作浏览器页面元素,达到模拟用户操作的行为 为什 ...

  6. 零基础转行web前端,要学习多久?需要掌握些什么?

    web前端开发技术人才越来越吃香,而且web前端领域划分越来越细,对技术的需求越来越高,想学习web前端的人也是越来越多.那么,如何学习web前端知识?从哪开始?转型成为web前端工程师需要学些什么? ...

  7. [网络流 24 题] luoguP2763 试题库问题

    [返回网络流 24 题索引] 题目描述 假设一个试题库中有 nnn 道试题.每道试题都标明了所属类别.同一道题可能有多个类别属性.现要从题库中抽取 mmm 道题组成试卷.并要求试卷包含指定类型的试题. ...

  8. [JZOJ4685] 【NOIP2016提高A组8.12】礼物

    Description 夏川的生日就要到了.作为夏川形式上的男朋友,季堂打算给夏川买一些生日礼物.商店里一共有种礼物.夏川每得到一种礼物,就会获得相应喜悦值Wi(每种礼物的喜悦值不能重复获得).每次, ...

  9. Controller层的方法访问标志与Spring装配与AspectJ切面处理

    最近在做AspectJ实现的日志模块,在spring配置中加入了<aop:aspectj-autoproxy/>,之后发现,只要有用到自定义注解的类,某些方法经MVC请求时就报空指针错误. ...

  10. 【css】CSS设置文字不能被选中

    CSS设置文字不能被选中 /*设置文字不能被选中 以下为css样式*/ -webkit-user-select:none; -moz-user-select:none; -ms-user-select ...