Reduction
Operations
Consider the min( ) and max( ) methods in the preceding example program. Both are terminal
operations that return a result based on the elements in the stream. In the
language of the stream API, they represent reduction
operations because each reduces a stream to a single value—in this case,
the minimum and maximum. The stream API refers to these as special case reductions because they perform a specific function.
In addition to min( ) and max( ), other special case reductions
are also available, such as count( ),
which counts the number of elements in a stream. However, the stream API
generalizes this concept by providing the reduce(
) method. By using reduce( ),
you can return a value from a stream based on any arbitrary criteria. By definition, all reduction operations are
terminal operations.
Stream defines three versions of
reduce( ). The two we will use first are shown here: Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identityVal, BinaryOperator<T> accumulator)
The first form returns an
object of type Optional, which
contains the result. The second form returns an object of type T (which is the element type of the
stream). In both forms, accumulator is
a function that operates on two values and produces a result. In the second form, identityVal is a value such that an accumulator operation involving
identityVal and any element of the
stream yields that element, unchanged. For example, if the operation is addition,
then the identity value will be 0 because 0 + x is x. For multiplication, the
value will be 1, because 1 * x is x.
BinaryOperator is a functional interface declared in java.util.function that extends the BiFunction functional interface. BiFunction defines this abstract method:
R apply(T val, U val2)
Here, R specifies the result type, T
is the type of the first operand, and U
is the type of second operand. Thus, apply(
) applies a function to its two operands (val and val2) and returns
the result. When BinaryOperator
extends BiFunction, it specifies the
same type for all the type parameters. Thus, as it relates to BinaryOperator, apply( ) looks like this:
T apply(T val, T val2)
Furthermore, as it relates to
reduce( ), val will contain the previous result and val2 will contain the next element. In its first invocation, val will contain either the identity
value or the first element, depending on which version of reduce( ) is used.
It is important to understand
that the accumulator operation must satisfy three constraints. It must be
Stateless
Non-interfering
Associative
As explained earlier, stateless means that the operation does
not rely on any state information. Thus, each element is processed
independently. Non-interfering means
that the data source is not modified by the operation. Finally, the operation
must be associative. Here, the term associative is used in its normal,
arithmetic sense, which means that, given an associative operator used in a sequence of operations, it does not matter which
pair of operands are processed first. For example,
(10 * 2) * 7
yields the same result as 10
* (2 * 7)
Associativity is of
particular importance to the use of reduction operations on parallel streams,
discussed in the next section.
The following program
demonstrates the versions of reduce( )
just described:
// Demonstrate the reduce() method.
import java.util.*;
import java.util.stream.*;
class StreamDemo2 {
public static void main(String[] args) {
// Create a list of Integer values.
ArrayList<Integer> myList = new
ArrayList<>( );
myList.add(7);
myList.add(18);
myList.add(10);
myList.add(24);
myList.add(17);
myList.add(5);
//Two ways to obtain the integer product of the
elements
//in myList by use of reduce().
Optional<Integer> productObj =
myList.stream().reduce((a,b) -> a*b); if(productObj.isPresent())
System.out.println("Product as Optional:
" + productObj.get());
int product = myList.stream().reduce(1, (a,b)
-> a*b); System.out.println("Product as int: " + product);
}
}
As the output here shows,
both uses of reduce( ) produce the
same result:
Product as Optional: 2570400
Product as int: 2570400
In the program, the first
version of reduce( ) uses the lambda
expression to produce a product of two values. In this case, because the stream
contains Integer values, the Integer objects are automatically
unboxed for the multiplication and reboxed to return the result. The two values
represent the current value of the running result and the next element in the
stream. The final result is returned in an object of type Optional. The value is obtained by calling get( ) on the returned object.
In the second version, the
identity value is explicitly specified, which for multiplication is 1. Notice
that the result is returned as an object of the element type, which is Integer in this case.
Although simple reduction
operations such as multiplication are useful for examples, reductions are not
limited in this regard. For example, assuming the preceding program, the
following obtains the product of only the even values:
int evenProduct = myList.stream().reduce(1,
(a,b) -> { if(b%2 == 0) return a*b; else return a;
});
Pay special attention to the
lambda expression. If b is even,
then a * b is returned. Otherwise, a is
returned. This works because a holds
the current result and b holds the
next element, as explained earlier.
Related Topics
Privacy Policy, Terms and Conditions, DMCA Policy and Compliant
Copyright © 2018-2023 BrainKart.com; All Rights Reserved. Developed by Therithal info, Chennai.