Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does combiner in Stream.collect method work in java 8?

I created simple demo :

public static void main(String[] args) {
        List<String> list2 = Arrays.asList("adf", "bcd", "abc", "hgr", "jyt", "edr", "biu");

String collect = list2.stream().collect(String::new, (res, elem) -> {
              res=res.concat(" ").concat(elem);
//            System.out.printf("res=%s, elem=%s\n", res.isEmpty(), elem);

        }, (res1, res2) -> {
            System.out.printf("res1=%s, res2=%s\n", res1, res2);            
        });
        System.out.println("collect=" + collect);
}

The problem is that BiConsumer combiner part of collect doesn't run at all.

It runs if I use parallelStream() but two arguments res1 and res2 are equal to supplier String::new.

How to make combiner work in collect method?

like image 931
Vova Programmer Avatar asked Apr 30 '15 05:04

Vova Programmer


People also ask

How do I combine two streams in Java?

concat() in Java. Stream. concat() method creates a concatenated stream in which the elements are all the elements of the first stream followed by all the elements of the second stream. The resulting stream is ordered if both of the input streams are ordered, and parallel if either of the input streams is parallel.

How does stream collect work Java?

Java Stream collect() is mostly used to collect the stream elements to a collection. It's a terminal operation. It takes care of synchronization when used with a parallel stream. The Collectors class provides a lot of Collector implementation to help us out.

How does stream collect work?

Stream. collect() is one of the Java 8's Stream API's terminal methods. It allows us to perform mutable fold operations (repackaging elements to some data structures and applying some additional logic, concatenating them, etc.) on data elements held in a Stream instance.

What is a combiner in Java?

A Combiner, also known as a semi-reducer, is an optional class that operates by accepting the inputs from the Map class and thereafter passing the output key-value pairs to the Reducer class. The main function of a Combiner is to summarize the map output records with the same key.


3 Answers

First off, there is no need for the combiner to be executed in a non-parallel stream since there is nothing to combine.

Secondly, your issue stems from using String::new and String.concat. The accumulator is supposed to modify the first argument by combining the second argument with it but since strings in Java are immutable your code will produce nothing.

          res=res.concat(" ").concat(elem);

will create a new string and then throw away it. You want to use a StringBuilder instead so you can keep the intermediate results:

public static void main(String[] args) {
    List<String> list2 = Arrays.asList("adf", "bcd", "abc", "hgr", "jyt", "edr", "biu");

    String collect = list2.stream().collect(StringBuilder::new, (res, elem) -> {
        res.append(" ").append(elem);
    }, (res1, res2) -> {
        res1.append(res2.toString());
        System.out.printf("res1=%s, res2=%s\n", res1, res2);
    }).toString();
    System.out.println("collect=" + collect);
}

This will also work correctly with a parallel stream

res1= hgr jyt, res2= jyt
res1= bcd abc, res2= abc
res1= adf bcd abc, res2= bcd abc
res1= edr biu, res2= biu
res1= hgr jyt edr biu, res2= edr biu
res1= adf bcd abc hgr jyt edr biu, res2= hgr jyt edr biu
collect= adf bcd abc hgr jyt edr biu

like image 67
Raniz Avatar answered Oct 06 '22 01:10

Raniz


I think that the combiner is only used in parallel Streams (to combine the partial outputs of the parallel computations), so make your Stream parallel.

String collect = list2.parallelStream().collect(...
like image 42
Eran Avatar answered Oct 05 '22 23:10

Eran


It is more interesting result with and without parallel for Raniz sample:

    String collect = list2.stream().collect(StringBuilder::new,
            (res, elem) -> {
                System.out.printf("ACCUMULATE res=%s, elem=%s\n", res, elem);
                res.append(" ").append(elem);
        },
            (res1, res2) -> {
                System.out.printf("COMBINE res1=%s, res2=%s\n", res1, res2);
                res1.append(res2.toString());
            }).toString();

Without parallel combine never have been called:

ACCUMULATE res=, elem=adf
ACCUMULATE res= adf, elem=bcd
ACCUMULATE res= adf bcd, elem=abc
ACCUMULATE res= adf bcd abc, elem=hgr
ACCUMULATE res= adf bcd abc hgr, elem=jyt
ACCUMULATE res= adf bcd abc hgr jyt, elem=edr
ACCUMULATE res= adf bcd abc hgr jyt edr, elem=biu
collect= adf bcd abc hgr jyt edr biu

And with parallel

ACCUMULATE res=, elem=jyt
ACCUMULATE res=, elem=hgr
COMBINE res1= hgr, res2= jyt
ACCUMULATE res=, elem=biu
ACCUMULATE res=, elem=edr
COMBINE res1= edr, res2= biu
ACCUMULATE res=, elem=bcd
COMBINE res1= hgr jyt, res2= edr biu
ACCUMULATE res=, elem=abc
ACCUMULATE res=, elem=adf
COMBINE res1= bcd, res2= abc
COMBINE res1= adf, res2= bcd abc
COMBINE res1= adf bcd abc, res2= hgr jyt edr biu
collect= adf bcd abc hgr jyt edr biu
like image 27
Grigory Kislin Avatar answered Oct 05 '22 23:10

Grigory Kislin