Search This Blog

Wednesday 6 January 2016

Java Streams

We have been exploring Lambda expressions and Functional Interfaces in the past few posts. Now is the time to move to a related new  feature called Streams.
The definition of Stream from the Oracle documentation is :
A sequence of elements supporting sequential and parallel aggregate operations.
If I were to break this down into a picture
This kind of starts feeling like map reduce system operating inside JVM.
Now that we have seen a stream, how do we get one ?
In Map Reduce, any data stream comes from a data collection - a file. In JVM, the data is most commonly held in Java Collections. So the easiest way to get a stream is to start from a collection.
Accordingly I have this List of users which I would like to convert to a stream.
public class TestStreams {
   
   static class Person {
      String name;
      long id;
      int age;

      public Person(String name, long id, int age) {
         this.name = name;
         this.age = age;
         this.id = id;
      }
   }

   static final List<Person> persons = new ArrayList<Person>();

   static {
      persons.add(new Person("Robin", 12345, 27));
      persons.add(new Person("Reena", 109876, 22));
      persons.add(new Person("Meenakshi", 22245, 37));
      persons.add(new Person("Nitin", 1511, 31));
   }

}
Now to get a stream.
public static void display(List<Person> persons) {
      Stream<Person> stream = persons.stream();
      System.out.println("The Stream instance is " + stream);
      System.out.println("No Of Elements in stream is " + stream.count());
   }
The output of the code is as below
The Stream instance is java.util.stream.ReferencePipeline$Head@6d06d69c
No Of Elements in stream is 4
As seen here we called the stream method on the List to get a Stream instance.There is also a count method that returned the number of elements in the stream.
The stream method is defined in the Collection class and returns a sequential stream using the calling collection as the source:
Now that we have a stream, the next step would be to operate on it. I am going to first look at transformation operations available:
public static void booleanChecks(List<Person> persons) {
   boolean allAdults = persons.stream().allMatch(person -> person.age > 21);
   System.out.println("All Passed users are adults ? " + allAdults);

   boolean includesUsersWithNamesBeginningWithM = persons.stream().anyMatch(person -> person.name.startsWith("M"));
   System.out.println("Any users whose name begins with M ? " + includesUsersWithNamesBeginningWithM);

   boolean adminUser = persons.stream().noneMatch(person -> person.id == 1);
   System.out.println("Is Admin Present ? " + adminUser);
}
The output is
All Passed users are adults ? true
Any users whose name begins with M true
Is Admin Present ? true
The above three methods take a Predicate as an instance and decide if the stream elements meet the condition. For example anyMatch() method returns true even if one of the elements in the sequence returns a true result for the passed predicate instance.The allMatch() method took each element in the sequence and evaluated the predicate returning a true, only if all the elements in the sequence satisfy  the predicate. Conversely noneMatch() method will return true if the predicate fails for all elements in the stream.
public static void mapToOptional(List<Person> persons) {
   Optional<Person> anyPerson = persons.stream().findAny();
   System.out.println("findAny returned " + (anyPerson.isPresent() ? anyPerson.get().name : " null "));
   
   Optional<Person> firstPerson = persons.stream().findFirst();
   System.out.println("findFirst returned " + (firstPerson.isPresent() ? firstPerson.get().name : " null "));
}
In this method, both operations return an instance of Optional. This is basically a container that may or may not include any element. The findAny method returns an Optional with a Person if any are present in the Stream. The findFirst method returns the first element it finds in the stream, else an Optional with no element.
The output is as below:
findAny returned Robin
findFirst returned Robin
The next methods look at value elimination from the streams
public static void mapToStream(List<Person> persons) {
   Stream<Person> distinctPersons = persons.stream().distinct();
   System.out.println("Number of distinct users as per equals() method is " + distinctPersons.count());

   System.out.println("Users over 30 are ");
   Stream<Person> personsOver30 = persons.stream().filter(person -> person.age > 30);
   personsOver30.forEach(person -> System.out.println(person.name + " is of age " + person.age));

   System.out.println("Sorting the persons list");
   Stream<Person> sortedPersons = persons.stream().sorted(Comparator.comparing(Person::getName));
   sortedPersons.forEach(person -> System.out.println(person.getName()));
   System.out.println("The persons list unsorted ");
   persons.forEach(person -> System.out.println(person.getName()));
}
The output is as below:
Number of distinct users as per equals() method is 4
Users over 30 are 
Meenakshi is of age 37
Nitin is of age 31
Sorting the persons list 
Meenakshi
Nitin
Reena
Robin
The persons list unsorted 
Robin
Reena
Meenakshi
Nitin
As can be seen here:
  1. The distinct method returns a new stream that contains only unique objects from the original string - uniqueness evaluated using the equals implementation.
  2. The filter method returns a stream based on the passed Predicate instance. It retains those stream members that pass the Predicate test.
  3. The sorted method returns a new stream which is the sorted version of the stream. As can be seen from the output, the new stream is a different stream. Our original stream stays unchanged.
public static void mapToStream(List<Person> persons) {
  Optional<Person> ascPerson1 = persons.stream().min(Comparator.comparing(Person::getName));
  Optional<Person> descPerson1 = persons.stream().max(Comparator.comparing(Person::getName));
  System.out.println("first Person is " + (ascPerson1.isPresent() ? ascPerson1.get().name : " null ")
     + " & last person is " + (descPerson1.isPresent() ? descPerson1.get().name : " null "));
  
  System.out.println("2 Persons in list are : ");
  Stream<Person> twoSizePersons = persons.stream().limit(2);
  twoSizePersons.forEach(person -> System.out.println(person.getName()));
  
  System.out.println("Person names in caps :: ");
  Stream<Person> renamedPersonsStream = persons.stream().peek(
  person -> person.name = person.name.toUpperCase());
  renamedPersonsStream.forEach(person -> System.out.println(person.getName()));

  System.out.println("After skipping, Persons in list are :: ");
  Stream<Person> skippedPersons = persons.stream().skip(2);
  skippedPersons.forEach(person -> System.out.println(person.getName()));
}
The output is as below:
first Person is Meenakshi & last person is Robin
2 Persons in list are : 
Robin
Reena
Person names in caps :: 
ROBIN
REENA
MEENAKSHI
NITIN
After skipping, Persons in list are :: 
MEENAKSHI
NITIN
I looked at some other methods related to stream:
  1. The limit method of stream limits the size of the stream to the passed number. Here the first two elements were selected for the new Stream and the rest were not added to the Stream. This is quite like the LIMIT operator in SQL.
  2. The next is the peek method. This will return a new stream that contains all the elements in the original stream. Additionally the elements passed to the peek method are sent to a consumer. This will apply the operation before returning the new stream. 
  3. The last is the skip method. This is the opposite of the limit method.It will skip the initial n elements and return the rest as a stream.

No comments:

Post a Comment