Saturday 15 August 2015

java8 Streams

In simple terms, stream is a sequence of data items that are processed one at a time. Streams in Java are similar to streams in UNIX. You can pass output stream of one program to input stream of other.

Java8 implements streams in java.util.stream package. Stream<T> represents a sequence of items of type T. Streams API provides number of methods to achieve UNIX pipe kind of functionality in Java.

Streams are used to manipulate collection of data in declarative way. You just tell stream what you want, stream does that for you (Less worries for developer). In addition to this, streams can be processed parallel (You can achieve parallel behavior using multi threading, which is complex and possibility of errors) to leverage your multicore architectures.

Following application shows simple example of using streams.

Suppose I want to get all employee first names whose salary is > 40000 and sort them by first name. You no need to do lot of coding to solve above problem. Following streams code snippet do this for you.
List<String> firstNames = employees.stream().filter((Employee emp) -> emp.getSalary() > 40000).sorted(comparing(Employee::getFirstName)).map(Employee::getFirstName).collect(Collectors.toList());

public class Employee {
  private int id;
  private String firstName;
  private String lastName;
  private int age;
  private String city;
  private double salary;

  public Employee(int id, String firstName, String lastName, int age,
      String city, double salary) {
    super();
    this.id = id;
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
    this.city = city;
    this.salary = salary;
  }

  public int getId() {
    return id;
  }

  public void setId(int id) {
    this.id = id;
  }

  public String getFirstName() {
    return firstName;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }

  public int getAge() {
    return age;
  }

  public void setAge(int age) {
    this.age = age;
  }

  public String getCity() {
    return city;
  }

  public void setCity(String city) {
    this.city = city;
  }

  public double getSalary() {
    return salary;
  }

  public void setSalary(double salary) {
    this.salary = salary;
  }

  @Override
  public boolean equals(Object employee) {
    if (Objects.isNull(employee))
      return false;

    if (!(employee instanceof Employee))
      return false;

    Employee emp = (Employee) employee;

    return id == emp.id;
  }

  @Override
  public int hashCode() {
    return Objects.hash(id, firstName, lastName, age);
  }

  @Override
  public String toString() {
    return String.format("%s(%s,%d,%f)", firstName, city, age, salary);
  }

}


import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import static java.util.Comparator.comparing;

public class SortEmployee {
  public static void main(String args[]) {
    Employee emp1 = new Employee(1, "Hari Krishna", "Gurram", 26,
        "Bangalore", 40000);
    Employee emp2 = new Employee(2, "Joel", "Chelli", 27, "Hyderabad",
        35000);
    Employee emp3 = new Employee(3, "Shanmukh", "Kummary", 28, "Chennai",
        76000);
    Employee emp4 = new Employee(4, "Harika", "Raghuram", 27, "Chennai",
        75000);
    Employee emp5 = new Employee(5, "Sudheer", "Ganji", 27, "Bangalore",
        90000);
    Employee emp6 = new Employee(6, "Rama Krishna", "Gurram", 27,
        "Bangalore", 100000);
    Employee emp7 = new Employee(7, "PTR", "PTR", 27, "Hyderabad", 200000);
    Employee emp8 = new Employee(8, "Siva krishna", "Ponnam", 28,
        "Hyderabad", 55000);
    Employee emp9 = new Employee(9, "Gopi", "Battu", 28, "Hyderabad", 78000);
    Employee emp10 = new Employee(10, "Anil", "Tadavarthi", 28,
        "Hyderabad", 30000);

    List<Employee> employees = new ArrayList<>();

    employees.add(emp1);
    employees.add(emp2);
    employees.add(emp3);
    employees.add(emp4);
    employees.add(emp5);
    employees.add(emp6);
    employees.add(emp7);
    employees.add(emp8);
    employees.add(emp9);
    employees.add(emp10);

    List<String> firstNames = employees.stream()
        .filter((Employee emp) -> emp.getSalary() > 40000)
        .sorted(comparing(Employee::getFirstName))
        .map(Employee::getFirstName).collect(Collectors.toList());

    System.out.println(firstNames);

  }
}


Output

[Gopi, Harika, PTR, Rama Krishna, Shanmukh, Siva krishna, Sudheer]


Lets closely look at following statement.
List<String> firstNames = employees.stream().filter((Employee emp) -> emp.getSalary() > 40000).sorted(comparing(Employee::getFirstName)).map(Employee::getFirstName).collect(Collectors.toList());

a.   First I get a stream for list of employees.
b.   Filter list of employees on filed salary
c.    Sort employees on firstName
d.   Extract firstName field of employees
e.   Collect result as list.
Suppose you had 1GB of employee data, to speed up processing you want to use all processors of your system. Stream provide parallel processing functionality, instead of calling stream method on collection, call parallelStream method.

List<String> firstNames = employees.parallelStream().filter((Employee emp) -> emp.getSalary() > 40000).sorted(comparing(Employee::getFirstName)).map(Employee::getFirstName).collect(Collectors.toList());

What can I do using streams
a.   You can perform data processing effectively
Streams support number of operations like sort, map, collect, map, reduce etc., to process data. One good thing is you can process stream data in sequence (or) in parallel
b.   Pipelining
Almost all stream operations return stream as output, so you can pass output stream of one method as input stream to another method. Which is similar like pipelining.
c.    Internal iteration
Suppose you want to apply some operation on a collection, you no need to iterate collection and process each and every element. Stream operations do the iteration behind the scenes for you.
  
Note:
1.   If you generate stream from an ordered collection like ArrayList, stream preserves ordering. 
2. You can consume stream only once. If you tries to use stream, which is already consumed, java.lang.IllegalStateException thrown

import java.util.*;
import java.util.stream.Stream;

public class Test {

  public static void main(String args[]) {
    List<Integer> list = Arrays.asList(2, 3, 5, 7, 11);
    Stream<Integer> stream = list.stream();
    stream.forEach(System.out::println);

    System.out.println("Calling stream second time");
    stream.forEach(System.out::println);
  }
}


When you ran above program, you will get following output.

2
Exception in thread "main" 3
5
7
11
Calling stream second time
java.lang.IllegalStateException: stream has already been operated upon or closed
         at java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:274)
         at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
         at miscellaneous.Test.main(Test.java:14)



Prevoius                                                 Next                                                 Home

No comments:

Post a Comment