Stream API

Along with Lambda Expressions the Streams API is one of the important, it works very well with Lambda Expressions. Streams brings great power which is able to perform operations that search. filter, map or manipulate data. Streams have the ability to chain actions one after another, however to get the full benefit from streams you need a very good understanding of both Lambda Expressions and Generics, also basic knowledge of parallel execution and the Collections Framework would be handy as well.

I have another section on Functional Programming - Streams and Lambdas, which covers advanced topics and has many complex examples on how to use streams and lambdas

So what is a stream, in its basic terms it's a conduit for data, it represents a sequence of objects. It works on a data source such as an array or collection, it performas actions on the data passed possibly filreing, sorting, etc. One major point to make is that a stream returns a result as it does not change the original data source. As streams extends AutoCloseable you can use it with a try-with-resource statement. There are a number of methods that you can use from the BaseStream interface, the Stream interface then expands on BaseStream to define more methods.

In the table below I list the methods of both interfaces you will also notice a column called operation:

Some operations are stateless and others are stateful, in stateless 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, when parallel processing of a stream a stateful operation may require more than one pass to complete.

BaseStream Interface
Method Operation Description
close closes the invoking stream calling any registered close handlers
isParallel returns True if invoking stream is parallel
iterator Terminal obtains an iterator to the stream
onClose Intermediate returns a new stream with the close handler specified by handler
parallel Intermediate returns a parallel stream based on the invoking stream
sequential Intermediate returns sequential stream based on the invoking stream
spliterator Terminal obtains a spliterator to the stream
unordered Intermediate retruns an unordered stream based on the invoking stream, it already unordered then the stream is returned
Stream Interface
Method Operation Description
collect Terminal collects elements into a container which is changeable and returns the container
count Terminal counts and returns the elements in the stream
filter Intermediate produces a stream that contains those elements from the invoking stream that statisfy the predicate.
forEach Terminal for each element in the invoking stream the code specified by action is executed
map, mapToDouble, mapToInt, mapToLong Intermediate applies mapFunc to the elements from the invoking stream yielding a new stream (or DoubleStream, IntStream, LongStream) that contains those elements
max, min Terminal using the ordering specified by comp finds and returns the maximum/minimum element in the invoking stream
reduce Terminal returns a result based on the elements in the invoking stream
sorted Intermediate naturally sorted the elements in the invoking stream
toArray Terminal creates an array from the elements in the invoking stream

Let see a basic stream example

Streams example
// This is a reduction operation as each example reduces the stream into a single value

// 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 uses of min(), max(), isPresent(), and get().
// we also use a method reference Integer::compare
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();

Output
---------------------------
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 

Reduction Operations

You can use the reduce() method to return a value from a stream based on any arbitrary criteria. You can either return a type of Optiona or a type Of T (which is the element type of the stream).

Streams example
// 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); 
 
// Two ways to obtain the integer product of the elements in myList by use of reduce(). 
Optional<Integer> productObj = myList.stream().reduce((a,b) -> a*b); 
if(productObj.isPresent()) 
    System.out.println("Product as Optional: " + productObj.get()); 
 
int product = myList.stream().reduce(1, (a,b) -> a*b); 
System.out.println("Product as int: " + product); 

Output
---------------------------------
Product as Optional: 2570400
Product as int: 2570400

Using Parallel Streams

You can use the multi-core aspects of your server by processing streams in parallel, this increase performances but adds complexility. You can use the parallelStream() method or use the parallel() method.

// This is now a list of double values. 
ArrayList<Double> myList = new ArrayList<>( ); 
 
myList.add(7.0); 
myList.add(18.0); 
myList.add(10.0); 
myList.add(24.0); 
myList.add(17.0); 
myList.add(5.0); 
 
double productOfSqrRoots = myList.parallelStream()
                                 .reduce( 
                                     1.0, 
                                     (a,b) -> a * Math.sqrt(b),                                      
                                     (a,b) -> a * b 
                                ); 
 
System.out.println("Product of square roots: " + productOfSqrRoots);

Output
---------------------------------------------------------
Product of square roots: 1603.246705906486

Mapping

Mapping elements from one stream to another stream is a common practice, using the map() method you can map the transformed elements into a new stream.

Mapping example one
// A list of double values.
ArrayList<Double> myList = new ArrayList<>( );

myList.add(7.0);
myList.add(18.0);
myList.add(10.0);
myList.add(24.0);
myList.add(17.0);
myList.add(5.0);

// Map the square root of the elements in myList to a new stream.
Stream<Double> sqrtRootStrm = myList.stream().map((a) -> Math.sqrt(a));

// Find the product to the square roots.
double productOfSqrRoots = sqrtRootStrm.reduce(1.0, (a,b) -> a*b);

System.out.println("Product of square roots is " + productOfSqrRoots);

Output
-----------------------------------------------
Product of square roots is 1603.2467059064866
Mapping example two
import java.util.ArrayList;
import java.util.stream.Stream;

public class StreamTest {

    public static void main(String[] args) {

        // A list of names, phone numbers, and e-mail addresses.
        ArrayList<NamePhoneEmail> myList = new ArrayList<>( );

        myList.add(new NamePhoneEmail("Will", "999",
                "will@datadisk.co.uk"));
        myList.add(new NamePhoneEmail("Moore", "999",
                "moore@datadisk.co.uk"));
        myList.add(new NamePhoneEmail("Graham", "999",
                "graham@datadisk.co.uk"));

        System.out.println("Original values in myList: ");
        myList.stream().forEach( (a) -> {
            System.out.println(a.name + " " + a.phonenum + " " + a.email);
        });
        System.out.println();

        // Map just the names and phone numbers to a new stream.
        Stream<NamePhone> nameAndPhone = myList.stream().map(
                (a) -> new NamePhone(a.name,a.phonenum)
        );

        System.out.println("List of names and phone numbers: ");
        nameAndPhone.forEach( (a) -> {
            System.out.println(a.name + " " + a.phonenum);
        });

    }
}

class NamePhoneEmail {
    String name;
    String phonenum;
    String email;

    NamePhoneEmail(String n, String p, String e) {
        name = n;
        phonenum = p;
        email = e;
    }
}

class NamePhone {
    String name;
    String phonenum;

    NamePhone(String n, String p) {
        name = n;
        phonenum = p;
    }
}

Output
------------------------------------
Original values in myList: 
Will 999 will@datadisk.co.uk
Moore 999 moore@datadisk.co.uk
Graham 999 graham@datadisk.co.uk

List of names and phone numbers: 
Will 999
Moore 999
Graham 999

Collecting

You can obtain a collection from a stream using he collect() method.

Collecting example (NamePhoneEmail and NamePhone classes are above)
// A list of names, phone numbers, and e-mail addresses.
ArrayList<NamePhoneEmail> myList = new ArrayList<>( );

myList.add(new NamePhoneEmail("Will", "999",
        "will@datadisk.co.uk"));
myList.add(new NamePhoneEmail("Moore", "999",
        "moore@datadisk.co.uk"));
myList.add(new NamePhoneEmail("Graham", "999",
        "graham@datadisk.co.uk"));

// Map just the names and phone numbers to a new stream.
Stream<NamePhone> nameAndPhone = myList.stream().map(
        (a) -> new NamePhone(a.name,a.phonenum)
);

// Use collect to create a List of the names and phone numbers.
List<NamePhone> npList = nameAndPhone.collect(Collectors.toList());

System.out.println("Names and phone numbers in a List:");
for(NamePhone e : npList)
    System.out.println(e.name + ": " + e.phonenum);

// Obtain another mapping of the names and phone numbers.
nameAndPhone = myList.stream().map(
        (a) -> new NamePhone(a.name,a.phonenum)
);

// Now, create a Set by use of collect().
Set<NamePhone> npSet = nameAndPhone.collect(Collectors.toSet());

System.out.println("\nNames and phone numbers in a Set:");
for(NamePhone e : npSet)
    System.out.println(e.name + ": " + e.phonenum);
    
Outout
----------------------------------------------
Names and phone numbers in a List:
Will: 999
Moore: 999
Graham: 999

Names and phone numbers in a Set:
Will: 999
Moore: 999
Graham: 999

Iterators and Spliterators

You can obtain a Iterator or a Spliterator from a stream and use as they are intended to be used.