Iterators
and Streams
Although a stream is not a
data storage object, you can still use an iterator to cycle through its
elements in much the same way as you would use an iterator to cycle through the
elements of a collection. The stream API supports two types of iterators. The
first is the traditional Iterator.
The second is Spliterator, which was
added by JDK 8. It provides significant advantages
in certain situations when used with parallel streams.
Use
an Iterator with a Stream
As just mentioned, you can
use an iterator with a stream in just the same way that you do with a
collection. Iterators are discussed in Chapter 18, but a brief review will be
useful here. Iterators are objects that implement the Iterator interface declared in java.util.
Its two key methods are hasNext( )
and next( ). If there is another
element to iterate, hasNext( )
returns true, and false otherwise. The next( ) method returns the next element
in the iteration.
To obtain an iterator to a
stream, call iterator( ) on the
stream. The version used by Stream is
shown here.
Iterator<T> iterator( )
Here, T specifies the element type. (The primitive streams return
iterators of the appropriate primitive type.)
The following program shows
how to iterate through the elements of a stream. In this case, the strings in
an ArrayList are iterated, but the
process is the same for any type of stream.
// Use an iterator with a stream.
import java.util.*;
import java.util.stream.*;
class StreamDemo8 {
public static void main(String[] args) {
//Create a list of Strings.
ArrayList<String> myList = new
ArrayList<>( ); myList.add("Alpha");
myList.add("Beta");
myList.add("Gamma");
myList.add("Delta");
myList.add("Phi");
myList.add("Omega");
//Obtain a Stream to the array list.
Stream<String> myStream =
myList.stream();
//Obtain an iterator to the stream. I
terator<String> itr = myStream.iterator();
//Iterate the elements in the stream.
while(itr.hasNext())
System.out.println(itr.next());
}
}
The output is shown here:
Alpha
Beta
Gamma
Delta
Phi
Omega
Use
Spliterator
Spliterator offers an alternative to
Iterator, especially when parallel processing is involved. In general, Spliterator is more sophisticated than Iterator, and a discussion of Spliterator
is found in Chapter 18. However, it will be useful to review its key features
here. Spliterator defines several
methods, but we only need to use three. The first is tryAdvance( ). It performs an action on the next element and then
advances the iterator. It is shown here:
boolean
tryAdvance(Consumer<? super T> action)
Here, action specifies the action that is executed on the next element in
the iteration. tryAdvance( ) returns true if there is a next element. It
returns false if no elements remain. As discussed earlier in this chapter, Consumer declares one method called accept( ) that receives an element of
type T as an argument and returns void.
Because tryAdvance( ) returns false
when there are no more elements to process, it makes the iteration loop
construct very simple, for example:
while(splitItr.tryAdvance( // perform action
here );
As long as tryAdvance( ) returns true, the action is applied to the next
element. When tryAdvance( ) returns false, the iteration is complete.
Notice how tryAdvance( ) consolidates the purposes of hasNext( ) and next( )
provided by Iterator into a single
method. This improves the efficiency of the iteration process.
The following version of the
preceding program substitutes a Spliterator
for the Iterator:
// Use a Spliterator.
import java.util.*;
import java.util.stream.*;
class StreamDemo9 {
public static void main(String[] args) {
//Create a list of Strings.
ArrayList<String> myList = new
ArrayList<>( ); myList.add("Alpha");
myList.add("Beta");
myList.add("Gamma");
myList.add("Delta");
myList.add("Phi");
myList.add("Omega");
//Obtain a Stream to the array list.
Stream<String> myStream = myList.stream();
//Obtain a Spliterator.
Spliterator<String> splitItr =
myStream.spliterator();
// Iterate the elements of the stream.
while(splitItr.tryAdvance((n) ->
System.out.println(n)));
}
}
The output is the same as
before.
In some cases, you might want
to perform some action on each element collectively, rather than one at a time.
To handle this type of situation, Spliterator
provides the forEachRemaining( ) method,
shown here:
default void
forEachRemaining(Consumer<? super T> action)
This method applies action to each unprocessed element and
then returns. For example, assuming the preceding program, the following
displays the strings remaining in the stream:
splitItr.forEachRemaining((n) ->
System.out.println(n));
Notice how this method eliminates
the need to provide a loop to cycle through the elements one at a time. This is
another advantage of Spliterator.
One other Spliterator method of particular
interest is trySplit( ). It splits
the elements being iterated in two, returning a new Spliterator to one of the partitions. The other partition remains
accessible by the original Spliterator.
It is shown here:
Spliterator<T>
trySplit( )
If it is not possible to
split the invoking Spliterator, null is returned. Otherwise, a
reference to the partition is returned. For example, here is another version of
the preceding program that demonstrates trySplit(
):
// Demonstrate trySplit().
import java.util.*;
import java.util.stream.*;
class StreamDemo10 {
public static void main(String[] args) {
//Create a list of Strings.
ArrayList<String> myList = new
ArrayList<>( ); myList.add("Alpha");
myList.add("Beta");
myList.add("Gamma");
myList.add("Delta");
myList.add("Phi");
myList.add("Omega");
//Obtain a Stream to the array list.
Stream<String> myStream =
myList.stream();
//Obtain a Spliterator.
Spliterator<String> splitItr =
myStream.spliterator();
//Now, split the first iterator.
Spliterator<String> splitItr2 =
splitItr.trySplit();
//If splitItr could be split, use splitItr2
first.
if(splitItr2 != null) {
System.out.println("Output from splitItr2:
");
splitItr2.forEachRemaining((n) ->
System.out.println(n));
}
//Now, use the splitItr.
System.out.println("\nOutput from
splitItr: "); splitItr.forEachRemaining((n) -> System.out.println(n));
}
}
The output is shown here:
Output from splitItr2:
Alpha
Beta
Gamma
Output from splitItr:
Delta
Phi
Omega
Although splitting the Spliterator in this simple illustration
is of no practical value, splitting can be of great value when parallel processing over large data sets. However,
in many cases, it is better to use one of the other Stream methods in conjunction with a parallel stream, rather than
manually handling these details with Spliterator.
Spliterator is primarily for the
cases in which none of the predefined methods seems appropriate.
More
to Explore in the Stream API
This chapter has discussed
several key aspects of the stream API and introduced the techniques required to
use them, but the stream API has much more to offer. To begin, here are a few
of the other methods provided by Stream
that you will find helpful:
To determine if one or more elements in a stream satisfy a
specified predicate, use allMatch( ), anyMatch( ), or noneMatch( ).
To obtain the number of elements in the stream, call count( ).
To obtain a stream that contains only unique elements, use distinct( ).
To create a stream that contains a specified set of elements, use of( ).
One last point: the stream
API is a powerful addition to Java. It is likely that it will be enhanced over
time to include even more functionality. Therefore, a periodic perusal of its
API documentation is advised.
Related Topics
Privacy Policy, Terms and Conditions, DMCA Policy and Compliant
Copyright © 2018-2023 BrainKart.com; All Rights Reserved. Developed by Therithal info, Chennai.