Lambdas and Streams

Rule 3:Streams


index:
A bit of background
Effective use.
TODO:
NEED TO BE WELL AWARE OF FUNCTIONAL INTERFACE FOR INDEPTH WORKING IMPLEMENTATION OFF STREAMS AND LAMBDAS ..KEEP ON TIP ..LIKE WHAT IT TAKES/Parameters and RETURN.

A bit of background:
What is It?
Stream is sequence of elemnts that provide the aggregate finction (like map,reduce,filter etc)

Concurrency is an important concept but it comes it learning curve.Java-8 goes a step ahead and support parallaism using streams ,hiding low level thread implementation.
To leaverages the harware capabilities java introduced Join fork frame work.Java 8 Streams API supports many parallel operations to process the data.

Streams work on concept of converting the collections(or array) to stream and applying aggregate function to process the elements. the aggregate function can be INTERMEDIATE operation resulting in streams and hence can be connected together to form a pipeline of operations. or can be a TERMINAL operation which return either void or a collection not a stream.

map- intermediate operation in stream which converts a stream to other stream by applying Function(part of functional Interface) .
filter- intermediate operation in stream which filters out the streams based on (lambda)condition in filter operation.
collect- Terminal operation which returns the Collection
e.g : collect(Collectors.toList())

How it insures the atomicity of operation:-
REMEMBER that the intermediate operations are lazy. The intermediate operations will not be invoked until the terminal operation is invoked. This is very important when we are processing larger data streams. The process only on demand principle drastically improves the performance. The laziness of the intermediate operations help to invoke these operation in one pass.

Range of Numbers:
Java-8 has introduced three method of stream to get range of numbers
IntStream.rangeClosed(1,10).forEach(a->aa); LongStream.rangeClosed(1,10).forEach(a->aa);
DoubleStream.rangeClosed(1,10).forEach(a->a*a);

Internal working:(ACTS)

Let’s extend the above example by the terminal operation forEach:

Stream.of(“d2”, “a2”, “b1”, “b3”, “c”)
.filter(s -> {
System.out.println(“filter: ” + s);
return true;
})
.forEach(s -> System.out.println(“forEach: ” + s));

Executing this code snippet results in the desired output on the console:

filter: d2
forEach: d2
filter: a2
forEach: a2
filter: b1
forEach: b1
filter: b3
forEach: b3
filter: c
forEach: c

Observe the order !!!!!
Generally u expect to execute the operations horizontally one after another on all elements of the stream. But instead each element moves along the chain vertically. The first string “d2” passes filter then forEach, only then the second string “a2” is processed.

anyMatch(predicate) : it is a SHORT-CIRCUTING TERMINAL OPERATION . in calse predicate is true no further processing on stream takes place
other Varient of above:
allmatch(predicate)-if all the elemst of stream return true then it returns true .it peculicar as stream works in vertical fashion.

Note: the order in which Intermediate operations are written can improve/degrade the performance.
Example:(Ref-Acts)

Why order matters

The next example consists of two intermediate operations map and filter and the terminal operation forEach. Let’s once again inspect how those operations are being executed:

Stream.of(“d2”, “a2”, “b1”, “b3”, “c”)
.map(s -> {
System.out.println(“map: ” + s);
return s.toUpperCase();
})
.filter(s -> {
System.out.println(“filter: ” + s);
return s.startsWith(“A”);
})
.forEach(s -> System.out.println(“forEach: ” + s));

// map: d2
// filter: D2
// map: a2
// filter: A2
// forEach: A2
// map: b1
// filter: B1
// map: b3
// filter: B3
// map: c
// filter: C

As you might have guessed both map and filter are called five times for every string in the underlying collection whereas forEach is only called once.

We can greatly reduce the actual number of executions if we change the order of the operations, moving filter to the beginning of the chain:

Stream.of(“d2”, “a2”, “b1”, “b3”, “c”)
.filter(s -> {
System.out.println(“filter: ” + s);
return s.startsWith(“a”);
})
.map(s -> {
System.out.println(“map: ” + s);
return s.toUpperCase();
})
.forEach(s -> System.out.println(“forEach: ” + s));

// filter: d2
// filter: a2
// map: a2
// forEach: A2
// filter: b1
// filter: b3
// filter: c

Now, map is only called once so the operation pipeline performs much faster for larger numbers of input elements.

sorting Intermediate Operation :
sorted((a,b)->return a.compareTo(b)):
this works horizontally on collention.

Effective use:
STREAM PIPELINE IS EVALUATED LAZILY.Invocation doesnot start untill the terminal operation executed.
Java-8 way of adding element to Map using function computeIfAbsent(key,function)where key is computed based on function
example:
Map> groups = new HashMap<>();
groups.computeIfAbsent(alphabetize(word),
(unused) -> new TreeSet<>()).add(word);
Note; we can iterate over file and add values from there to the map

This method looks up a key in the map: If the key is present, the
method simply returns the value associated with it. If not, the method computes a
value by applying the given function object to the key, associates this value with
the key, and returns the computed value.

There is no hard and fast rule where to use and where not to use, but we can judge based on heuristics:
a.Overusing streams makes programs hard to read and maintain.
b.In the absence of explicit types, careful naming of lambda parameters is essential to the readability of stream pipelines.
c.Using helper methods is even more important for readability in stream pipelines than in iterative code because pipelines lack explicit type information and named temporary variables.
d.refrain from using streams to process char values (or any other except the inbuilt support ones)as no inbuilt support in streams(Only for Int,Long,Double).
e.Refactor existing code to use streams and use them in new code only where it makes sense to do so.

The STREAM PIPELINE express REPEATED COMPUTATION using FUNCTION OBJECT (typically lambdas or method references), while ITERATIVE CODE EXPRESSES repeated computation using code blocks.
There are some things you can do from code blocks that you can’t do from function objects:
• From a code block, you can read or modify any local variable in scope; from a
lambda, you can only read final or effectively final variables [JLS 4.12.4], and
you can’t modify any local variables.
• From a code block, you can return from the enclosing method, break or
continue an enclosing loop, or throw any checked exception that this method
is declared to throw; from a lambda you can do none of these things.
If a computation is best expressed using these techniques, then it’s probably not a
good match for streams. Conversely, streams make it very easy to do some things:
• Uniformly transform sequences of elements.
• Filter sequences of elements
• Combine sequences of elements using a single operation (for example to add
them, concatenate them, or compute their minimum)
• Accumulate sequences of elements into a collection, perhaps grouping them by
some common attribute.
• Search a sequence of elements for an element satisfying some criterion.

For better readability , make the methid name plural if it returns many elements out of stream (prime nimer series-primeNumbers) keep singluar otherwise.
The bottom line is for readbility you can use combination of both the streams and the block of code.

Published by

Unknown's avatar

sevanand yadav

software engineer working as web developer having specialization in spring MVC with mysql,hibernate

Leave a comment