Stream Basics - | Study Material, Lecturing Notes, Assignment, Reference, Wiki description explanation, brief detail |

Chapter: Java The Complete Reference - The Java Library - The Stream API

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.

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.


Study Material, Lecturing Notes, Assignment, Reference, Wiki description explanation, brief detail


Copyright © 2018-2021 BrainKart.com; All Rights Reserved. (BS) Developed by Therithal info, Chennai.