Monday, June 27, 2016

operations on Java Streams -- continued

Filter Operation:

We can apply filter operation in an input stream  to produce another filtered stream.Suppose we have a finite stream of natural numbers but we want to filter the even numbers only,so we can apply filter operation here.Please note that ,unlike the map operation the elements in the filtered stream are  of the same type as the elements in the input stream.

The filter operation takes a functional interface Predicate as it's argument.Since the Predicate interface has a public method test which returns a boolean value, so we can pass a Lambda expression here as the argument to filter operation, which evaluates to a boolean value. 


 The size of the input stream is less than or equal to the size of the output stream.Please refer the below example.


Stream.of(1, 2, 3, 4, 5,6).filter(n->n%2==0).forEach(System.out::println);

Reduce Operation:

This combines all elements of a stream to generate a single result by applying a combining function repeatedly.Computing the sum, maximum, average, count etc.  are examples of the reduce operation.

The reduce operation takes two parameters  an initial value and an accumulator. The accumulator is the combining function. If the stream is empty, the initial value is the result. 


The initial value and an element are passed to the accumulator, which returns a partial result. This repeats until all elements in the stream are finished. The last value returned from the accumulator is the result of the reduce operation. 

 The Stream interface contains a reduce() method to perform the reduce operation. The method has three overloaded versions:  

1.Let's take the example for the first one

T reduce(T identity, BinaryOperator accumulator)


List<integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
.reduce(0, Integer::sum);
System.out.println(sum);

Here notice that 0 is the initial value and  Integer::sum is the accumulator ie. the combining function.

2.Let's take example for the second one

U reduce(U identity, BiFunction accumulator,BinaryOperator combiner) 

Note that the second argument, which is the accumulator, takes an argument whose type may be different from the type of the stream. This is used for the accumulating the partial results. The third argument is used for combining the partial results when the reduce operation is performed in parallel.Then the result of all the different threads will be combined.But if we are not doing it in parallel , the combiner  has no use. 


int result = List<Employee>
.stream()
.reduce(0, (intermediteSum, employee) ->intermediateSum + employee.getSalary(), Integer::sum);
System.out.println(sum);


The above code shows how to calculate the sum of salary of all  the   employees by using the reduce operation.Here 0 is the initial value  and sec
ond argument is the accumulator and Integer::sum is the combiner

3.Let's take the example for the third one

Optional reduce(BinaryOperator accumulator)

Sometimes we cannot specify an initial value for a reduce operation.Let's assume we get a list of numbers on the fly.We have no idea whether the list is empty or it has sum elements and we want to get maximum integer
value from a the List of numbers. If the underlaying stream is empty, we cannot initialize the maximum value. In such a case, the result is not defined.This version of the reduce method returns an Optional object that contains the result. If  the stream contains only one element, that element is the result. 

The following snippet of code computes the maximum of integers in a stream:


Optional<integer> maxValue = Stream.of(1, 2, 3, 4, 5)
.reduce(Integer::max);
if (maxValue.isPresent()) {
System.out.println("max = " + maxValue.get());
}
else {
System.out.println("max is not available.");
}

Collect Operation:

We saw in case of reduce operation we get a single value as the result.But sometimes we want to collect a set of values as the result of stream pipeline operations.

Let's take an example.We have a map of users having user name as key and user Account No as value.This map contains all the users those are active and non active.And we have another List of names which contains only active users.And our requirement is to get all the active  Account numbers.So my result here will be  itself a List of active Account numbers.


Set<string> activeUserList= new HashSet<>();
Map<String,String> completeUserMap=new HashMap<>();

List<String> keys =completeUserMap.entrySet().stream().
filter(e->activeUserList.contains(e.getKey())).
map(Map.Entry::getKey).collect(Collectors.toList());


Here notice that first we are filtering completeUserMap with activeUserList and then using the map operation to get user Account number from the Map entry and then collecting the result in a List.

Here let's collect the same result in a map,ie. we will collect the map of active users and their Account numbers.


Map<String,String> values = completeUserMap.entrySet().stream().
filter(e->activeUserList.contains(e.getKey())).
collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));


We will learn  about  Collectors,parallel stream and operation reordering in details in our next series.

No comments:

Post a Comment