Sunday, 3 June 2018

Java 8 Stream Introduction

A stream represents a sequence of elements and supports different kind of operations to perform computations upon those elements:

Example: create stream from List by calling stream() method

              List<String> myList =
                     Arrays.asList("Hello", "World", "Java8", "Stream");

           myList
               .stream()
              .filter(s -> s.startsWith("Java"))
             .map(String::toUpperCase)
             .sorted()
             .forEach(System.out::println);

//output
// Java8



Stream operations are either intermediate or terminal.
Intermediate operations return a stream , can be chained for multiple intermediate operations. 
Terminal operations are either void or return a non-stream result. 
In the above example filter, map and sorted are intermediate operations whereas forEach is a terminal operation. .


Sequential Stream

 Sequential stream is created by calling stream() method

Arrays.asList("One", "Two", "Three")
    .stream()
    .findFirst()
    .ifPresent(System.out::println);  // One

Use Stream.of() to create a stream from  object references.
Stream.of("One", "Two", "Three")
    .findFirst()
    .ifPresent(System.out::println);  // One



Java 8  gives special kinds of streams for working with the primitive data types int, long and double.
 IntStream, LongStream and DoubleStream.

Example :
IntStream.range(1, 4)
    .forEach(System.out::println);

// 1
// 2
// 3
Primitive streams use specialized lambda expressions, e.g. IntFunction instead of Function or IntPredicate instead of Predicate.
Primitive streams also support the additional terminal aggregate operations sum() and average():

   Arrays.stream(new int[] {1, 2, 3})
    .map(n -> 2 * n + 1)
    .average()
    .ifPresent(System.out::println);  // 5.0

A regular object stream can be transfored to a primitive stream or vice versa. 
Object streams support the special mapping operations mapToInt(), mapToLong() and mapToDouble:

Stream.of("a1", "a2", "a3")
    .map(s -> s.substring(1))
    .mapToInt(Integer::parseInt)
    .max()
    .ifPresent(System.out::println);  // 3

Primitive streams can be transformed to object streams via mapToObj():

IntStream.range(1, 4)
    .mapToObj(i -> "a" + i)
    .forEach(System.out::println);

// a1
// a2
// a3


Intermediate and Terminal operations

Intermediate operations on stream are lazy , which means intermediate operations are executed only if the stream method chains ends with Action or terminal function


Stream.of("Hello", "World", "Java8", "Stream");
    .filter(s -> {
        System.out.println("filter: " + s);
        return true;
    });
//no output
No terminal operation present, means no execution.


Lets add forEach terminal operation and run again

Stream.of("Hello", "World", "Java8", "Stream");
    .filter(s -> {
         return true;
    }).forEach(System.out::println);

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

//ouput
Hello
World
Java8
Stream


Collectors

Collect is an extremely useful terminal operation to transform the elements of the stream into a different kind of result, e.g. a List, Set or Map
// example return result as List

List<String> listOfString =
 Stream.of("Hello", "World", "Java8", "Stream");
    .filter(s -> {
        System.out.println("filter: " + s);
        return true;
    })
.collect(Collectors.toList());


 // example join string

       List<String> list = Arrays.asList("java", "python", "nodejs", "ruby");

String result = list.stream().map(x -> x).collect(Collectors.joining(" | "));   

//java | python | nodejs | ruby



FlatMap

FlatMap transforms each element of the stream into a stream of other objects. So each object will be transformed into zero, one or multiple other objects backed by streams.

Lets see an example

class Student{
    String name;
    List<Section> sections = new ArrayList<>();

    Student(String name) {

        this.name = name;
    }
}

class Section{

    String name;

    Section(String name) {

        this.name = name;
    }
}

// create list of students List<Student> students = new ArrayList<>();

// create students
IntStream
    .range(1, 4)
    .forEach(i -> students .add(new Student("Student" + i)));

// create bars

students .forEach(s ->
    IntStream
        .range(1, 3)
        .forEach(i -> s.sections.add(new Section("Section" + i + " <- " + s.name))));



///

students.stream()
    .flatMap(s -> s.sections.stream())
    .forEach(sc -> System.out.println(sc.name));

// Student1 <- Section1
// Student2 <- Section1
// Student3 <- Section1
// Student1 <- Section2
// Student2 <- Section2
// Student3 <- Section2



Reduce

The reduction operation combines all elements of the stream into a single result.

persons
    .stream()
    .reduce((p1, p2) -> p1.age > p2.age ? p1 : p2)
    .ifPresent(System.out::println);  





Parallel Streams

Streams can be executed in parallel to increase runtime performance on large amount of input elements.
Parallel streams use a common ForkJoinPool available via the static ForkJoinPool.commonPool() method.
The size of the underlying thread-pool threads depending on the amount of available physical CPU cores

ForkJoinPool commonPool = ForkJoinPool.commonPool();
System.out.println(commonPool.getParallelism());  

Parallelism can be set using following jvm parameter


-Djava.util.concurrent.ForkJoinPool.common.parallelism=5



Collections support the method parallelStream() to create a parallel stream of elements. Alternatively you can call the intermediate method parallel() on a given stream to convert a sequential stream to a parallel counterpart.

In order to understate the parallel execution behavior of a parallel stream the next example prints information about the current thread to sout:

Arrays.asList("a1", "a2", "b1", "c2", "c1")
    .parallelStream()
    .filter(s -> {
        System.out.format("filter: %s [%s]\n",
            s, Thread.currentThread().getName());
        return true;
    })
    .map(s -> {
        System.out.format("map: %s [%s]\n",
            s, Thread.currentThread().getName());
        return s.toUpperCase();
    })
    .forEach(s -> System.out.format("forEach: %s [%s]\n",
        s, Thread.currentThread().getName()));

Here is the output

filter:  b1 [main]
filter:  a2 [ForkJoinPool.commonPool-worker-1]
map:     a2 [ForkJoinPool.commonPool-worker-1]
filter:  c2 [ForkJoinPool.commonPool-worker-3]
map:     c2 [ForkJoinPool.commonPool-worker-3]
filter:  c1 [ForkJoinPool.commonPool-worker-2]
map:     c1 [ForkJoinPool.commonPool-worker-2]
forEach: C2 [ForkJoinPool.commonPool-worker-3]
forEach: A2 [ForkJoinPool.commonPool-worker-1]
map:     b1 [main]
forEach: B1 [main]
filter:  a1 [ForkJoinPool.commonPool-worker-3]
map:     a1 [ForkJoinPool.commonPool-worker-3]
forEach: A1 [ForkJoinPool.commonPool-worker-3]
forEach: C1 [ForkJoinPool.commonPool-worker-2]



Parallel stream operations share the same JVM-wide common ForkJoinPool.
Think before using it , cz it may slow down other parts of your application which rely heavily on parallel streams.

Detail about stream api  can be found at https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html


No comments:

Post a Comment