Working with terminal operations

A couple of terminal operations which require some attention in detail are as follows:

  • forEach(): One of the most important paradigms where functional programming differs from imperative programming is the way it is focused on what to implement rather than how to implement. This cannot be explained any better than the new foreach() method of Java 8. Previous implementation of iteration, however unique each may be, focused on how the iteration will take place and each time it was different for the list, HashMap, array, set, and so on. This way of iterating over elements was also called external iteration, which inherently not only processed data serially, but also followed the storage and retrieval order of the collection. This in certain cases, had a performance impact and hence the Stream API introduced the forEach() method to overcome some of these limitations. The iteration using the forEach() method in the stream API is internal, which means that we do not define how to iterate, but rather what to do while iterating. This also provides the opportunity to both serialize as well as parallelize the iteration and is taken care of by the API itself:
Supplier<Stream<String>>streamSupplier =()->Stream.of( new String[]{"Stream","from","an","array","of","objects"} ) ;

//Sequential For each
streamSupplier.get().sequential().forEach(P->System.out.println("Sequential output :: "+P));

We can also iterate over the same elements in parallel as follows:

Supplier<Stream<String>>streamSupplier =()->Stream.of( new String[]
{"Stream","from","an","array","of","objects"} ) ;

//Parallel For each
streamSupplier.get().parallel().forEach(P->System.out.println("Parallel
output :: "+P));

An important thing to notice however is that order of output has changed as parallel iteration does not guarantee the Collection order of operation.

  • Sum : Sum is one of the simplest reduction terminal operators. What reduction operators essentially do is to iterate an entire stream and perform an operation that tends to reduce the size of result, which may be as small as one element. The sum function does exactly that, it iterates over the set of elements in the stream and sums the elements over the iteration:
System.out.println("Number of alphabets present in the stream ::"+streamSupplier.get().mapToInt(x ->x.length()).sum());
  • Reduce: Reduce refers to an accumulation operation where each element of the stream is operated upon to produce a resultant element. Two basic tenets of a reduction operations is that it should have two operands:
    • A cumulative or derived collective value of the elements iterated so far which should be of the same type as the elements of the stream.
    • A subsequent non-iterated element of the stream.

A fold operation is applied on both these operands to return the operated value as the first operand for the next non-iterated element. This operation continues until no unprocessed element is left behind in the stream. The reduce() method is a more generalized reduction operation that results in a single value.

The signatures of the reduce() method in Java are:

Optional<T> reduce(BinaryOperator<T> reducer)

This is the simplest of the available reduce methods; the argument reducer is an associative function, an accumulator, to combine two elements. Let's take an example of adding up the numbers using reduce() instead of sum as we did before:

Stream<Strings> streamSupplier = .......
Optional<Integer> simpleSum= streamSupplier.map(x->x.length()).reduce((x,y)-> x+y);

Also note that if the input stream is empty then so is the result and hence the return type for such a signature is Optional. If we have just one element in the stream then the result is simply that value, in this case:

T r------educe(T identity, BinaryOperator<T> reducer)

This signature of the reduce method expects two arguments, identity, which acts as an initial value for the second argument, which is an associative function, an accumulator. The stream may be empty in this case, but the result is not empty and an identity value is taken as the default value. We can refactor the previous summing of the word length example as follows:

Stream<Strings> streamSupplier = .......
Integer defaulValSum= streamSupplier.map(x->x.length()).reduce(0,(x,y)-> x+y);

Now we have seen that both the reduce() methods perform the same action, so which one to prefer and when? There is no straightforward answer to this, but to say that it is use case dependent. Let s take an example of a cricket match and applying a reduce method() to count the score. Here having an identity value of say 0 would mean that even those batsman yet to bat have a zero score just like the batsmen who have already had their chance. Now that can be misleading and we would want the reduce method to have no default values in this case. So the choice of the reduce method is subjective to the use case being developed:

<U> U reduce(U identity, BiFunction<U,? super T,U> reducer, BinaryOperator<U> combiner)

The reduce() method also has a signature where it accepts three parameters. An identity element that also acts as the default value and hence the return type is the same as well. The second parameter reducer is an accumulator function that operates upon the next element of the stream and the previous accumulated partial result. The reducer accumulator is special in a sense that the input parameter can be of different types. The third parameter is a combiner that combines all partial results to form other partial results.

What makes this reduce() method special is that it is ideally designed to work in parallel or in use cases where an accumulator and combiner are required for performance reasons to say the least. In parallel execution, the stream is split into segments, which are processed in separate threads each having a copy of the identity elements and the accumulator function producing intermediate results. These intermediate results are then fed to the combiner to arrive at a result. Carrying forward the same summation example, but by using the three argument signature, the reducer function will look like this:

Stream<Strings> streamSupplier = .......
Integer valSum= streamSupplier.reduce(0,(x,y)-> x+y.length(),(acc1,acc2)->acc1+acc2);

Also it is important to note that if the reducer type is the same as that of the combiner then both reducer and combiner are essentially doing the same thing.

  • Collect: Collect is a mutable reduction operation, and unlike the reduce() method which reduces the data into a single value or element, the collect() method returns mutable objects such as list, set, array, HashMap, and so on.

The signature of the collect() method in Java is:

<R> R collect(Supplier<R> resultFactory, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner)

The collect() method accepts three parameters and similar to reduce() method, it also has an accumulator and combiner. Where it differs from the reduce() method is the resultFactory, which is an empty result container. The relationship among the parameters being passed to the collect() method therefore becomes that of creating result containers, accumulating partial results in containers, and finally combining the partial containers. String concatenation can be an example to show these functional relationships:

StringBuilder concat = streamSupplier.get().collect(() ->
new StringBuilder(),(sbuilder, str) ->sbuilder.append(str), (sbuilder1, sbuiler2) ->sbuilder1.append(sbuiler2));

Now here in the example, the first parameter creates a result container in the form of an empty StringBuilder object, the second parameter accumulates the next stream elements to the StringBuilder object and finally, the combiner combines the partial StringBuilder object into one:

<R> R collect(Collector<? super T,R> collector)

The collect() method also has a simplified method signature in which the role of accumulator, combiner, and result container generation can be encapsulated into a single abstraction called the Collector interface. The Collectors class is an implementation of the Collector interface which has abundant factory methods to perform various reduction operations.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.163.62