Streams API
Streams API
Streams API
parallel aggregate operations. The Stream API in Java 8 introduced the concept of streams and stream
classes in Java, which offer specialized functionality for manipulating and transforming data. Developers
can utilize the Stream API in Java to write code that is more concise, expressive, and efficient.
The Stream API in Java provides a rich set of operations that can be applied to streams, such as filtering,
mapping, and reducing. The Stream API in Java provides a high-level abstraction for working with
streams and offers a wide range of operations to manipulate and process data.
Here's an example that demonstrates the use of Streams in Java to filter and transform a collection of
names:
List<String> names = Arrays.asList("John", "Jane", "Adam", "Eve", "Mike");
.map(String::toUpperCase)
.collect(Collectors.toList());
In this example, we create a stream from the names list, filter the names starting with "J", convert them
to uppercase using the map operation, and collect the results into a new list using the collect terminal
operation. The output is the filtered and transformed names: [JOHN, JANE].
Stream provides various operations that can be chained together to produce results. Stream operations
can be classified into two types.
Intermediate Operations
Terminal Operations
1. Intermediate Operations
Intermediate operations return a stream as the output, and intermediate operations are not executed
until a terminal operation is invoked on the stream. This is called lazy evaluation, and it is discussed in
detail in the later section (Lazy Evaluation).
filter()
The filter() method returns a stream with the stream's elements that match the given predicate.
Predicate is a functional interface in Java that accepts a single input and can return a boolean value.
Example
.collect(Collectors.toList());
System.out.println(Arrays.toString(ans.toArray()));
Output
[2, 4]
Explanation This example filters the even values based on the predicate (value -> value % 2 == 0) passed
to it.
map()
The map() method returns a stream with the resultant elements after applying the given function on the
stream elements.
Example
.collect(Collectors.toList());
System.out.println(Arrays.toString(ans.toArray()));
Output
Explanation
In the example the map() method is called with the function value -> value * 10 on the stream. The
function is called for all values of the stream, and hence the result contains all stream values multiplied
by 10.
sorted()
The sorted() method returns a stream with the elements of the stream sorted according to natural order
or the provided Comparator.
Example
System.out.println("Ascending Order");
list.stream().sorted()
.forEach(System.out::println);
System.out.println("\nDescending Order");
list.stream().sorted(Comparator.reverseOrder())
.forEach(System.out::println);
Output
Ascending Order
5
Descending Order
Explanation
The sorted() method without any parameters sorts the elements in ascending order.
The sorted() method with the comparator Comparator.reverseOrder() sorts the element in the
descending order.
distinct()
This distinct() method returns a stream consisting of distinct elements of the stream (i.e.) it removes
duplicate elements.
Example
.distinct()
.collect(Collectors.toList());
Output
Explanation
peek()
The peek() method returns a stream consisting of the elements of the stream after performing the
provided action on each element. This is useful when we want to print values after each intermediate
operation.
Example
.collect(Collectors.toList());
System.out.println(Arrays.toString(ans.toArray()));
Output
Filtered 2
Filtered 4
[20, 40]
Explanation
We printed the filtered values using the peek() method after the intermediate filter() operation.
limit()
The limit() method returns a stream with the stream elements limited to the provided size.
Example
public class Main {
.limit(3)
.collect(Collectors.toList());
Output
Explanation
skip()
This skip() method returns a stream consisting of the stream after discarding the provided first n
elements.
Example
public class Main {
.skip(2)
.collect(Collectors.toList());
Output
Explanation The first two elements are skipped using the skip(2) method.
2. Terminal Operations
Terminal operations produce the results of the stream after all the intermediate operations are applied,
and we can no longer use the stream once the terminal operation is performed. forEach()
The forEach() method iterates and performs the specified action for each stream element. For parallel
stream, it doesn't guarantee to maintain the order of the stream.
Example
public class Main {
list.stream().forEach(System.out::println);
Output
Explanation
The forEach() method iterates and prints all the stream values.
forEachOrdered()
The forEachOrdered() method iterates and performs the specified action for each stream element. This
is similar to the forEach() method, and the only difference is that it maintains the order when the stream
is parallel.
public class Main {
Stream.of("A","B","C")
.parallel()
Stream.of("A","B","C")
.parallel()
Output
forEach: B
forEach: C
forEach: A
forEachOrdered: A
forEachOrdered: B
forEachOrdered: C
Explanation
From the output, we can see that the forEach() method doesn't maintain the order of the stream,
whereas the forEachOrdered() method maintains the order of the stream. collect() The collect() method
performs a mutable reduction operation on the elements of the stream using a Collector.
Mutable Reduction
A mutable reduction is an operation in which the reduced value is a mutable result container, like an
ArrayList.
Collector
A Collector is a class in Java that implements various reduction operations such as accumulating
elements into collections, summarizing elements, etc.
Example
.filter(x -> x % 2 == 0)
.collect(Collectors.toList());
System.out.println(evenList);
Output
[2, 4]
Explanation
The filter(x -> x % 2 == 0) method return a stream that contains even numbers. The stream is then
converted to a list via collect(Collectors.toList()).
count()
The count() method returns the total number of elements in the stream.
Example
Output
Count: 5
Explanation
The count() method returns the total number of elements in the stream, 5.
reduce()
The reduce() method performs a reduction on the elements of the stream and returns the value.
Example
Output
Sum: 15
Explanation
The reduce() method is called with two arguments, an initial value (0) and the accumulator method
(value, sum) -> sum += value).
Each stream element will be added to the previous result to produce the sum.
toArray()
The toArray() method returns an array that contains the elements of the stream.
Example
System.out.println(Arrays.toString(stream.toArray()));
Output
[1, 2, 3, 4, 5]
Explanation
The toArray() method returns an array with the elements of the stream.
min() and max()
The min() and max() methods return an Optional that contains the minimum and maximum elements of
the stream, respectively, according to the provided comparator.
Example
Output
Minimum: 1
Maximum: 5
Explanation
The min() and max() methods return optionals that contain the minimum and maximum elements of the
stream respectively. The comparator used to compare the elements is (a, b) -> Integer.compare(a, b).
This comparator returns 0 if a == b, a negative value if a < b and, a positive value if a > b.
findFirst()
The findFirst() method returns an Optional that contains the first element of the stream or an empty
Optional if the stream is empty.
Example
Output
First Value: 1
Explanation The findFirst() method returns an Optional with the first element of the stream, which is 1.
findAny()
The findAny() method returns an Optional containing some element of the stream or an empty Optional
if the stream is empty.
Example
Output
Any Value: 1
Explanation
The findAny() method can return any element in this stream. In this example, it returns 1. noneMatch()
When no stream elements match the specified predicate, the noneMatch() method returns true,
otherwise false. If the stream is empty, it returns true.
Example
Output
Is 2 present: false
Is 0 present: true
Explanation
The first noneMatch(value -> value == 2) returns false because there is a 2 in the stream.
The second noneMatch(value -> value == 0) returns true because there is no 0's in the stream.
allMatch()
When all the stream elements meet the specified predicate, the allMatch() method returns true,
otherwise false. If the stream is empty, it returns true.
Example
Output
Explanation
The allMatch(value -> value == 2) returns false because there are other elements in the stream apart
from 2.
anyMatch()
When any stream element matches the specified predicate, the anyMatch() method returns true,
otherwise false. If the stream is empty, it returns false.
Example
Output
Is 2 present: true
Explanation
The anyMatch(value -> value == 2) returns true because there is a 2 in the stream.
Java streams can be created in many ways, including creating an empty stream, creating a stream from
an existing array, creating a stream from specified values, etc.
Stream.empty()
Stream.empty() creates an empty stream without any values. This avoids null pointer exceptions when
calling methods with stream parameters. We create empty streams when we want to add objects to the
stream in the program.
Syntax
Example
Output
Size: 0
Explanation
The Stream.empty() method created an empty stream without any values in it. Hence calling count()
method on the stream returned 0.
Stream.builder()
Stream.builder() returns a builder (a design pattern that allows us to construct an object step-by-step)
that can be used to add objects to the stream. The objects are added to the builder using the add()
method. Calling build() method on the builder creates an instance of the Stream. This implementation of
Stream creation is based on the famous Builder Design Pattern.
Syntax
Example
builder.add("Tony Stark")
.add("Steve Rogers")
.add("Thor Odinson");
Output
Tony Stark
Steve Rogers
Thor Odinson
Explanation
The Stream.builder() method returned a Stream.Builder instance and it is used to add strings to the
builder instance. The Stream instance is created from the builder by calling the build() method.
Stream.of()
Stream.of() method creates a stream with the specified values. This method accepts both single and
multiple values. It does the work of declaring and initializing a stream.
Syntax
// Single value
// Multiple values
Output
Size of Stream1: 1
Size of Stream2: 3
Explanation
We created two streams stream1 and stream2 by passing single (1) and multiple values (1, 2, 3) to the
Stream.of() method.
Arrays.stream()
Arrays.stream() method creates a stream instance from an existing array. The resulting stream instance
will have all the elements of the array.
Syntax
Example
Output
Size: 5
Explanation
The stream instance is created from the values of the array passed to the Arrays.stream() method. The
created stream instance will have all the values of the array.
Stream.concat()
Stream.concat methods combine two existing streams to produce a new stream. The resultant stream
will have all the elements of the first stream followed by all the elements of the second stream.
Syntax
Example
stream3.forEach(System.out::println);
Output
2
3
Explanation
In this example, stream3 is created by combining stream1 and stream2. stream3 contains all the
elements of stream1 followed by all the elements of stream2.
Stream.generate()
Stream.generate() returns an infinite sequential unordered stream where the values are generated by
the provided Supplier.
Supplier A functional interface in Java represents an operation that takes no argument and returns a
result.
Stream.generate() is useful to create infinite values like random integers, UUIDs (Universally Unique
Identifiers), constants, etc. Since the resultant stream is infinite, it can be limited using the limit()
method to make it run infinite time.
Syntax
Stream<T> stream = Stream.generate(Supplier<T> supplier);
Example
.limit(5)
.collect(Collectors.toList());
uuids.forEach(System.out::println);
Output
7dc6148c-4fa4-431d-b2a7-2f7ee5c64193
07e25f0d-d739-46ef-8117-e4947c79d09c
d8a840b4-b2cd-44fc-8242-910a742fe6bd
3652935b-c626-442d-8139-03929f3eae4a
4b5e8446-db37-4b4e-a539-7c5b16878537
Explanation
In this example, we created an infinite stream of UUIDs using the Stream.generate() method.
Stream.iterate()
Stream.iterate() returns a infinite sequential ordered stream stream where the values are generated by
the provided UnaryOperator. Lets understand the important terms.
UnaryOperator A functional interface in Java that takes one argument and returns a result that is of the
same type as its argument.
Stream.iterate() methods accept two arguments, an initial value called the seed value and a unary
operator. The first element of the resulting stream will be the seed value. The following elements are
created by applying the unary operator on the previous element.
Syntax
Example
.collect(Collectors.toList());
values.forEach(System.out::println);
Output
16
Explanation
In this example, we created an infinite ordered stream using the Stream.iterate() method that generates
multiples of 2.
The first element of the stream is 1, which is the seed value passed. The following elements are created
by applying the unary operator n -> n * 2 on the previous element.
Since the stream is infinite, the results are limited using the limit() method.
These terminal operations return the minimum and maximum element of a stream based on a specified
comparator. For example:
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 6);
System.out.println(min.orElse(0)); // Output: 1
System.out.println(max.orElse(0)); // Output: 8
distinct
The distinct operation filters out duplicate elements from a stream, based on their natural order or the
provided comparator. For example:
These terminal operations check if certain conditions are true for all, any, or none of the elements in a
stream. They return a boolean value indicating the result. For example:
In the above example, allMatch checks if all numbers are even (false), anyMatch checks if any number is
even (true), and noneMatch checks if none of the numbers are negative (true).
Stream short-circuit operations can be better understood with Java's logical && and || operators.
expression1 && expression2 doesn't evaluate expression2 if expression1 is false because false &&
anything is always false.
expression1 || expression2 doesn't evaluate expression2 if expression1 is true because true || anything
is always true.
Stream short-circuit operations are those that can terminate without processing all the elements in a
stream. Short-circuit operations can be classified into two types.
Intermediate short-circuit operations produce a finite stream from an infinite stream. The intermediate
short-circuit operation in the Java stream is limit().
Example
stream.limit(3).forEach(System.out::println);
Output
Explanation
Terminal short-circuit operations are those that can produce the result before processing all the
elements of the stream. The terminal short-circuit operations in stream are findFirst(), findAny(),
allMatch(), anyMatch(), and noneMatch().
Example
Ouput
Checking 1
Checking 2
Checking 3
Is 3 present: true
Explanation
The terminal operation anyMatch(x -> x == 3) halts once 3 is found and doesn't process the remaining
stream elements.
Parallel Stream
By default, all stream operations are sequential in Java unless explicitly specified as parallel. Parallel
streams are created in Java in two ways.
parallel()
Example
stream.parallel().forEach(System.out::println);
Output
Explanation
The stream instance is converted to parallel stream by calling stream.parallel(). Since the forEach()
method is called on a parallel stream, the output order will not be same as the input order because the
elements are processed parallel.
parallelStream()
parallelStream() is called on Java collections like List, Set, etc to make it a parallel stream in Java.
Example
list.parallelStream().forEach(System.out::println);
Output
Explanation
The list object is converted to a parallel stream by calling the parallelStream() method.
Intermediate operations transform or filter elements in a stream, returning a new stream. Examples:
filter, map, distinct, sorted, limit.
Terminal operations produce a result or side effect, marking the end of a stream. Examples: forEach,
collect, reduce, count, min, max, anyMatch, allMatch, noneMatch.
Short-circuiting operations can terminate stream processing early based on a condition. Examples:
findFirst, findAny, limit.
Pipelines chain intermediate and terminal operations, processing data in a fluent and expressive
manner. Each operation takes input from the previous and produces output for the next. Pipelines allow
concise and declarative coding. Result is obtained from the terminal operation.
.filter(n -> n % 2 == 0)
.map(n -> n * 2)
.reduce(0, Integer::sum);
System.out.println(sum); // Output: 12
In this example, the stream pipeline starts with the stream() method on the numbers list. It then applies
the filter intermediate operation to keep only the even numbers, followed by the map operation to
double each number. Finally, the reduce terminal operation sums up all the elements in the stream. The
output is the sum of the doubled even numbers, which is 12. Use this Online Compiler to compile your
Java code.
Lazy Evaluation
Lazy evaluation (aka) call-by-need evaluation is an evaluation strategy that delays evaluating an
expression until its value is needed. All intermediate operations are performed on the stream only when
a terminal operation is invoked on it. Lazy evaluation is one of the critical characteristics of Java streams
that allows significant optimizations.
Example
Consider the below example where we created a stream that filters odd numbers.
.filter(x -> x % 2 == 1)
System.out.println("Result");
System.out.println(stream.collect(Collectors.toList()));
}
Output
Result
Filtered 1
Filtered 3
[1, 3]
Explanation
Each element of the above stream is passed to the filter() method where odd numbers are filtered and
then the peek() method where the filtered value is printed.
Filtered 1
Filtered 2
Result
[1, 3]
But we got a different order because the intermediate operations filter() and peek() are not executed
until the terminal operation collect() is invoked on the stream.