Stream.reduce()
Before learning about the stream.reduce(), first we will learn two functional interfaces.
BinaryOperator<T>
Represents an operation upon two operands of the same type, producing a result of the same type as the operands.
This is a specialization of BiFunction for the case where the operands and the result are all of the same type.
The SAM is T apply(T t, T t);
BiFunction<T, U, R>
Represents a function that accepts two arguments and produces a result.
The SAM is R apply(T t, U u);
Now we will start the Stream.reduce()
- Reduction stream operations allow us to produce one single result from a sequence of elements.
- A reduction is a terminal operation that aggregates a stream into a type or a primitive. For example, a set of predefined reduction operations, such as average(), sum(), min(), max(), and count(), return one value by combining the elements of a stream.
- Stream.reduce() is a general-purpose method for generating our custom reduction operations.
- Before we look deeper into the Stream.reduce() operation, let's break down the operation's participant elements into separate blocks.
- Identity – an element that is the initial value of the reduction operation and the default result if the stream is empty.
- Accumulator – a function that takes two parameters: a partial result of the reduction operation and the next element of the stream.
- Combiner – a function used to combine the partial result of the reduction operation when the reduction is parallelized or when there's a mismatch between the types of the accumulator arguments and the types of the accumulator implementation.
- The Stream.reduce() method is overloaded and present inside the Stream interface.
- Optional<T> reduce(BinaryOperator<T> accumulator);
- This method performs a reduction on the elements of this stream, using an associative accumulation function. It returns an Optional describing the reduced value if any.
- T reduce(T identity, BinaryOperator<T> accumulator);
- This method takes two parameters: the identity and the accumulator. It returns a new partial result. The Stream.reduce() method returns the result of the reduction.
- <U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);
- This method takes three parameters: the identity, the accumulator, and the combiner.
- Example for subpoints 1 and 2 of point 5:
public class Test {
public static void main(String[] args) {
List<Book> books = new ArrayList<>();
books.add(new Book("9781976704031", 9.99));
books.add(new Book("9781976704032", 15.99));
Book book = books.stream().reduce(new Book("9781976704033", 0.0), (b1, b2) -> {
b1.price = b1.price + b2.price;
return new Book(b1.isbn, b1.price);
});
//System.out.println(book);
books.add(book);
books.parallelStream().reduce((x, y) -> x.price > y.price ? x : y).ifPresent(System.out::println);
}
}
class Book {
String isbn;
double price;
Book(String isbn, double price) {
this.isbn = isbn;
this.price = price;
}
public String toString() {
return "Book[" + isbn + ":" + price + "]";
}
}
- Example for subpoint 3 of point 5:
int length = Arrays.asList("str1", "str2").parallelStream()
.reduce(0, (accumulatedInt, str) -> accumulatedInt + str.length(),
(accumulatedInt1, accumulatedInt2) -> accumulatedInt1 + accumulatedInt2);
System.out.println(length);- In the above example if you are using a parallel stream then you don’t need to do the same thing in the accumulator and combiner. You can skip in the accumulator and do the same in the combiner. Below is an example of the same.
int length1 = Arrays.asList("str1", "str2").parallelStream()
.reduce(0, (accumulatedInt, str) -> str.length(),
(accumulatedInt1, accumulatedInt2) -> accumulatedInt1 + accumulatedInt2);
System.out.println(length1);- Why do we use the combiner function in the third version of the reduce method?
- To do parallel reduction to a different result type, we need two functions: one that accumulates T elements to intermediate U values and a second that combines the intermediate U values into a single U result.
Comments
Post a Comment