Streams are Monads. In functional programming, a monad is a structure that represents computations defined as sequences of steps. Java Stream doesn’t store data, it operates on the source data structure (like collection and array) and produce pipelined data. On that pipelined (similar to assembly line/conveyor belt) data we can perform specific operations. Steam makes bulk processing on collections convenient and fast.
Stream operations are either intermediate or terminal. Intermediate operations return a stream so we can chain multiple intermediate operations without using semicolons. Such a chain of stream operations is also known as operation pipeline. An important characteristic of intermediate operations is laziness. Intermediate operations will only be executed when a terminal operation is present. Terminal operations are either void or return a non-stream result.
Java Stream operations use functional interfaces, that makes it a very good fit for functional programming using lambda expression. Most of those functional operations must be both non-interfering and stateless. A function is non-interfering when it does not modify the underlying data source of the stream. A function is stateless when the execution of the operation is deterministic i.e. no lambda expression depends on any mutable variables or states from the outer scope which might change during execution.
Java Streams are consumable, so there is no way to create a reference to stream for future usage. Since the data is on-demand, it’s not possible to reuse the same stream multiple times.
Processing Order
In stream, each element moves along the chain vertically i.e. to the operations are executed vertically one after another on all elements of the stream. Due to this processing order, operations like filters should be placed in beginning of the chain to reduce the number of executions.
Stream Creation
Java stream can be created from array and collections in many ways. Here are some examples:
- Stream.of() can be used in several ways. We can create stream by providing some elements with specific values.
Stream
stream = Stream.of(1,2,3,4); - An array of Objects can used to create steam.
Stream
stream = Stream.of(new Integer[]{1,2,3,4}); - Java 8 added a new stream() method to the Collection interface. So, steam can be created from any existing list or other collection.
List
myList = Arrays.asList("a1", "a2", "b1", "c2", "c1"); Stream stream = myList.stream();
Intermediate Operations
Here are some common stream intermediate operations:
- filter: filter() method takes a Predicate with some condition that is applied to filter the stream. It return a new stream that contains subset of the original stream.
List
myList = Arrays.asList("a1", "a2", "b1", "c2", "c1"); myList.stream() .filter(s -> s.startsWith("c")) .forEach(System.out::println); -
map: map() method takes a Function and apply it to all elements of the stream.
List
myList = Arrays.asList("a1", "a2", "b1", "c2", "c1"); myList.stream() .map(String::toUpperCase) .forEach(System.out::println); -
sorted: sorted() method returns a stream consisting of the elements of this stream, sorted according to natural order.
List
The overloaded version takes a Comparator as parameter.myList = Arrays.asList( "b1", "a1", "a2", "c2", "c1"); myList.stream() .sorted() .forEach(System.out::println); List
The Comparator can be provided as lambda expression:myList = Arrays.asList( "b1", "a1", "a2", "c2", "c1"); myList.stream() .sorted(Comparator.reverseOrder()) .forEach(System.out::println); List
myList = Arrays.asList( "b1", "a1", "a2", "c2", "c1"); myList.stream() .sorted((s1, s2) -> s2.compareTo(s1)) .forEach(System.out::println); -
flatMap: FlatMap transforms each element of the stream into a stream of other objects. So each object will be transformed into zero, one or multiple other objects backed by streams. It helps us to flatten the data structure to simplify further operations.
Stream< List< String>> namesOriginalList = Stream.of( Arrays.asList("Sajib"), Arrays.asList("Salman", "Anitam"), Arrays.asList("Sazzad")); namesOriginalList.flatMap(strList -> strList.stream()) .forEach(System.out::println);
In real scenario, usually several intermediate operations are chained.
ListmyList = Arrays.asList( "b1", "a1", "a2", "c2", "c1"); myList.stream() .filter(s -> s.startsWith("c")) .map(String::toUpperCase) .sorted() .forEach(System.out::println);
Terminal Operations
Computation on the source data is only performed when the terminal operation is initiated, and source elements are consumed only as needed. All intermediate operations are lazy, so they're not executed until a result of a processing is actually needed. Here are some common stream terminal operations:
-
count: We can use this terminal operation to count the number of items in the stream.
List
myList = Arrays.asList( "b1", "a1", "a2", "c2", "c1"); System.out.println(myList.stream().count()); -
forEach: This can be used for iterating over the stream.
List
myList = Arrays.asList("b1", "a1", "a2", "c2", "c1"); myList.stream() .forEach(System.out::println); -
collect: Collect is an extremely useful terminal operation to transform the elements of the stream into a different kind of result specially collection like List, Set or Map.
List
myList = Arrays.asList("b1", "a1", "a2", "c2", "c1"); List filtered =myList.stream() .filter(s -> s.startsWith("c")) .collect(Collectors.toList()); System.out.println(filtered); -
findFirst: This is a short circuiting terminal operation. It Returns an Optional describing the first element of this stream, or an empty Optional if the stream is empty.
List
myList = Arrays.asList("b1", "a1", "a2", "c2", "c1"); Optional filtered = myList.stream() .filter(s -> s.startsWith("c")) .findFirst(); if (filtered.isPresent()) { System.out.println(filtered.get()); }
References: