Stream
Basics
Let’s begin by defining the
term stream as it applies to the
stream API: a stream is a conduit for data. Thus, a stream represents a
sequence of objects. A stream operates on a data source, such as an array or a
collection. A stream, itself, never provides storage for the data. It simply
moves data, possibly filtering, sorting, or otherwise operating on that data in
the process. As a general rule, however, a stream operation by itself does not
modify the data source. For example, sorting a stream does not change the order
of the source. Rather, sorting a stream results in the creation of a new stream
that produces the sorted result.
Stream
Interfaces
The stream API defines
several stream interfaces, which are packaged in java.util.stream. At the foundation is BaseStream, which defines the basic functionality available in all
streams. BaseStream is a generic
interface declared like this:
interface BaseStream<T, S
extends BaseStream<T, S>>
Here, T specifies the type of the elements in the stream, and S specifies the type of stream that
extends BaseStream. BaseStream extends the AutoCloseable interface; thus, a stream
can be managed in a try-with-resources
statement. In general, however, only those streams whose data source requires
closing (such as those connected to a file) will need to be closed. In most
cases, such as those in which the data source is a collection, there is no need
to close the stream. The methods declared by BaseStream are shown in Table 29-1.
From BaseStream are derived several types of stream interfaces. The most
general of these is Stream. It is
declared as shown here:
interface Stream<T>
Here, T specifies the type of the elements in the stream. Because it is
generic, Stream is used for all
reference types. In addition to the methods that it inherits from BaseStream, the Stream interface adds several of its own, a sampling of which is
shown in Table 29-2.
In both tables, notice that
many of the methods are notated as being either terminal or intermediate.
The difference between the two is very important. A terminal operation consumes the
stream. It is used to produce a result, such as finding the minimum value in
the stream, or to execute some action, as is the case with the forEach( ) method. Once a stream has
been consumed, it cannot be reused. Intermediate
operations produce another stream. Thus, intermediate operations can be used to
create a pipeline that performs a
sequence of actions. One other point: intermediate operations do not take place
immediately. Instead, the specified action is performed when a terminal
operation is executed on the new stream created by an intermediate operation.
This mechanism is referred to as lazy
behavior, and the intermediate operations are referred to as lazy. The use of lazy behavior enables
the stream API to perform more efficiently.
Another key aspect of streams
is that some intermediate operations are stateless
and some are stateful. In a stateless
operation, each element is processed independently of the others. In a stateful
operation, the processing of an element may depend on aspects of the other
elements. For example, sorting is a stateful operation because an element’s
order depends on the values of the other elements. Thus, the sorted( ) method is stateful. However,
filtering elements based on a stateless predicate is stateless because each
element is handled individually. Thus, filter(
) can (and should be) stateless. The difference between stateless and
stateful operations is especially important when parallel processing of a
stream is desired because a stateful operation may require more than one pass
to complete.
Because Stream operates on object references, it can’t operate directly on
primitive types. To handle primitive type streams, the stream API defines the
following interfaces:
DoubleStream IntStream
LongStream
These streams all extend BaseStream and have capabilities
similar to Stream except that they
operate on primitive types rather than reference types. They also provide some
convenience methods, such as boxed( ),
that facilitate their use. Because streams of objects are the most common, Stream is the primary focus of this
chapter, but the primitive type streams can be used in much the same way.
How
to Obtain a Stream
You can obtain a stream in a
number of ways. Perhaps the most common is when a stream is obtained for a
collection. Beginning with JDK 8, the Collection
interface has been expanded to include two methods that obtain a stream from a
collection. The first is stream( ),
shown here:
default Stream<E>
stream( )
Its default implementation
returns a sequential stream. The second method is parallelStream( ), shown next:
default Stream<E>
parallelStream( )
Its default implementation
returns a parallel stream, if possible. (If a parallel stream can not be
obtained, a sequential stream may be returned instead.) Parallel streams
support parallel execution of stream operations. Because Collection is implemented by every collection, these methods can be
used to obtain a stream from any collection class, such as ArrayList or HashSet.
A stream can also be obtained
from an array by use of the static stream(
) method, which was added to the Arrays
class by JDK 8. One of its forms is shown here:
static <T>
Stream<T> stream(T[ ] array)
This method returns a
sequential stream to the elements in array.
For example, given an array called addresses
of type Address, the following
obtains a stream to it:
Stream<Address> addrStrm =
Arrays.stream(addresses);
Several overloads of the stream( ) method are also defined, such
as those that handle arrays of the primitive types. They return a stream of
type IntStream, DoubleStream, or LongStream.
Streams can be obtained in a
variety of other ways. For example, many stream operations return a new stream,
and a stream to an I/O source can be obtained by calling lines( ) on a BufferedReader.
However a stream is obtained, it can be used in the same way as any other stream.
A
Simple Stream Example
Before going any further,
let’s work through an example that uses streams. The following program creates
an ArrayList called myList that holds a collection of
integers (which are automatically boxed into the Integer reference type). Next, it obtains a stream that uses myList as a source. It then
demonstrates various stream operations.
// Demonstrate several stream operations.
import java.util.*;
import java.util.stream.*;
class StreamDemo {
public static void main(String[] args) {
// Create a list of Integer values.
ArrayList<Integer> myList = new
ArrayList<>( ); myList.add(7);
myList.add(18);
myList.add(10);
myList.add(24);
myList.add(17);
myList.add(5);
System.out.println("Original list: "
+ myList);
//Obtain a Stream to the array list.
Stream<Integer> myStream =
myList.stream();
//Obtain the minimum and maximum value by use
of min(),
max(), isPresent(), and get().
Optional<Integer> minVal =
myStream.min(Integer::compare); if(minVal.isPresent())
System.out.println("Minimum value: " +
minVal.get());
//Must obtain a new stream because previous
call to min()
//is a terminal operation that consumed the
stream.
myStream = myList.stream();
Optional<Integer> maxVal =
myStream.max(Integer::compare); if(maxVal.isPresent())
System.out.println("Maximum value: " +
maxVal.get());
// Sort the stream by use of sorted().
Stream<Integer> sortedStream =
myList.stream().sorted();
Display the sorted stream by use of forEach().
System.out.print("Sorted stream: ");
sortedStream.forEach((n) ->
System.out.print(n + " "));
System.out.println();
//Display only the odd values by use of
filter().
Stream<Integer> oddVals =
myList.stream().sorted().filter((n) -> (n %
2) == 1);
System.out.print("Odd values: ");
oddVals.forEach((n) -> System.out.print(n +
" "));
System.out.println();
//Display only the odd values that are greater
than 5. Notice that
//two filter operations are pipelined.
oddVals = myList.stream().filter( (n) -> (n
% 2) == 1)
.filter((n) -> n > 5);
System.out.print("Odd values greater than 5: "); oddVals.forEach((n)
-> System.out.print(n + " ") ); System.out.println();
}
}
The output is shown here:
Original list: [7, 18, 10, 24, 17, 5]
Minimum value: 5
Maximum value: 24
Sorted stream: 5 7 10 17 18 24
Odd values: 5 7 17
Odd values greater than 5: 7 17
Let’s look closely at each
stream operation. After creating an ArrayList,
the program obtains a stream for the list by calling stream( ), as shown here:
Stream<Integer> myStream =
myList.stream();
As explained, the Collection interface now defines the stream( ) method, which obtains a
stream from the invoking collection. Because Collection is implemented by every collection class, stream( ) can be used to obtain stream
for any type of collection, including the ArrayList
used here. In this case, a reference to the stream is assigned to myStream.
Next, the program obtains the
minimum value in the stream (which is, of course, also the minimum value in the
data source) and displays it, as shown here:
Optional<Integer> minVal =
myStream.min(Integer::compare); if(minVal.isPresent())
System.out.println("Minimum value: " +
minVal.get());
Recall from Table 29-2 that min( ) is declared like this:
Optional<T> min(Comparator<? super T> comp)
First, notice that the type
of min( )’s parameter is a Comparator. This comparator is used to
compare two elements in the stream. In the example, min( ) is passed a method reference to Integer’s compare( )
method, which is used to implement a Comparator
capable of comparing two Integers.
Next, notice that the return type of min(
) is Optional. The Optional class is described in Chapter
19, but briefly, here is how it works. Optional
is a generic class packaged in java.util
and declared like this:
class Optional<T>
Here, T specifies the element type. An Optional instance can either contain a value of type T or be empty. You can use isPresent( ) to determine if a value
is present. Assuming that a value is
available, it can be obtained by calling get(
). In this example, the object returned will hold the minimum value of the
stream as an Integer object.
One other point about the preceding
line: min( ) is a terminal operation
that consumes the stream. Thus, myStream
cannot be used again after min( )
executes.
The next lines obtain and
display the maximum value in the stream:
myStream = myList.stream();
Optional<Integer> maxVal = myStream.max(Integer::compare);
if(maxVal.isPresent()) System.out.println("Maximum value: " +
maxVal.get());
First, myStream is once again assigned the stream returned by myList.stream( ). As just explained,
this is necessary because the previous call to min( ) consumed the previous stream. Thus, a new one is needed.
Next, the max( ) method is called to
obtain the maximum value. Like min( ),
max( ) returns an Optional object. Its value is obtained
by calling get( ).
The program then obtains a
sorted stream through the use of this line:
Stream<Integer> sortedStream =
myList.stream().sorted();
Here, the sorted( ) method is called on the
stream returned by myList.stream( ).
Because sorted( ) is an intermediate
operation, its result is a new stream, and this is the stream assigned to sortedStream. The contents of the sorted stream are displayed by
use of forEach( ):
sortedStream.forEach((n) ->
System.out.print(n + " "));
Here, the forEach( ) method executes an operation
on each element in the stream. In this case, it simply calls System.out.print( ) for each element in
sortedStream. This is accomplished
by use of a lambda expression. The forEach(
) method has this general form:
void forEach(Consumer<?
super T> action)
Consumer is a generic functional interface declared in java.util.function. Its abstract method is accept( ),
shown here:
void accept(T objRef)
The lambda expression in the
call to forEach( ) provides the
implementation of accept( ). The forEach( ) method is a terminal
operation. Thus, after it completes, the stream has been consumed.
Next, a sorted stream is
filtered by filter( ) so that it
contains only odd values:
Stream<Integer> oddVals =
myList.stream().sorted().filter((n) -> (n % 2) == 1);
The filter( ) method filters a stream based on a predicate. It returns
a new stream that contains only those elements that satisfy the predicate. It
is shown here:
Stream<T>
filter(Predicate<? super T> pred)
Predicate is a generic functional interface defined in java.util.function. Its abstract method is test( ), which is
shown here:
boolean test(T objRef)
It returns true if the object referred to by objRef satisfies the predicate, and false otherwise. The lambda expression
passed to filter( ) implements this
method. Because filter( ) is an
intermediate operation, it returns a new stream that contains filtered values,
which, in this case, are the odd numbers. These elements are then displayed via
forEach( ) as before.
Because filter( ), or any other intermediate operation, returns a new
stream, it is possible to filter a filtered stream a second time. This is
demonstrated by the following line, which produces a stream that contains only
those odd values greater than 5:
oddVals = myList.stream().filter((n) -> (n %
2) == 1)
.filter((n) -> n > 5);
Notice that lambda
expressions are passed to both filters.
Related Topics
Privacy Policy, Terms and Conditions, DMCA Policy and Compliant
Copyright © 2018-2023 BrainKart.com; All Rights Reserved. Developed by Therithal info, Chennai.