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"),
};
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));
}
return(Arrays.asList(allContacts));
}
public static List<Contacts>
getSampleContacts() {
return(Arrays.asList(sampleContacts));
}
return(Arrays.asList(sampleContacts));
}
private static Stream<Contacts>
allContacts() {
return(ContactsSamples.getAllContacts().stream());
}
return(ContactsSamples.getAllContacts().stream());
}
private static Stream<Contacts>
sampleContacts() {
return(ContactsSamples.getSampleContacts().stream());
}
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
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");
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");
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));
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
- 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));
return(ContactsSamples.findContacts(n));
};
Predicate<Contacts> checkForNull =
c -> { System.out.println("Checking for null");
return(c != null);
};
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"));
};
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));
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]
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