JAVA 8 Streams with examples | Stream API JAVA 8 | Tutorials - Scratcharound

No comments
In last few tutorils we get to know about some new features of java 8 Java 8 Lambda Expressions Interface changes in Java 8 and Functional Interface in java 8 . Now we will look into one of the most exciting and helpful API of Java 8 release - Stream API.
First of all make it clear that this api has nothing to do with IO Streams in java just because of the similar names. Java 8 Stream Api is a part of java.util.stream package.

What is JAVA 8 Stream ?

According to the java doc Stream is -
A sequence of elements supporting sequential and parallel aggregate operations

Which means we can get a sequence of Objects from some source like Collection and perform different sequential operations on each object. Also with the use of streams we can easily perform operations in a parallel manner for a large Collection of objects which is even more efficient and time saving. For parallel processing we use parallelStream which is an alternative of stream. We will discuss parallelStream also later in this tutorial.

Now let's quicky take a look at sample use case of java 8 Stream -
Example: Suppose we have a list of Student objects and we want to print the firstName of Student objects for which firstName starts with "J". Now if we use the old approach , we will have to do the iteration twice -
- First to filter the objects with firstName starting with "J".
- Second to Print the filtered content from the newly created list.
The code with the above old approach would look something like this - Code without Stream -
package com.scratcharound.tutorials.java8.streams;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class HelloStream {
 public static void main(String [] args){
  List<Student> students = Arrays.asList(
    new Student("George","Thomas" ),
    new Student("Jonney","Stuart" ),
    new Student("Jackson","Ford" ),
    new Student("Jack","Pit" ),
    new Student("Ashley","Benson"));
  
  List jStudents = new ArrayList<>();
  
 // first iteration for filtering the list and creating a new list
  for(Student s : students){
   if(s.firstName.startsWith("J")){
    jStudents.add(s);
   }
  }
 // Second iteration for printing the newly created list 
  for(Student s : jStudents){
   System.out.println(s.firstName);
  }
 }
}
What if we could do this entire task with just 3 lines of code and without creating a new ArrayList object. Yes we can do this with the help of java 8 stream, see the code below -
Code example with Stream
package com.ashu.java8.streams;

import java.util.Arrays;
import java.util.List;

public class HelloStream {
 public static void main(String [] args){
  List students = Arrays.asList(
    new Student("George","Thomas" ),
    new Student("Jonney","Stuart" ),
    new Student("Jackson","Ford" ),
    new Student("Jack","Pit" ),
    new Student("Ashley","Benson"));
  
  students.stream()
  .filter(i-> i.firstName.startsWith("J"))
  .forEach(i-> System.out.println(i.firstName));
 }
}
Output :Jonney Jackson Jack Note : For Using java 8 Streams, you must be familier with java 8 Lambda Expressions.

In above example we have done the filtering and printing operations in only one iteration and that too without creating a new ArrayList.
Code Explaination: In above code stream() is used for getting the stream (a sequential stream) from the collection, like we obtain Iterator using .iterator() method. This has been provided for all the Collections in java 8.
filter(i-> i.firstName.startsWith("J")) , filters the stream and remove the elements for which the condition defined in the filter is false. This method takes a functional interface Predicate as parameter which has a abstract method test(T t) that return a boolean value. We have used lambda for this Predicate and implemented the test(T t) method to return the true or false based on the condition provided.
Finally we have forEach(i-> System.out.println(i.firstName)) method which prints the firstName of the remaining filtered objects from the stream.

Thus we can perform different Aggregate operations on streams like map, filter, forEach etc. Now as we have seen java 8 streams in action, we can list some features about java 8 streams.
  • Java 8 Stream is not a in-memory data structure like collections, but data is computed on demand in Streams.
  • Java Stream does not store data, but it performs the operations on data provided by some data source like collection in our case.
  • As we have seen in our above example Stream uses functional interface and Lambda interface which makes it suitable for functional programming and writing sort lines of code.
  • Java 8 Stream provides internal iterattion for Collections.
  • We can also use parallel processiong for larger set of data, with the help of parallel stream.

Stream operations types -

Basically streams has two types of operations -
1. Intermediate Operations
2. Terminal Operations
Intermediate Operations are operations which returns a stream after operation so that we can chain multiple intermediate operations without the termination.
As we can see in the example above filter() is an intermediate operation because it returns a filtered stream which we can use to perform further operations.
Terminal Operations are non-stream results, ie either they return a void or some other results which is not a stream. In our example above forEach() is a termination operation which returns void. We can use another termination operation count() on the intermediate stream which returns a long value, the count of stream after filter().
Long size = students.stream()
    .filter(i-> i.getfName().startsWith("J"))
    .count();
System.out.println(size);
As we have seen above, streams can be created using stream() and parallelStream() method, specially for collections. But we can create strema without using .stream() method by just using of() method by providing a bunch of object references as argument.
See the code given below -
Stream.of(new Student("George","Thomas" ),
    new Student("Jonney","Stuart" ),
    new Student("Jackson","Ford" ),
    new Student("Jack","Pit" ),
    new Student("Ashley","Benson")).filter(i-> i.firstName.startsWith("J"))
  .forEach(i-> System.out.println(i.firstName));

Reusing a stream ?

We cannot reuse a stream after calling a termination operation. See code below for example -
Stream stream = Stream.of(new Student("George","Thomas" ),
    new Student("Jonney","Stuart" ),
    new Student("Jackson","Ford" ),
    new Student("Jack","Pit" ),
    new Student("Ashley","Benson")).filter(i-> {
    System.out.println("Filter "+i.getfName());
    return i.firstName.startsWith("J");});
  stream.forEach(i-> System.out.println("for each "+i.firstName));
  
  streamSupplier.count();
  
exception -
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
 at java.util.stream.AbstractPipeline.evaluate(Unknown Source)
 at java.util.stream.ReferencePipeline.anyMatch(Unknown Source)
 at com.ashu.java8.streams.HelloStream.main(HelloStream.java:33)

above code will throw exception after executing count() termination operation.
To overcome this limitation we will have to create a new stream every time we want to call a termination operation. This task can be done with the help of java.util.function.Supplier interface of java 8.

see the code below -
Supplier<Stream<Student>> streamSupplier = ()-> Stream.of(new Student("George","Thomas" ),
    new Student("Jonney","Stuart" ),
    new Student("Jackson","Ford" ),
    new Student("Jack","Pit" ),
    new Student("Ashley","Benson")).filter(i-> {
    System.out.println("Filter "+i.getfName());
    return i.firstName.startsWith("J");});
  
  streamSupplier.get().forEach(i-> System.out.println("for each "+i.firstName));
  
  System.out.println(streamSupplier.get().count());
In above example each time we call get() , it will create a new Stream on which we can call a termination operation.

No comments :

Post a Comment