I try to understand how does the reduce()
method work in java-8 .
For example I have this code:
public class App {
public static void main(String[] args) {
String[] arr = {"lorem", "ipsum", "sit", "amet"};
List<String> strs = Arrays.asList(arr);
int ijk = strs.stream().reduce(0,
(a, b) -> {
System.out.println("Accumulator, a = " + a + ", b = " + b);
return a + b.length();
},
(a, b) -> {
System.out.println("Combiner");
return a * b;
});
System.out.println(ijk);
}
}
And the output is this:
Accumulator, a = 0, b = lorem
Accumulator, a = 5, b = ipsum
Accumulator, a = 10, b = sit
Accumulator, a = 13, b = amet
17
It is a sum of the length of these strings. And I see that the combiner is not accessed, so it will not multiply the numbers, it only adds the numbers.
But if I replace stream
with parallelStream
:
int ijk = strs.parallelStream().reduce(0,
(a, b) -> {
System.out.println("Accumulator, a = " + a + ", b = " + b);
return a + b.length();
},
(a, b) -> {
System.out.println("Combiner");
return a * b;
});
System.out.println(ijk);
This is the output:
Accumulator, a = 0, b = ipsum
Accumulator, a = 0, b = lorem
Accumulator, a = 0, b = sit
Combiner
Accumulator, a = 0, b = amet
Combiner
Combiner
300
I see that the Accumulator and Combiner are accessed both, but only the multiplication is return. So what's happen with the sum?
You should read the documentation of reduce
that says:
Additionally, the combiner function must be compatible with the accumulator function; for all u and t, the following must hold:
combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t)
In your case, you are breaking that law (doing a sum in accumulator
and multiplication in the combiner
), so the result you see for such an operation is really undefined and depends on how the Spliterator for the underlying source is implemented (don't do that!).
Besides, the combiner
is only called for a parallel stream.
Of course, your entire approach could be simplified to:
Arrays.asList("lorem", "ipsum", "sit", "amet")
.stream()
.mapToInt(String::length)
.sum();
If you are doing that just for learning purposes, a correct reduce
would be (to get the sum
):
strs.parallelStream()
.reduce(0,
(a, b) -> {
System.out.println("Accumulator, a = " + a + ", b = " + b);
return a + b.length();
},
(a, b) -> {
System.out.println("Combiner");
return a + b;
});
The Key Concepts: Identity, Accumulator, and Combiner
Stream.reduce() operation : let's break down the operation's participant elements into separate blocks. That way, we'll understand more easily the role that each one plays
When a stream executes in parallel, the Java runtime splits the stream into multiple substreams. In such cases, we need to use a function to combine the results of the substreams into a single one. This is the role of the combiner
Case 1 : Combiner works with parallelStream
as showed in your example
Case 2 : Example accumulator with different type of arguments
In this case, we have a stream of User objects, and the types of the accumulator arguments are Integer and User. However, the accumulator implementation is a sum of Integers, so the compiler just can't infer the type of the user parameter.
List<User> users = Arrays.asList(new User("John", 30), new User("Julie", 35));
int computedAges = users.stream().reduce(0, (partialAgeResult, user) -> partialAgeResult + user.getAge());
Compile Error
The method reduce(User, BinaryOperator<User>) in the type Stream<User> is not applicable for the arguments (int, (<no type> partialAgeResult, <no type> user) -> {})
We can fix this issue by using a combiner: which is method reference Integer::sum
or by using lambda expression (a,b)->a+b
int computedAges = users.stream().reduce(0, (partialAgeResult, user) -> partialAgeResult + user.getAge(),Integer::sum);
To put it simply, if we use sequential streams and the types of the accumulator arguments and the types of its implementation match, we don't need to use a combiner.
There are 3 ways to reduce using java-stream . In a nutshell, Stream::reduce
starts with two consequent items (or an identity value one with the first one) and performs an operation with them producing new reduced value. For each next item, the same happens and an operation is performed with the reduced value.
Let's say you have a stream of 'a'
, 'b'
, 'c'
and 'd'
. The reduction performs the following sequence of operations:
result = operationOn('a', 'b')
- the operationOn
might be anything (sum of the lengths of inputs..) result = operationOn(result, 'c')
result = operationOn(result, 'd')
result is returned
The methods are:
Optional<T> reduce(BinaryOperator<T> accumulator)
performs a reduction on the elements. Starts with the first two items producing a reduced value, then each item with the reduced value. The Optional<T>
is returned since it is not guaranteed the input stream is not empty.
T reduce(T identity, BinaryOperator<T> accumulator)
does the same as the method above, except the identity value is given as the first item. The T
is returned since there is always at least one item guaranteed, because of T identity
.
U reduce(U identity, BiFunction<U,? super T, U> accumulator, BinaryOperator<U> combiner)
does the same as the method above, with an addition that the functions are combined. The U
is returned since there is always at least one item guaranteed, because of U identity
.
I assume you chose to do addition and multiplication just as a demo to see what exactly happens.
As you already noticed, and as was already mentioned, the combiner is only called on parallel streams.
In short, on parallel strams, a part of the stream (resp. the underlying Spliterator) is cut off and processed by a different thread. After several parts are processed, their result is combined with a combiner.
In your case, the four elements all are processed by a different thread, and combination happens element-wise then. That's why you don't see any addition (apart from 0 +
) being applied, but only multiplication.
In order to get a meaningful result, however, you should switch from *
to +
and instead do a more meaningful output.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.