zoukankan      html  css  js  c++  java
  • 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 了

    灵魂追问

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

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

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

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

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


  • 相关阅读:
    UVA 11987 几乎就是并查集= =
    UVALive 5908 更新一下线段相交模板
    【poor几何】UVALive 5908 更新一下线段相交模板
    【poor几何】UVALive 5908 更新一下线段相交模板
    UVALive 3634 熟悉一下STL
    UVALive 3634 熟悉一下STL
    UVALive 3634 熟悉一下STL
    hdu2665 主席树模板题
    hdu2665 主席树模板题
    迷宫问题 POJ
  • 原文地址:https://www.cnblogs.com/FraserYu/p/11811242.html
Copyright © 2011-2022 走看看