About Me
Facebook
Facebook
Linked In
Linked In
Twitter
Twitter
YouTube
YouTube
Google +
Google +

June 25, 2015

Java 8 Streams

The Stream API in Java 8 provides expressive power to Java language. Following are the characteristics of Streams.
         Streams do not have storage and they carry the values from a source through a pipeline of operations.
         Streams in Java 8 are designed for Lambdas and the operations have lambdas as arguments.
          It does not support indexed access and hence only the first element can be asked.
         An array or List can be built from Streams.
         The lazy feature in Streams enable to postpone the Stream operations until it is known how much data is needed.
         If a Stream is designated as parallel, then operations on it can be done concurrently without the need for an explicit multi-threading code.
         Streams can be unbounded. This essentially means a generator function can be designed and clients can consume entries as long as they want with the values getting generated on the fly.
Following are the three most common ways to make Stream:
         From individual values.  i.e., Stream.of(value1, value2, ...)
         From array. i.e., Stream.of(array)
         From List. i.e., list.stream()
Other ways of making Stream are:
         From a function.  i.e., Stream.generate, Stream.iterate
         From a StreamBuilder. i.e., builder.build()
         From String. i.e., String.chars, Stream.of(str.split(...))
         From another Stream. i.e., distinct, limit, sorted, filter, substream, map
Following is an example of generating IntStream.
Integer[] intNumbers = {6, 7, 8, 9, 10};
Stream.of(intNumbers) or Arrays.stream(intNumbers) produces a Stream<Integer>. This produces a 5-item Stream containing Integers.
Stream Methods
We can wrap a Stream around an array or List. We can then do operations on each element, remove elements that don't match certain criteria, make a new Stream by transforming each element etc.
We will illustrate the various stream methods by using the following code snippet (It is not a complete code and only relevant portions are provided).
Consider the following code snippet for illustration of various stream methods:
private static Contacts[] allContacts = {
      new Contacts("Ramesh", "Naudu", 1, "98554455552"),
      new Contacts("Rajesh", "Kumar", 2, "98554455558"),
      new Contacts("Michael", "D’Souza", 3, "98554455550"),
      new Contacts("Abas", "Flaherty", 4, "98554455551"),
      new Contacts("Irfan", "SM", 5, "98554455559"),
      new Contacts("Narayana", "Singh", 6, "98554455555"),
      new Contacts("Yuvi", "Sharma", 7, "98554455553"),
};
public static List<Contacts> getAllContacts() {
      return(Arrays.asList(allContacts));
}
public static List<Contacts> getSampleContacts() {
      return(Arrays.asList(sampleContacts));
}
private static Stream<Contacts> allContacts() {
      return(ContactsSamples.getAllContacts().stream());
}
private static Stream<Contacts> sampleContacts() {
      return(ContactsSamples.getSampleContacts().stream());
}
forEach
forEach provides a way to loop over Stream elements. A lambda is supplied to forEach which is called on each element of the Stream.
The code snippet allContacts().forEach(System.out::println);
would yield the result
Ramesh naudu [Contacts#1 98554455552]
Rajesh Kumar [Contacts#2 98554455558]
Michael D’Souza [Contacts#3 98554455550]
....
map
map produces a new Stream that is the result of applying a function to each element of original Stream.
The code snippet
Integer[] ids = { 1, 2, 5, 7 };
printStreamAsList(Stream.of(ids), "IDs");
printStreamAsList (Stream.of(ids).map(ContactsSamples::findContacts).map(Person::getFullName),"Names of Contacts with given IDs");
would yield the result
Names of Contacts with given IDs: 
[Ramesh Shetty, Raj Kumar, Irfan Ahmed, Yuvraj Sharma].
filter
filter produces a new Stream that contain only the elements of the original Stream that pass a given test.
The code snippet
Integer[] ids = { 7, 5, 2, 1 };
printStreamAsList (Stream.of(ids).map(ContactsSamples::findContacts).filter(c -> c != null).filter(c -> c.getPhoneNumber().equals("9845167894")), "Contact with mentioned phone number");
Would yield the result
Contact with mentioned phone number
[Irfan Ahmed[Contacts#5 9845167894]].
 findFirst
findFirst returns an Optional for the first entry in the Stream. Since Streams are results of filtering, there may not be a first entry, so Optional could be empty. findFirst is faster when paired with map or filter.
Consider the following code snippet:
Integer[] ids = { 7, 5, 2, 1 };
System.out.printf("Contact with phone number 9845167894: %s%n", Stream.of(ids).map(Contactsamples::findContacts).filter(c -> c != null).filter(c -> c.getPhoneNumber().equals("9845167894").findFirst().orElse(null));
In the above code snippet, following are the number of times that each of the mentioned items would be called.
- findContacts:  2
- Check for null: 2
- getPhoneNumber: 1
Lazy Evaluation
Streams defer doing most operations until the results are actually needed. This can result in operations that appear to traverse Stream multiple times actually traverse it only once. Because of "short-circuit" methods, operations that appear to traverse entire stream can stop much earlier.
Method Types
Intermediate Methods: Methods that produce other Streams. These don't get processed until some terminal method is called.
Terminal Methods: After one of these methods is invoked, the Stream is considered consumed and no more operations can be performed on it.
Short-circuit Methods: These methods cause intermediate methods to be processed only until the short-circuit method can be evaluated.
Consider the following code snippet to illustrate lazy evaluation and the order of operations.
Function<Integer,Contacts> findContacts = n -> { System.out.println("Finding Contact with ID " + n);
    return(ContactsSamples.findContacts(n));
};
Predicate<Contacts> checkForNull =
c -> { System.out.println("Checking for null");
       return(c != null);
     };
Predicate<Contacts> checkPhoneNumber =
c -> { System.out.println("Checking if phone number equals 9845167894");
       return(c.getPhoneNumber().equals("9845167894"));
     };
Integer[] ids = { 7, 5, 2, 1 };
System.out.printf("Contact with phone number 9845167894: %s%n",Stream.of(ids).map(findContacts).filter(checkForNull).filter(checkPhoneNumber).findFirst().orElse(null));
Results in
Finding Contact with ID 7
Checking for null
Finding Contact with ID 5
Checking for null
Checking if phone number equals 98554455559
Contact with phone number 98554455559: Irfan SM [Contacts#5 98554455559]
If you observe the result above, it builds a pipeline that, for each element in turn, calls findContacts, then checks that same element for null, then if non-null, checks the phone number of that same element, and if it exists, returns it.
If the Streams had behaved like Collections, then the following behavior would have been observed, which is not the case.
-          Would first call findContacts on all 4 ids, resulting in 4 Contacts
-          Would then call checkForNull on all 4 Contacts
-          Would then call checkPhoneNumber on all remaining Contacts
-          Would then get the first one (or null, if no Contacts

0 comments :

Post a Comment

Designed By AMEER BASHA G