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