Java Collectors API practices

summary

Collectors is a public final class that extends the Object class. The collectors class provides various useful reduction operations, such as accumulating elements into a collection, summarizing elements according to various standards, and so on. All methods in the PS: collectors class are static. So it's best to use static import. Generally, the IDE will do this for us without much concern.

prepare

We will use the following FunTester classes in this article.

    private static class FunTester {

        public int id;

        public String name;

        public int age;

        public String title;

        public double size;

        public FunTester(int id, String name, int age, String title, double size) {
            this.id = id;
            this.name = name;
            this.age = age;
            this.title = title;
            this.size = size;
        }
    Omitted here getter and setter as well as tostring method
    }

Then we create several FunTester objects.

        List<FunTester> funs = new ArrayList<>();
        List<FunTester> funs = new ArrayList<>();
        funs.add(new FunTester(1, "1 number", 10, "robot", 1.1));
        funs.add(new FunTester(2, "2 number", 20, "robot", 2.2));
        funs.add(new FunTester(3, "3 number", 30, "Back pot man", 3.3));
        funs.add(new FunTester(4, "4 number", 40, "BUG King", 4.4));
        funs.add(new FunTester(5, "5 number", 50, "green hand", 5.5));

Stream.collect() example

The most powerful Stream method in Java 8 is the collect() method. This is a very important part of the Stream API. It allows us to perform variable folding operations on the data elements saved in the Stream instance (repackaging the elements into some data structures and applying some additional logic, connecting them, etc.). The implementation of these logic is provided through the collector interface. The source code is < R, a > R collect (Collector <? Super T, a, R > collector);.

Collectors.toList() example

The toList() collector can be used to collect all Stream elements into one List instance.

The following shows how to collect the names of all FunTester objects into a list.

        List<String> names = funs.stream().map(f -> f.name).collect(Collectors.toList());
        output(names);

Console output:

INFO-> main No. 1: No. 1
INFO-> main No. 2: No. 2
INFO-> main No. 3: No. 3
INFO-> main No. 4: No. 4
INFO-> main No. 5: No. 5

Collectors.toSet() example

The toSet() collector is used to collect all Stream elements into a Set instance. The difference from toList() is that it will de duplicate.

Next, I'll show you how to collect all non duplicate title information.

        Set<String> titles = funs.stream().map(f -> f.title).collect(Collectors.toSet());
        output(titles);

Console output:

INFO-> main robot
INFO-> main Back pot man
INFO-> main green hand
INFO-> main BUG King

PS: the contents stored in set here are unordered.

Collectors.toCollection() example

Collection results cannot be set and processed when using the toSet() and toList() collectors. If you want to use a custom implementation or LinkedList or TreeSet, you need to use the toCollection collector.

For example, collect name into the LinkedList example instead of the default List implementation.

        LinkedList<String> names = funs.stream().map(f -> f.name).collect(Collectors.toCollection(LinkedList::new));

Collectors.toMap() example

The toMap()API converts the collected results into a map. The source code is slightly complex. There are two and three overloaded methods. I will show the most parameters:

    /**
     * @param <T> the type of the input elements
     * @param <K> the output type of the key mapping function
     * @param <U> the output type of the value mapping function
     * @param <M> the type of the resulting {@code Map}
     * @param keyMapper a mapping function to produce keys
     * @param valueMapper a mapping function to produce values
     * @param mergeFunction a merge function, used to resolve collisions between
     *                      values associated with the same key, as supplied
     *                      to {@link Map#merge(Object, Object, BiFunction)}
     * @param mapSupplier a function which returns a new, empty {@code Map} into
     *                    which the results will be inserted
     * @return a {@code Collector} which collects elements into a {@code Map}
     */
    public static <T, K, U, M extends Map<K, U>>
    Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
                                Function<? super T, ? extends U> valueMapper,
                                BinaryOperator<U> mergeFunction,
                                Supplier<M> mapSupplier) {
        BiConsumer<M, T> accumulator
                = (map, element) -> map.merge(keyMapper.apply(element),
                                              valueMapper.apply(element), mergeFunction);
        return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID);
    }

Four of the parameters are closures, which I mentioned earlier How do Java, Groovy, Python, and Golang treat methods as parameters There is no more grammar here. The first parameter is to set the key, the second parameter is to set the value, the third parameter is to set the value when the key is repeated, and the fourth parameter is to set the type of returned map.

Here is my example:

        HashMap<Integer, String> collect = funs.stream().collect(Collectors.toMap(new Function<FunTester, Integer>() {
            @Override
            public Integer apply(FunTester funTester) {
                return funTester.id;
            }
        }, new Function<FunTester, String>() {
            @Override
            public String apply(FunTester funTester) {
                return funTester.name;
            }
        }, (v1, v2) -> v2, HashMap<Integer, String>::new));

The simplified version is as follows:

        HashMap<Integer, String> collect = funs.stream().collect(Collectors.toMap(FunTester::getId, FunTester::getName, (v1, v2) -> v2, HashMap<Integer, String>::new));

Console output:

INFO-> main {1=1 number, 2=2 number, 3=3 number, 4=4 number, 5=5 number}

Collectors.summingInt() example

The summingInt() method sums all the elements extracted from the stream and returns an Integer value. The following is a demonstration of the age statistics results of all FunTester objects:

        IntSummaryStatistics statistics = funs.stream().map(FunTester::getAge).collect(Collectors.summarizingInt(f -> f));
        output(statistics.getSum());
        output(statistics.getAverage());
        output(statistics.getCount());
        output(statistics.getMax());
        output(statistics.getMin());

Console output:

INFO-> main 150
INFO-> main 30.0
INFO-> main 5
INFO-> main 50
INFO-> main 10

There are also two similar API s: java.util.stream.Collectors#summarizingLong and java.util.stream.Collectors#summarizingDouble.

In addition, java.util.stream.Collectors also provides three fast averaging methods:

        funs.stream().map(FunTester::getAge).collect(Collectors.averagingDouble(f -> f));
        funs.stream().map(FunTester::getAge).collect(Collectors.averagingInt(f -> f));
        funs.stream().map(FunTester::getAge).collect(Collectors.averagingLong(f -> f));

There is also a quantitative API:

        funs.stream().map(FunTester::getAge).collect(Collectors.counting());

Collectors.joining() example

The function of Collectors.joining() is to convert the data in stream into String type. This method also has three overloads. The following shows the most parameters.

    /**
     * Returns a {@code Collector} that concatenates the input elements,
     * separated by the specified delimiter, with the specified prefix and
     * suffix, in encounter order.
     *
     * @param delimiter the delimiter to be used between each element
     * @param  prefix the sequence of characters to be used at the beginning
     *                of the joined result
     * @param  suffix the sequence of characters to be used at the end
     *                of the joined result
     * @return A {@code Collector} which concatenates CharSequence elements,
     * separated by the specified delimiter, in encounter order
     */
    public static Collector<CharSequence, ?, String> joining(CharSequence delimiter,
                                                             CharSequence prefix,
                                                             CharSequence suffix) {
        return new CollectorImpl<>(
                () -> new StringJoiner(delimiter, prefix, suffix),
                StringJoiner::add, StringJoiner::merge,
                StringJoiner::toString, CH_NOID);
    }

The three parameters are prefix, suffix and separator.

The following demonstrates the effect:

        String join = funs.stream().map(FunTester::getTitle).collect(Collectors.joining(",","[","]"));

Console output:

INFO-> main [robot,robot,Back pot man,BUG King,green hand]

Collectors.groupingBy() example

Collectors.groupingBy() is an API for grouping stream data. The parameter also has a java.util.function.Function. Here is an example of grouping object name s by length:

        Map<Integer, List<String>> group = funs.stream().map(FunTester::getTitle).collect(Collectors.groupingBy(String::length));
        output(group);

Console output:

INFO-> main {2=[green hand], 3=[robot, robot, Back pot man], 4=[BUG King]}

PS: here, the * *: * * syntax in Java does not support passing, and can only be used once.

Collectors. Partitionby() example

Collectors. Partitionby() is grouped according to the judgment condition, which is the passed in parameter. Let me demonstrate:

        Map<Boolean, List<FunTester>> group = funs.stream().collect(Collectors.partitioningBy(f -> f.getId() > 2));
        output(group.get(false));
        output("----------------");
        output(group.get(true));

Console output:

INFO-> main 1st: FunTester{id=1, name='1 number', age=10, title='robot', size=1.1}
INFO-> main 2nd: FunTester{id=2, name='2 number', age=20, title='robot', size=2.2}
INFO-> main ----------------
INFO-> main 1st: FunTester{id=3, name='3 number', age=30, title='Back pot man', size=3.3}
INFO-> main 2nd: FunTester{id=4, name='4 number', age=40, title='BUG King', size=4.4}
INFO-> main 3rd: FunTester{id=5, name='5 number', age=50, title='green hand', size=5.5}

Posted on Fri, 03 Dec 2021 18:22:56 -0500 by nitation