Java12 Collectors.teeing 你需要了解一下
前言
在 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 了
灵魂追问
- collectors 里面的静态方法你应用的熟练吗?
- 项目中你们在用 jdk 的版本是多少?
- lambda 的使用熟练吗?
- 你的灯还亮着吗?
欢迎持续关注公众号:「日拱一兵」
- 前沿 java 技术干货分享
- 高效工具汇总 | 回复「工具」
- 面试问题分析与解答
- 技术资料领取 | 回复「资料」
以读侦探小说思维轻松趣味学习 java 技术栈相关知识,本着将复杂问题简单化,抽象问题具体化和图形化原则逐步分解技术问题,技术持续更新,请持续关注......
上一篇: 随身有没有带孜然和辣椒面