Java12 Collectors.teeing你真的需要了解一下

开发 后端
在 Java 12 里面有个非常好用但在官方 JEP 没有公布的功能,因为它只是 Collector 中的一个小改动,它的作用是 merge 两个 collector 的结果。

前言

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

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

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

Collectors.teeing

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

  1. /**  
  2.  * Returns a {@code Collector} that is a composite of two downstream collectors.  
  3.  * Every element passed to the resulting collector is processed by both downstream  
  4.  * collectors, then their results are merged using the specified merge function  
  5.  * into the final result.  
  6.  *  
  7.  * <p>The resulting collector functions do the following:  
  8.  *  
  9.  * <ul>  
  10.  * <li>supplier: creates a result container that contains result containers  
  11.  * obtained by calling each collector's supplier  
  12.  * <li>accumulator: calls each collector's accumulator with its result container  
  13.  * and the input element  
  14.  * <li>combiner: calls each collector's combiner with two result containers  
  15.  * <li>finisher: calls each collector's finisher with its result container,  
  16.  * then calls the supplied merger and returns its result.  
  17.  * </ul>  
  18.  *  
  19.  * <p>The resulting collector is {@link Collector.Characteristics#UNORDERED} if both downstream  
  20.  * collectors are unordered and {@link Collector.Characteristics#CONCURRENT} if both downstream  
  21.  * collectors are concurrent.  
  22.  *  
  23.  * @param <T>         the type of the input elements  
  24.  * @param <R1>        the result type of the first collector  
  25.  * @param <R2>        the result type of the second collector  
  26.  * @param <R>         the final result type  
  27.  * @param downstream1 the first downstream collector  
  28.  * @param downstream2 the second downstream collector  
  29.  * @param merger      the function which merges two results into the single one  
  30.  * @return a {@code Collector} which aggregates the results of two supplied collectors.  
  31.  * @since 12  
  32.  */  
  33. public static <T, R1, R2, R>  
  34. Collector<T, ?, R> teeing(Collector<? super T, ?, R1> downstream1,  
  35.                           Collector<? super T, ?, R2> downstream2,  
  36.                           BiFunction<? super R1, ? super R2, R> merger) {  
  37.     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 方法 

  1. @FunctionalInterface  
  2. public interface BiFunction<T, U, R> {  
  3.     /**  
  4.      * Applies this function to the given arguments.  
  5.      *  
  6.      * @param t the first function argument  
  7.      * @param u the second function argument  
  8.      * @return the function result  
  9.      */  
  10.     R apply(T t, U u);  

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

例子

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

计数和累加

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

  1. class CountSum {  
  2.     private final Long count;  
  3.     private final Integer sum;  
  4.     public CountSum(Long count, Integer sum) {  
  5.         this.count = count; 
  6.          this.sum = sum;  
  7.     }  
  8.     @Override  
  9.     public String toString() {  
  10.         return "CountSum{" +  
  11.                 "count=" + count +  
  12.                 ", sum=" + sum +  
  13.                 '}';  
  14.     }  

通过 Collectors.teeing 处理 

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

运行结果: 

  1. CountSum{count=7sum=46

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

最大值与最小值

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

  1. class MinMax {  
  2.     private final Integer min;  
  3.     private final Integer max;  
  4.     public MinMax(Integer min, Integer max) {  
  5.         this.min = min;  
  6.         this.max = max;  
  7.     }  
  8.     @Override  
  9.     public String toString() {  
  10.         return "MinMax{" +  
  11.                 "min=" + min +  
  12.                 ", max=" + max +  
  13.                 '}';  
  14.     }  

通过 teeing API 计算结果: 

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

运行结果:   

  1. MinMax{min=1max=12

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

  1. MinMax minmax = Stream.of(null, 2, 11, 1, 5, 7, 8, 12)  
  2.                 .collect(Collectors.teeing(  
  3.                         minBy(Comparator.nullsFirst(Comparator.naturalOrder())),  
  4.                         maxBy(Comparator.nullsLast(Comparator.naturalOrder())),  
  5.                         (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 方法,分别输出最小值与最大值

运行结果:   

  1. MinMax{min=-2147483648, max=2147483647

瓜的总重和单个重量

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

  1. // 定义瓜的类型和重量  
  2. class Melon {  
  3.     private final String type;  
  4.     private final int weight;  
  5.     public Melon(String type, int weight) {  
  6.         this.type = type;  
  7.         this.weight = weight;  
  8.     }  
  9.     public String getType() {  
  10.         return type;  
  11.     }  
  12.     public int getWeight() {  
  13.         return weight;  
  14.     }  
  15.  
  16. // 总重和单个重量列表  
  17. class WeightsAndTotal {  
  18.     private final int totalWeight;  
  19.     private final List<Integer> weights;  
  20.     public WeightsAndTotal(int totalWeight, List<Integer> weights) {  
  21.         this.totalWeight = totalWeight;  
  22.         this.weights = weights;  
  23.     }  
  24.     @Override  
  25.     public String toString() {  
  26.         return "WeightsAndTotal{" +  
  27.                 "totalWeight=" + totalWeight +  
  28.                 ", weights=" + weights +  
  29.                 '}';  
  30.     }  

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

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

运行结果:   

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

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

预约人员列表和预约人数 

  1. class Guest {  
  2.     private String name;  
  3.     private boolean participating;  
  4.     private Integer participantsNumber;  
  5.     public Guest(String name, boolean participating, Integer participantsNumber) {  
  6.         this.name = name;  
  7.         this.participating = participating;  
  8.         this.participantsNumber = participantsNumber;  
  9.     }  
  10.     public boolean isParticipating() {  
  11.         return participating;  
  12.     }  
  13.     public Integer getParticipantsNumber() {  
  14.         return participantsNumber;  
  15.     }  
  16.     public String getName() {  
  17.         return name;  
  18.     }  
  19.  
  20. class EventParticipation {  
  21.     private List<String> guestNameList;  
  22.     private Integer totalNumberOfParticipants;  
  23.     public EventParticipation(List<String> guestNameList, Integer totalNumberOfParticipants) {  
  24.         this.guestNameList = guestNameList;  
  25.         this.totalNumberOfParticipants = totalNumberOfParticipants; 
  26.     }  
  27.     @Override  
  28.     public String toString() {  
  29.         return "EventParticipation { " +  
  30.                 "guests = " + guestNameList +  
  31.                 ", total number of participants = " + totalNumberOfParticipants +  
  32.                 " }";  
  33.     }  

通过 teeing API 处理 

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

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

运行结果:   

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

总结

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

 

责任编辑:庞桂玉 来源: segmentfault
相关推荐

2015-12-23 10:00:04

多种编程语言

2021-07-06 14:56:20

深度学习编程人工智能

2021-05-19 09:40:14

Android 12Android

2024-01-31 13:02:00

高并发热点散列库存分桶

2017-09-22 14:12:33

Android字体 Typeface

2012-06-27 09:11:47

2022-08-25 14:42:45

JavaScrip字符串

2012-06-26 10:13:55

2011-04-01 11:16:06

hessian

2021-08-09 14:40:02

物联网IOT智能家居

2017-11-16 18:48:48

Hadoop数据库数据处理

2018-04-02 14:21:43

互联网物联网

2016-11-01 16:41:08

直通网线连接端口传输数据

2023-05-28 18:21:32

2022-03-24 13:36:18

Java悲观锁乐观锁

2017-12-18 15:33:56

Java基础编程

2022-10-27 10:29:15

2022-07-29 09:17:46

JavaScriptReactJS学习

2021-04-30 19:53:41

Java表达式代码

2022-01-04 19:28:05

VMware云端虚拟化
点赞
收藏

51CTO技术栈公众号