Java Stream API
Stream API is a major feature introduced in Java 8. It provides convenient operations on collections.
The most used class is Stream Interface. It is defined in java.util.stream package. Stream class is used to support functional-style operations on streams of elements.
Stream can only store a sequence of objects. For primitives, there are separate classes for that. like Stream, They inherit from BaseStream Interface but with primitive specific methods such as avarage, range and rangeClosed.
- IntStream
- LongStream
- DeoubleStream
Stream operations are divided into intermediate and terminal operations. Intermediate operations return a new stream. Terminal operation produce a result
Intermediate Operations
- filter()
- map()
- flatMap()
- distinct()
- sorted()
- peek()
- limit()
- skip()
Terminal Operations
- forEach()
- forEachOrdered()
- toArray()
- toList()
- reduce()
- collect()
- min()
- max()
- count()
- anyMatch()
- allMatch()
- noneMatch()
- findFirst()
- findAny()
Stream Creation
Collection
Collection interface provides stream()
method to create stream. You can also use parallelStream()
method to create a parallel stream.
1 | List<String> list = Arrays.asList("the", "quick", "brown", "fox"); |
Array
You can also create stream from array
1 | String[] strArray = new String[] {"the", "quick", "brown", "fox"}; |
Stream.of()
can be used to create stream too
1 | Stream<String> stream = Stream.of("the", "quick", "brown", "fox"); |
Empty Stream
use Stream.empty()
method to create empty stream
1 | Stream<String> emptyStream = Stream.empty(); |
Primitive Stream
Stream<T>
can not be used for primitives. Java 8 provides special class for primitive streams - IntStream, LongStream, DoubleStream
1 | IntStream intStream = IntStream.range(0, 2); // 0, 1 |
Stream.builder
use Stream.builder()
to build the stream
1 | Stream<String> stream = Stream.<String>builder().add("the").add("quick").add("brown").add("fox").build(); |
Stream.iterate
use Stream.iterate()
static method
1 | Stream.iterate(0, i -> i+1).limit(5).forEach(System.out::println); // 0, 1, 2, 3, 4 |
Stream.generate
use Stream.generate()
1 | Stream.generate(() -> new Random().nextInt(5)).limit(5).forEach(System.out::println); |
Create Stream from File
java.nio.file.Files
class provides lines()
method to generate Stream from a text file.
1 | Path path = Paths.get("/tmp/test.txt"); |
Merge Streams
use concat()
static method to merge two Streams.
1 | Stream<String> stream1 = Stream.of("Hello", "One"); |
If there are more than two Streams to merge, this is more complex. You can call multiple Stream.concat method or use the flatMap method
1 | Stream<String> stream1 = Stream.of("Hello", "One"); |
Here we first combine the 3 streams to create a Stream<Stream<String>>
variable, then use flatMap
method to flatten the variable and return Stream<String>
variable.
Create Parallel Stream
Stream class provides a parallel()
method to create a parallel stream. However, it doesn’t guarantee a performance increase. Be very careful when using parallel stream. for more info, see Think Twice Before Using Java 8 Parallel Streams by Lukas Krecan
Operations
filter
filter elements based on a criteria.
This is an intermediate operation.
1 | list.stream().filter( s -> s.startsWith("q")) |
map
1 | Stream<R> map(Function<? super T,? extends R> mapper) |
Apply function to each element in the stream.
map Post to its title.
1 | List<Post> posts = List.of(post1, post2, post3); |
There are variations if return type of the Function parameter is not an Object
- mapToDouble
- mapToInt
- mapToLong
flatMap
1 | Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper) |
flatMap does a map operation and then follow by an flat operation. flat
opertion avoids nested Stream<Stream<R>>
structure.
This method is usually used to merge collections. We can merge multiple list of Cities into one list in a single line.
1 | List<County> counties = new ArrayList<>(); |
There are variations if return type of the Function parameter is not an Object
- flatMapToDouble
- flatMapToInt
- flatMapToLong
Compare map()
and flatMap()
- Both map and flatMap can be applied to a
Stream<T>
and they both return aStream<R>
- map() operation takes a
Function
, which should apply to each value in the stream and produce a single value for each value. - flatMap() operation also takes a
Function
, which should apply to each value in the stream and produce an arbitrary number of values.
limit
limit the number of elements
1 | Stream.generate(() -> new Random().nextInt(5)).limit(5) |
distinct
Returns a stream consisting of the distinct elements (according to Object.equals(Object)) of this stream.
1 | list.stream().distinct().forEach(System.out::println); |
sorted
sorted()
method sort the elements in the stream
1 | new Random().ints(5).sorted().forEach(System.out::println); |
Sort String by length
1 | Stream<String> stringStream = Stream.of("aaaa", "bb", "fff", "o"); |
or
1 | Stream<String> stringStream = Stream.of("aaaa", "bb", "fff", "o"); |
Sort String by length, reverse order.
1 | Stream<String> stringStream = Stream.of("aaaa", "bb", "fff", "o"); |
peek
peek
performs action on each element. This is an intermediate operation.
1 | Stream<String> stringStream = Stream.of("a", "c", "b"); |
NOTE: This method exists mainly to support debugging, where you want to see the elements as they flow past a certain point in a pipeline
forEach
Performs an action for each element of this stream.
1 | Stream<String> stream = Stream.of("the", "quick", "brown", "fox"); |
forEach vs forEachOrdered
For forEach(), The behavior of this operation is explicitly nondeterministic. For parallel stream pipelines, this operation does not guarantee to respect the encounter order of the stream, as doing so would sacrifice the benefit of parallelism.
for forEachOrdered(), This operation processes the elements one at a time, in encounter order if one exists. So this operation sacrifice the benefit of parallelism for order.
forEach vs forEachOrdered
1 | List<String> list = Arrays.asList("the", "quick", "brown", "fox"); |
output
1 | brown |
anyMatch, allMatch and noneMatch
There are three methods you can use to find match: anyMatch()
, allMatch()
, nonMatch()
. These are all terminal operation.
anyMatch()
- Returns whether any elements of this stream match the provided predicate.allMatch()
- Returns whether all elements of this stream match the provided predicate.noneMatch()
- Returns whether no elements of this stream match the provided predicate.
1 | List<String> list = Arrays.asList("ggg", "tech"); |
findFirst and findAny
findFirst returns an Optional describing the first element of this stream, or an empty Optional if the stream is empty.
findAny also returns an element but its return element is nondeterministic.
1 | list.stream().findFirst().ifPresent(System.out::println); |
min and max
- min method returns the minimum value of the stream. It returns empty stream if these is no element. Similarly, max method returns the maximum value of the stream.
- The input parameteris a Comparator.
1 | List<Integer> list = Arrays.asList(1, 3, 2, 4); |
count
Returns the count of elements in this stream. This is a terminal operation.
1 | System.out.println(list.stream().count()); |
reduce
- reduce takes an identity(initial value) and accumulator to do the reduction.
- If the input Stream is empty, the return value will be the idenity(inital value).
Integer sum
1 | int sum = IntStream.range(0, 9).reduce(0, (a,b) -> a + b); |
or
1 | int sum = IntStream.range(0, 9).reduce(0, Integer::sum); |
BigDecimal sum
1 | List<BigDecimal> decimals = Arrays.asList(BigDecimal.ONE, BigDecimal.ZERO, BigDecimal.valueOf(2.2)); |
reduce without initial value
- If identity(inital value) is not provided, then reduce method will return an Optional.
- When the input stream is empty, reduce method will return Optional.empty().
1 | OptionalInt optionalSum = IntStream.range(0, 9).reduce( Integer::sum); |
When input Stream is empty, optionalSum will be Optional.empty() too.
1 | OptionalInt optionalSum = IntStream.empty().reduce(Integer::sum); |
toArray
Returns an array containing the elements of this stream. Note that the return type of toArray is Object[].
1 | Stream<String> stream = Stream.of("a", "c", "b"); |
If you want the return array have actual types, then provide a generator function
1 | Stream<String> stream = Stream.of("a", "c", "b"); |
toList
Accumulates the elements of this stream into a List. This method was introduced in Java 16. If this method is not available, use .collect(Collectors.toList()) instead.
1 | Stream<String> stream = Stream.of("a", "c", "b"); |
collect methods
performs mutable reduction operation on elements of a stream.
A mutable reduction operation accumulates input elements into a mutable result container, such as a Collection or StringBuilder, as it processes the elements in the stream.
.collect(Collectors.toList())
The most common operation is to return a collection using a list collector.
1 | List<String> upperCaseStringStream = stringStream |
Note that javadoc shows Collectors.toList() method doesn’t guarantee the return List type of the Collector. To explicitly set the return collection type, use toCollection method. e.g. collect(Collectors.toCollection(ArrayList::new))
.collect(Collectors.toSet())
1 | Set<Person> personSet = persons.stream() |
.collect(Collectors.toMap())
1 | List<Person> persons = new ArrayList<>(); |
Here, we use Function.identity()
as the value mapper. It returns the input value as the output value.
group elements
1 | Stream<Book> books = Stream.of(new Book("t1", "teen"),new Book("s1", "sci-fi"), new Book("t2", "teen")); |
String joining
1 | Stream<String> stringStream = Stream.of("one", "two", "three", "four"); |
Miscellaneous
Map-Reduce
Map-Reduce is a very powerful programming technique in solving complex problems. With the introduction of Stream API, We can now perform Map Reduce to solve complex problems more elegantly.
Example: find average salary of full time employees
1 | double average = employees.stream().filter(e -> e.isFulltime()).mapToDouble(e -> e.getSalary()).average().getAsDouble(); |
Note: average method is a reduce operation.
If you want to perform Map-Reduce in large scale, use parallelStream()
instead of stream()
method.
Filtering Null Values from a Stream
use filter method to filter out the null values in a stream
1 | List<String> list = Arrays.asList("the", "quick", null, "brown", null, "fox"); |
Alternatively, we can use Objects::nonNull
to do the filter
1 | List<String> list = Arrays.asList("the", "quick", null, "brown", null, "fox"); |
References
- java.util.stream package
- java.util.stream.Stream Interface
- java.util.stream.Collectors class
- Introduction to Java 8 Streams by Baeldung
- Java 8 Stream by Baeldung
- Tutorialspoint Streams
- What’s the difference between map() and flatMap() methods in Java 8?
- Java 8 – Filter a null value from a Stream by Mkyong