Java12 Collectors.teeing you need to understand

Preface

There is a very useful function in Java 12, but it is not published in the official JEP, because it is only a small change in the collector, and its function is the result of merge two collectors. This sentence is very abstract. According to the old rule, let's look at the figure first:

This little thing is often used in pipeline transformation, usually we call it "three-way". Its main function is to merge the inflow of downstream1 and downstream2, and then flow out from the merger

With this image, let's get to the point

Collectors.teeing

The small function mentioned above is the Collectors.teeing API. First, let's look at the JDK's description of the API. If you feel uncomfortable, you can ignore it directly and continue to look down on the example:

/**
 * 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);
}

The important sentence of API description is:

Every element passed to the resulting collector is processed by both downstream collectors
In combination with the "three-way diagram", every element in the collection to be passed into the merger is processed by downstream1 and downstream2

Where the type of merger is BiFunction, that is, to receive two parameters and output a value, see its apply method

@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);
}

As for how to deal with it, let's take a look at some examples

Example

In order to better illustrate the use of teeing, I've listed four examples. After reading these four examples and looking back at the above API instructions, I'm sure you'll find a way out

Counting and accumulation

Let's first look at a classic problem. Given a set of numbers, you need to map the number of elements in an integer stream and their sum

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 +
                '}';
    }
}

Processed through 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 counts collections through the static method counting of Collectors
  • downstream2 accumulates collection element values through the static method summingInt of Collectors
  • merger collects results through the CountSum constructor

Operation result:

CountSum{count=7, sum=46}

We can get the results we want at one time through teeing, and continue to look down to other examples:

Maximum and minimum

Through the given set, the maximum and minimum values of the set are calculated at one time. Also, a new class MinMax is created, and a constructor is created to collect the results of the 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 +
                '}';
    }
}

The result is calculated by 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 uses the static method minBy of Collectors and the Comparator comparator to find the minimum value in natural order
  • downstream2 finds the maximum value by the static method maxBy of Collectors and by the Comparator comparator according to the natural order
  • The result is collected by merger through the MinMax constructor, but in response to NPE, two input parameters of BiFunction are processed by Optional

Operation result:

MinMax{min=1, max=12}

To verify Optional, we will add a null element to the collection and modify the sorting rules to see the sorting results:

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 processing rule is to put null at the top of the sorting
  • downstream2 processing rule is to put null at the end of sorting
  • When the merger is processed, the optional.orElse method will be executed to output the minimum value and the maximum value respectively

Operation result:

MinMax{min=-2147483648, max=2147483647}

Total and individual weight of melon

Let's take an example of a more practical operation object

// Define melon type and weight
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;
    }
}

// List of total and individual weights
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 +
                '}';
    }
}

Calculating total weight and single list weight through 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 does weight accumulation through the static method summingInt of Collectors
  • downstream2 extracts the weight of the melon through the static method mapping of Collectors, and obtains the result through the end operation toList() of the stream
  • merger gets the result through the WeightsAndTotal constructor

Operation result:

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

Let's continue with a more practical example:

Appointment list and number of people

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 +
                " }";
    }
}

Processing through 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 filters out the participants through the filtering method, maps their names, and finally puts them into the toList collection
  • downstream2 counts and accumulates by summingInt method
  • merger collects results through the EventParticipation constructor

We defined var result to collect the results, and did not specify the type. This syntax sugar also accelerated the efficiency of our programming

Operation result:

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

summary

In fact, the team API is to flexibly apply the static methods defined in Collectors, process the collection elements through downstream1 and downstream2, and finally collect them through the merger. When there are two collection results in the project, it's time to apply our team API

Soul questioning

  1. Are you familiar with the static methods in Collectors?
  2. What version of JDK are you using in the project?
  3. Is Lambda skilled?
  4. Is your light still on?

  1. Java Concurrent series, in continuous update
  2. Maven relies on a thorough understanding of transitivity
  3. Reading Excel also uses
  4. Why do interviewers always ask Java String for basic interview

Welcome to pay attention to my public number, "daily arch one soldier". I am interested in original analysis of Java technology stack, simplifying complex problems and landing abstract problems.
If you are interested in my topic content or want to see more content first, please visit my blog dayarch.top

Tags: Java JDK Programming Lambda

Posted on Wed, 06 Nov 2019 23:13:21 -0500 by nwoeddie23