Java Streams were introduced in Java 8. They provide a declarative (or more precise: functional) API to work with collections. The java.util.stream
has the following description in the java docs.
Classes to support functional-style operations on streams of elements, such as map-reduce transformations on collections.
There is more about streams besides a functional way to handle collections. It is also about lazy evaluation, parallelism and streams not using collections (e.g. infinite streams, IntStream
, ...). But covering all these concepts exceed the scope of this article and therefore aren't described here (Maybe in an additional article?).
Comparison between functional and imperative paradigm
The following repl shows a comparison between a functional and declarative way of summing up som numbers. In my opinion the declarative way using streams isn't only more concise, but also more readable. Therefore, I prefer the functional style.
Creating streams
The interface Collection
(see documentation) contains the method default Stream<E> stream() {...};
, which returns a Stream
instance. Commonly used interfaces like List
, Stack
and Queue
implement the Collection
interface and thus you can create a Stream
by calling the stream()
method on instances of classes implementing these interfaces (e.g. HashSet
).
Bonus: infinite streams
There are also so called infinite streams, which are lazily evaluated (which means they are not created ahead of time; they have to, because otherwise they would require an infinite amount of memory). Make sure to limit the stream in order to prevent the program to potentially run endlessly.
Using streams / stream pipeline
There are two different kinds of operations you can do on a stream: intermediate and terminal operations. Chaining these operations creates a stream pipeline. Some operations are shown by examples below. You can read details about intermediate and terminal operations in the docs.
Intermediate operations
Intermediate operations return another Stream, so you can chain another operator to it. The following subsections show some examples. These are commonly used, but there are more (like flatMap
or peek
).
Stream<T> filter(Predicate<? super T> predicate)
(docs)
Returns a stream consisting of the elements of this stream that match the given predicate.
Example: Getting only books with more than 400 pages.
Stream<T> sorted(Comparator<? super T> comparator)
(docs)
Returns a stream consisting of the elements of this stream, sorted according to the provided Comparator.
What? Sort all books by their title.
Hint: there is also the Stream<T> sorted()
method, which can be used if T
implements the Comparable
interfaces compareTo(T o)
method.
<R> Stream<R> map(Function<? super T,? extends R> mapper)
(docs)
Returns a stream consisting of the results of applying the given function to the elements of this stream.
What? Getting only the title of all books.
Combining filter
, sorted
and map
What? Get only books with more than 400 pages, sort them by title and map them to only their title.
By reading the What? descriptions, you realize that this is all about declarative programming.
Terminal operations
Terminal operations terminate the stream pipeline. They either produce a side-effect (like printing) or a result (like reducing). After a terminal operation, the stream is consumed.
void forEach(Consumer<? super T> action)
(docs)
Performs an action for each element of this stream.
forEach
produces a side-effect (it returns void, so it has to produce a side-effect, otherwise it would just do nothing). forEach
is used in all previous examples to print the result.
T reduce(T identity, BinaryOperator<T> accumulator)
(docs)
Performs a reduction on the elements of this stream, using the provided identity value and an associative accumulation function, and returns the reduced value.
What? Get the sum of all pages of all the books.
Hint: There are also other overloads of the reduce
method.
<R,A> R collect(Collector<? super T,A,R> collector)
(docs)
Performs a mutable reduction operation on the elements of this stream using a Collector.
What? Get a Set
containing the titles of all books.
You can do a lot more with Collectors
. You could also group the result (imagine grouping books by author or genre).
Hint: There are also other overloads of the collect
method.
Feedback welcome
This is my very first blog post. I'd really appreciate your feedback. What did you (not) like? And why? Please let me know, so I can improve the content.
I also try to create valuable content on Twitter. Just have a look.
Top comments (2)
I just want to clarify what the term Stream means in FP. We already have stream-like behavior with lazy linked Lists. Lists differ from Streams in two properties though:
Cons
/Nil
as cases, whereas Stream doesn't need aNil
caseStreams are more expressive than Lists and you can apply fusion on it (avoiding intermediate structures) but it also loses some features like sharing, for instance (you can share a list).
Hi Iven,
thanks for the clarification, awesome!
Of course this article is more focused on the provided API than the underlying concept of streams. Therefore your comment is a nice addition.