Java8 Lambda Expressions Streams
Java8 Lambda Expressions Streams
Streams
Mastering Java 8 Lambdas and Streams
Fu Cheng
This book is for sale at http://leanpub.com/java8-lambda-expressions-streams
This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing
process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools and
many iterations to get reader feedback, pivot until you have the right book and build traction once
you do.
1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
2. Lambda expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
2.1 Start a thread - A simple example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
2.2 Functional interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2.3 Target typing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.4 Lambda expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.5 Lexical scoping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.6 Effectively final local variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.7 Method references . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.7.1 Types of method references . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.8 Default interface methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.8.1 Static interface methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3. Functional interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.1 Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.1.1 Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.1.2 BiFunction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3.1.3 Function and BiFunction with predefined types . . . . . . . . . . . . . . 13
3.2 Consumers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3.2.1 Consumer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3.2.2 BiConsumer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
3.2.3 Consumer with predefined types . . . . . . . . . . . . . . . . . . . . . . . . . 16
3.2.4 Example of using Consumer . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
3.3 Suppliers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.3.1 Supplier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.3.2 Supplier with predefined types . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.4 Predicates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
3.4.1 Predicate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
3.4.2 BiPredicate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
3.4.3 Predicate and BiPredicate with predefined types . . . . . . . . . . . . . 20
3.5 Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
3.5.1 UnaryOperator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
3.5.2 BinaryOperator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
CONTENTS
4. Streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
4.1 User-specified behavior . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
4.2 Basic stream concepts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
4.2.1 Sequential or parallel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
4.2.2 Encounter order . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
4.3 Stream operation characteristics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
4.3.1 Stateful or stateless . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
4.4 Stream . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
4.5 Spliterator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
4.5.1 Spliterator characteristics . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
4.5.2 Spliterators for primitive types . . . . . . . . . . . . . . . . . . . . . . . . 26
4.5.3 Spliterators to create Spliterators . . . . . . . . . . . . . . . . . . . . . 26
4.5.4 Late-binding and fail-fast . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
4.6 BaseStream . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
4.7 StreamSource . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
4.8 Stream static methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
4.9 Stream sources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
4.9.1 Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
4.9.2 Collections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
4.9.3 Stream builder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
4.9.4 I/O channels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
4.9.4.1 lines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
4.9.4.2 list . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
4.9.4.3 walk . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
4.9.4.4 find . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
4.10 Examples for stream processing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
4.11 Stream intermediate operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.11.1 map . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.11.2 flatMap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.11.3 filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
4.11.4 distinct . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
4.11.5 limit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
4.11.6 skip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
4.11.7 sorted . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
4.11.8 peek . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
4.11.9 Operations chaining . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
4.12 Stream terminal operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
4.12.1 forEach and forEachOrdered . . . . . . . . . . . . . . . . . . . . . . . . . . 38
4.12.2 reduce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
4.12.3 max and min . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
4.12.4 match . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
CONTENTS
4.12.5 find . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
4.12.6 toArray . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
4.12.7 collect . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
4.12.8 Collectors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
4.12.8.1 Collect into collections . . . . . . . . . . . . . . . . . . . . . . . 42
4.12.8.2 groupingBy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
4.12.8.3 joining . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
4.12.8.4 mapping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
4.12.8.5 partitioningBy . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
4.12.8.6 counting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
4.12.8.7 averaging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
4.12.8.8 summing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
4.12.8.9 maxBy and minBy . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
4.12.8.10 summarizing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
4.12.8.11 collectingAndThen . . . . . . . . . . . . . . . . . . . . . . . . . 48
4.12.8.12 reducing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
4.12.9 Parallel stream . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
4.12.9.1 Parallel reduction . . . . . . . . . . . . . . . . . . . . . . . . . . 49
4.13 How-tos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
4.13.1 How to convert a Collection to a Map? . . . . . . . . . . . . . . . . . . . . 49
4.13.2 How to filter map entries by keys? . . . . . . . . . . . . . . . . . . . . . . . 50
4.13.3 How to filter map entries by values? . . . . . . . . . . . . . . . . . . . . . . 50
4.13.4 How to group elements by multiple conditions? . . . . . . . . . . . . . . . 50
4.13.5 How to combine multiple collections into a stream? . . . . . . . . . . . . 51
4.13.6 How to convert a stream to another stream with different type? . . . . . 51
4.13.7 How to find an element in the stream? . . . . . . . . . . . . . . . . . . . . 52
4.13.8 How to sort map entries? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
4.13.9 Exceptions handling in streams . . . . . . . . . . . . . . . . . . . . . . . . . 53
5. Optional . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
5.1 What’s Optional . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
5.2 Usage of Optional . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
5.2.1 Simple usage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
5.2.2 Chained usage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
5.2.3 Functional usage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
5.3 How-tos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
5.3.1 How to interact with legacy library code before Optional? . . . . . . . . 59
5.3.2 How to get value from chained Optional reference path? . . . . . . . . . 59
5.3.3 How to get first value of a list of Optionals? . . . . . . . . . . . . . . . . . 60
5.3.4 How to chain method invocations with return value of Optional
objects in sequence? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
5.3.5 How to convert an Optional object to a stream? . . . . . . . . . . . . . . 63
5.3.6 How to use Optional to check for null and assign default values? . . . 64
1. Introduction
This book is not the first book about Java 8 lambda expressions and streams, and it’s definitely not
the last one. Java 8 is a Java platform upgrade which the community has been looking forward to
for a long time. Lambda expressions and streams quickly gain popularity in Java developers. There
are already a lot of books and online tutorials about lambda expressions and streams. This book is
trying to explain lambda expressions and streams from a different perspective.
• For lambda expressions, this book explains in details based on JSR 335.
• For streams, this book covers fundamental concepts of Java core library.
• This book provides how-to examples for lambda expressions and streams.
• This book also covers the important utility class Optional.
Lambda expressions and streams are easy to understand and use. This book tries to provide some
insights about how to use them efficiently.
You can purchase and down PDF/EPUB/MOBI version of this book on Leanpub¹.
¹https://leanpub.com/java8-lambda-expressions-streams
2. Lambda expressions
When people are talking about Java 8, lambda expression is always the first feature to mention.
Lambda expression in Java 8 brings functional programming style into Java platform, which has
been demanded by Java developers in the community for a long time. Lambda expression is also
widely used as a marketing term to promote Java 8 upgrade.
Lambda expressions can increase developers’ productivity in many ways. There are a lot of books
and online tutorials about Java 8 lambda expressions. This chapter is trying to explain lambda
expressions from a different perspective based on the official JSR 335: Lambda Expressions for the
JavaTM Programming Language¹. This chapter also provides how-tos for real programming tasks.
The small Java program in Listing 2.1 has 9 lines of code, but only one line of code (line 5) does the
real work. All the rest are just boilerplate code. To increase productivity, boilerplate code should be
removed as much as possible.
In Listing 2.1, line 1, 2, 8 and 9 are required for Java program to run, so these lines cannot be removed.
Line 3 to 7 create a new java.lang.Thread object with an implementation of java.lang.Runnable
interface, then invoke Thread object’s start() method to start the thread. Runnable interface is
implemented using an anonymous inner class.
¹https://jcp.org/en/jsr/detail?id=335
Lambda expressions 3
To simplify the code, new Runnable() in line 3 and 7 can be removed because the interface type
Runnable can be deduced from allowed constructors of Thread class. Line 4 and 6 can also be
removed, because run() is the only method in Runnable interface.
After removing boilerplate code in line 3, 4, 6 and 7, we get the new code using lambda expressions
in Listing 2.2.
Listing 2.2 Lambda expressions style to start a thread
The new Java program in Listing 2.2 only has 5 lines of code and the core logic is implemented with
only one line of code. Lambda expression () -> System.out.println("Hello World!") does the
same thing as anonymous class in Listing 2.1, but using lambda expression is concise and elegant
and easier to understand with less code. Less code means increasing productivity.
Simply put, lambda expression is the syntax sugar to create anonymous inner classes. But there are
more to discuss about lambda expressions.
• java.lang.Runnable
• java.util.concurrent.Callable
• java.util.Comparator
• java.io.FileFilter
Java SE 8 adds a new package java.util.function which includes new functional interfaces.
Java developers don’t need to care too much about the concept of functional interfaces. If we are
trying to use lambda expressions with non-functional interfaces, compiler will throw errors, then
Lambda expressions 4
we’ll know about that. Modern IDEs can also do checks for us. For API and library authors, if an
interface is designed to be functional, it should be marked with @FunctionalInterface annotation.
Compiler will generate an error if the interface doesn’t meet the requirements of being a functional
interface.
In Listing 2.3, lambda expression in line 1 only has one formal parameter, so parentheses are omitted.
Expression’s body is a single expression, so braces are also omitted. Formal parameter list has
no type declaration, so the type of list is implicitly inferred by compiler. Lambda expression
in line 3 has two formal parameters, so parentheses around parameters list are required. Lambda
expression in line 5 has explicit type for formal parameters x and y. Lambda expression in line 7 has
no formal parameters. In this case, parentheses are required. Lambda expression starts from line 9
is a complicated example with three formal parameters and multiple lines of code in body. In this
case, braces are required to wrap the body.
Lambda expression’s body can return a value or nothing. If a value is returned, the type of return
value must be compatible with target type. If an exception is thrown by the body, the exception must
be allowed by target type’s throws declaration.
Lambda expressions don’t introduce a new level of scoping. They are lexically scoped, which means
names in lambda expression’s body are interpreted as they are in the expression’s enclosing context.
So lambda expression body is executed as it’s in the same context of code enclosing it, except that
the body can also access expression’s formal parameters. this used in the expression body has the
same meaning as in the enclosing code.
Listing 2.4 Lexical scoping of lambda expression
public void run() {
String name = "Alex";
new Thread(() -> System.out.println("Hello, " + name)).start();
}
In Listing 2.4, lambda expression uses variable name from enclosing context. Listing 2.5 shows the
usage of this in lambda expression body. this is used to access sayHello method in the enclosing
context.
Listing 2.5 this in lambda expression
public class LambdaThis {
private String name = "Alex";
From code examples in Listing 2.4 and Listing 2.5, we can see how lambda expressions make
developers’ life much easier by simplifying the scope and name resolution.
Listing 2.6 Compilation error of using non-final variables in lambda expression body
@FunctionalInterface
public interface StringProducer {
String produce();
}
Static method
Refer to a static method using ClassName::methodName, e.g. String::format, Integer::max.
Instance method of a particular object
Refer to an instance method of a particular object using instanceRef::methodName, e.g.
obj::toString, str::toUpperCase.
Method from superclass
Refer to an instance method of a particular object’s superclass using super::methodName,
e.g. super::toString. In Listing 2.8, if replacing this::toString with super::toString, then
toString of Object class will be invoked instead of toString of StringProducerMain class.
Instance method of an arbitrary object of a particular type
Refer to an instance method of any object of a particular type using ClassName::methodName,
e.g. String::toUpperCase. The syntax is the same as referring a static method. The difference
is that the first parameter of functional interface’s method is the invocation’s receiver.
Class constructor
Refer to a class constructor using ClassName::new, e.g. Date::new.
Array constructor
Refer to an array constructor using TypeName[]::new, e.g. int[]::new, String[]::new.
API and its client. Before Java 8, once an interface is published and implemented by other clients,
it’s very hard to add a new method to the interface without breaking existing implementations. All
implementations must be updated to implement the new method. This means interface design has
to be done correctly at the first time and cannot happen iteratively without breaking existing code.
Before Java 8, an interface can only have abstract methods. Abstract methods must be implemented
by implementation classes. Default methods of interfaces in Java 8 can have both declarations and
implementations. Implementation of a default method will be inherited by classes which don’t
override it. If we want to add a new method to an existing interface, this new method can have
default implementation. Then existing code won’t break because this new method will use the
default implementation. New code can override this default implementation to provide a better
solution. In the default method’s implementation, only existing interface methods can be used.
Listing 2.9 is a simple example about how to use default interface methods. The scenario is to insert
records into a database.
Listing 2.9 First version of interface RecordInserter to insert records
In the first version, interface RecordInserter only has one abstract method insert. SimpleRecordInserter
class in Listing 2.10 is the first implementation.
Listing 2.10 First implementation of RecordInserter interface
Then we find out that the performance of inserting a lot of records is not very good, so we want
to add batch inserting to improve performance. So a new method insertBatch is added to the
RecordInserter interface with default implementation. insertBatch takes a list of Record as input,
so the default implementation just iterates the list and uses existing insert method to insert Record
one by one. This default implementation can make sure SimpleRecordInserter class still works.
Lambda expressions 10
To improve the performance, a new implementation FastRecordInserter class overrides the default
implementation of insertBatch method and create a better solution.
Listing 2.12 New implementation of insertBatch method
3.1 Functions
3.1.1 Function
Function<T, R> represents a function which takes one argument and produces one result. T is the
type of the input to this function and R is the type of the output of this function. For example,
Function<String, Integer> represents a function takes a String object as the input and produces
an Integer object as the output. The only abstract method in Function is R apply(T t), which
applies this function with given input argument and returns the result.
convert method in Listing 3.1 takes a Function<String, Integer> object and a String object
as the input arguments and returns the result of applying the function to input String object.
When using convert method, we use method references String::length and String::hashCode
as implementation of this functional interface. The first argument when invoking these two method
references is the invocation’s receiver, i.e. input object, so these two method references match the
target type of Function<String, Integer>.
Listing 3.1 Example of using Function interface
public class FunctionUsage {
public static void main(String[] args) {
String input = "Hello World!";
FunctionUsage usage = new FunctionUsage();
System.out.println(usage.convert(String::length, input));
System.out.println(usage.convert(String::hashCode, input));
}
Besides the abstract apply() method, Function interface has two default methods and one static
method. Default methods <V> Function<T,V> andThen(Function<? super R,? extends V> after)
and <V> Function<V,R> compose(Function<? super V,? extends T> before) are designed to chain
Function interfaces together to create a new Function interface. addThen creates a composed method
which applies current function to the input first, then applies given after function to the result of
previous invocation. compose is the opposite of andThen, which applies given before function to the
input first, then applies current function to the result of previous invocation.
Listing 3.2 Example of using addThen and before
In Listing 3.2, find method uses method reference String::toLowerCase to create an instance of
Function<String, String>, then uses andThen to chain it with another lambda expression to search
in the lowercased string. findUppercase method uses a lambda expression to create an instance of
Function<String, Integer> to search in the string, then uses compose to apply method reference
String::toUpperCase before the first function.
The static method <T> Function<T,T> identity() creates a function which always returns its input
argument when invoked. identity is useful to turn an arbitrary object into a function. The type of
created function is Function<T, T> where T is the type of input argument.
Functional interfaces 13
3.1.2 BiFunction
BiFunction<T,U,R> represents a function with takes two arguments and produces one result. T is the
type of the first input argument, U is the type of the second argument and R is the type of output of
the function. The only abstract method in BiFunction is R apply(T t, U u), with the same name as
in Function. In Listing 3.4, substring with type BiFunction<String, Integer, String> is created
using a method reference to String class’s substring(int beginIndex) method.
Listing 3.4 Example of BiFunction
BiFunction also has default method <V> BiFunction<T,U,V> andThen(Function<? super R,?
extends V> after) to chain current function with another function. After applying current
BiFunction instance to input arguments, the result is provided to the next Function instance to
produce the actual result. In Listing 3.5, toUpperCase is created by chaining substring in Listing 3.4
with method reference to String class’s toUpperCase() method.
Listing 3.5 Example of andThen
DoubleFunction<R>
DoubleFunction<R> is the simplified syntax to create a function which takes a double type input
argument. The abstract method in this interface is R apply(double value). DoubleFunction<R>
is the simplified version of Function<Double, R>.
IntFunction<R>
IntFunction<R> represents a function which takes a int value as input argument. The abstract
method in this interface is R apply(int value). IntFunction<R> is the simplified version of
Function<Integer, R>.
LongFunction<R>
LongFunction<R> represents a function which takes a long value as input argument. The
abstract method in this interface is R apply(long value). LongFunction<R> is the simplified
version of Function<Long, R>.
ToDoubleFunction<T>
ToDoubleFunction<T> represents a function which produces a double value as result. The
abstract method in this interface is double applyAsDouble(T value). ToDoubleFunction<T>
is the simplified version of Function<T, Double>.
ToIntFunction<T>
ToIntFunction represents a function which produces a int value as result. The abstract method
in this interface is int applyAsInt(T value). ToIntFunction<T> is the simplified version of
Function<T, Integer>.
ToIntFunction<T>
ToLongFunction represents a function which produces a long value as result. The abstract
method in this interface is long applyAsLong(T value). ToLongFunction<T> is the simplified
version of Function<T, Long>.
DoubleToIntFunction
DoubleToIntFunction represents a function which takes a double value as input argument and
produces a int value as result. The abstract method in this interface is int applyAsInt(double
value). DoubleToIntFunction is the simplified version of Function<Double, Integer>.
DoubleToLongFunction
DoubleToLongFunction represents a function which takes a double value as input argu-
ment and produces a long value as result. The abstract method in this interface is long
applyAsLong(double value). DoubleToLongFunction is the simplified version of Function<Double,
Long>.
IntToDoubleFunction
IntToDoubleFunction represents a function which takes a int value as input argument
and produces a double value as result. The abstract method in this interface is double
applyAsDouble(int value). IntToDoubleFunction is the simplified version of Function<Integer,
Double>.
IntToLongFunction
IntToLongFunction represents a function which takes a int value as input argument and
produces a long value as result. The abstract method in this interface is long applyAsLong(int
value). IntToLongFunction is the simplified version of Function<Integer, Long>.
Functional interfaces 15
LongToDoubleFunction
LongToDoubleFunction represents a function which takes a long value as input argument
and produces a double value as result. The abstract method in this interface is double
applyAsDouble(long value). LongToDoubleFunction is the simplified version of Function<Long,
Double>.
LongToIntFunction
LongToIntFunction represents a function which takes a long value as input argument and
produces a int value as result. The abstract method in this interface is int applyAsInt(long
value). LongToIntFunction is the simplified version of Function<Long, Integer>.
ToDoubleBiFunction<T,U>
ToDoubleBiFunction<T,U> represents a function which takes two arguments and produces a
double value as result. The abstract method in this interface is double applyAsDouble(T t, U
u). ToDoubleBiFunction<T,U> is the simplified version of BiFunction<T, U, Double>.
ToIntBiFunction<T,U>
ToIntBiFunction<T,U> represents a function which takes two arguments and produces a
int value as result. The abstract method in this interface is int applyAsInt(T t, U u).
ToIntBiFunction<T,U> is the simplified version of BiFunction<T, U, Integer>.
ToLongBiFunction<T,U>
ToLongBiFunction<T,U> represents a function which takes two arguments and produces a
long value as result. The abstract method in this interface is long applyAsLong(T t, U u).
ToLongBiFunction<T,U> is the simplified version of BiFunction<T, U, Long>.
See Listing 3.6 for some examples of using predefined function types.
Listing 3.6 Examples of using simplified function syntax
3.2 Consumers
3.2.1 Consumer
Consumer<T> represents a function which takes an input argument of type T and returns no result. The
input argument is consumed and certain side-effects are expected by design since there is no result.
For example, Consumer<String> represents a function which consumes a String object. The abstract
method in the interface is void accept(T t). Consumer interface has one default method Consumer<T>
andThen(Consumer<? super T> after) to chain current function with another Consumer function to
process in sequence, which is similar with andThen method in Function interface.
Functional interfaces 16
3.2.2 BiConsumer
BiConsumer<T,U> represents a function which takes two input arguments with type T and U and
returns no result. For example, Consumer<String, Integer> represents a function which consumes
a String and an Integer object. The abstract method in the interface is void accept(T t, U u).
BiConsumer interface also has the default method BiConsumer<T,U> andThen(BiConsumer<? super
T,? super U> after) to chain BiConsumer functions together.
@Override
public String toString() {
return "Record{" +
"name='" + name + '\'' +
", value=" + value +
'}';
}
}
Listing 3.8 shows RecordProcessor class to process Records. addProcessor method adds a Consumer<Record>
instance to the processor chain. New processor is added to the end of the chain using addThen.
process method uses the processor chain to process the record.
Functional interfaces 18
Listing 3.9 shows an example of using RecordProcessor to process records. The initial record object’s
name is Test and value is 1. Then three processors are added to the processing chain. After applying
processors to the record object, the record object’s name is Demo and value is 30.
Listing 3.9 Use RecordProcessor to process records
3.3 Suppliers
3.3.1 Supplier
Supplier<T> is the opposite of Consumer, which takes no input and returns a object of type T as the
result. The abstract method in Supplier is T get().
@Override
public long getAsLong() {
return random.nextLong();
}
}
Functional interfaces 20
3.4 Predicates
3.4.1 Predicate
Predicate<T> represents a function which takes one input argument of type T and returns a boolean
value. Predicates are very useful in many scenarios, e.g. filtering elements in a collection. The
abstract method boolean test(T t) in Predicate interface is used to test if a predicate is satisfied.
Predicate interface has three default methods which can be used to compose Predicate instances
together to create a composite predicate. Predicate<T> and(Predicate<? super T> other) creates
a new predicate which represents a short-circuiting logical AND composition of current predicate and
another predicate. Predicate<T> or(Predicate<? super T> other) is similar with and, but it creates
a new predicate which represents a short-circuiting logical OR composition. Complicated predicates
can be created by combining and and or. and and or methods are short-circuiting, so they may not
be evaluated if the result of composed predicate is known after evaluating the first predicate. For
example, if current predicate of and method is evaluated with result false, then the other predicate
is not evaluated as the result is always false. This is also the case when current predicate of or
method is evaluated with result true. Predicate<T> negate() creates a predicate which is the logic
negation of current predicate.
Static method <T> Predicate<T> isEqual(Object targetRef) creates a predicate to test if two
objects are equal.
3.4.2 BiPredicate
BiPredicate<T,U> is similar with Predicate except that BiPredicate takes two input arguments
of type T and U and returns a boolean value. The abstract and default methods in BiPredicate
have the same name and meaning as in Predicate. The abstract method is boolean test(T t,
U u). The default methods are BiPredicate<T,U> and(BiPredicate<? super T,? super U>
other), BiPredicate<T,U> or(BiPredicate<? super T,? super U> other) and BiPredicate<T,U>
negate().
boolean value. The abstract method in this interface is boolean test(int value). It also has
default methods IntPredicate and(IntPredicate other), IntPredicate or(IntPredicate
other) and IntPredicate negate(). IntPredicate is the simplified version of Predicate<Integer>.
LongPredicate
LongPredicate represents a function which takes a long value as input argument and returns
a boolean value. The abstract method in this interface is boolean test(long value).
It also has default methods LongPredicate and(LongPredicate other), LongPredicate
or(LongPredicate other) and LongPredicate negate(). LongPredicate is the simplified
version of Predicate<Long>.
3.5 Operators
Operators are special kinds of functions which extend from Function or BiFunction with predefined
types.
3.5.1 UnaryOperator
UnaryOperator<T> extends from Function<T, T>, so both the input argument and return result have
the same type T. For example, UnaryOperator<String> represents a function which takes a String
objects as the input argument and returns a String object result. UnaryOperator has a static method
<T> UnaryOperator<T> identity() with the same meaning as identity() method in Function.
3.5.2 BinaryOperator
BinaryOperator<T> extends from BiFunction<T,T,T>, so all the input arguments and return result
have the same type T. For example, BinaryOperator<Integer> represents a function which takes two
Integer objects as the input arguments and returns a Integer object as the result. BinaryOperator
has two static methods BinaryOperator<T> maxBy(Comparator<? super T> comparator) and <T>
BinaryOperator<T> minBy(Comparator<? super T> comparator). maxBy() creates a BinaryOperator
which returns the input argument with greater value by comparing using given Comparator. minBy()
is similar with maxBy(), except that it returns the input argument with lesser value.
Functional interfaces 22
Stream source
Stream source is the source of elements in the stream. There are different kinds of stream
sources in Java 8, including arrays, collections, generator functions and I/O channels.
Intermediate operation
An intermediate operation perform computation on a stream and returns a new stream.
Intermediate operations are lazily executed.
Terminal operation
A terminal operation traverses the stream to produce a result or a side-effect. After a terminal
operation is performed on a stream, the stream is consumed and cannot be consumed again.
A stream pipeline starts its execution when the terminal operation is executed.
Stateful operations may need to process the entire input stream before they can produce results. So
stateful operations may affect performance in parallel computation. sorted operation may require
multiple passes on the stream to finish sorting. distinct operation needs additional memory to store
seen elements. If a stream pipeline only contains stateless operations, then it can be processed in one
pass with minimal extra memory.
4.4 Stream
Stream<T> is the interface to represent a stream. T is the type of elements in the stream.
Except from generic streams of objects, there are three other interfaces to represent streams of
primitive values.
IntStream, LongStream and DoubleStream should be used for int, long and double values in-
stead of using Stream<Integer>, Stream<Long> and Stream<Double>. IntStream, LongStream and
DoubleStream remove unnecessary boxing and unboxing, so they have better performance than
streams of wrapper types.
4.5 Spliterator
Spliterator is the most important concept to understand how streams work in Java 8. All streams
are created using some sort of Spliterators. The name Spliterator itself comes from split and
iterator. We can think Spliterator as a parallel version of Iterator which can partition elements
by splitting the data source. Spliterators can work like Iterators to traverse elements sequentially
by using tryAdvance() method. Spliterators can also be used in parallel operations by using
trySplit() method to create a new instance of Spliterator which works on a different set of
elements.
Spliterator also supports bulk traversal using default method forEachRemaining(Consumer<?
super T> action), which performs specified action for each remaining element in the source
sequentially. forEachRemaining is useful to process a relatively small number of elements when there
is no need for further splitting. When there are a large number of elements, we can use trySplit
to split current Spliterator into Spliterators with less elements. When the number of elements is
small enough, we can use forEachRemaining to process all remaining elements sequentially instead
of using trySplit to split again. We can get an estimate of the number of elements that would be
encountered by a forEachRemaining traversal using estimateSize() method, which can give us a
rough idea about the size of elements to be processed.
Streams 26
Name Description
IMMUTABLE Signifies that the element source cannot be modified, so there cannot be any change during
traversal.
CONCURRENT Signifies that the element source may be concurrently modified without using external
synchronization.
DISTINCT Signifies that the element source contains no duplicate elements.
NONNULL Signifies that the element source contains no null elements.
ORDERED Signifies that elements in the source have a defined encounter order. Elements will be
traversed in the encounter order.
SORTED Signifies that elements in the source have a defined sort order. If a Spliterator is SORTED,
then it must be ORDERED.
SIZED Signifies that the element source has an exact number of elements.
SUBSIZED Signifies that all Spliterators created from trySplit() will be both SIZED and SUBSIZED.
Spliterator also has some default methods which may be useful depends on its characteristics. If a
Spliterator is SORTED, we can use getComparator() method to get the Comparator instance used to
sort the elements. If elements are sorted in natural order, then getComparator method returns null.
getExactSizeIfKnown() method returns result of estimateSize() method when the Spliterator is
SIZED, otherwise returns -1.
Some Spliterators are also fail-fast, which means they throw ConcurrentModificationException
when detecting any interference during traversal after binding to the stream source.
In Listing 4.2. we use two threads to simulate concurent modification to the stream’s source during
traversal. When the code is running, ConcurrentModificationException will be thrown.
Streams 28
stream.forEach((str) -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(str);
});
4.6 BaseStream
BaseStream<T,S extends BaseStream<T,S>> is the superinterface of Stream<T>, IntStream, LongStream
and DoubleStream. Some aforementioned methods come from BaseStream, including isParallel(),
parallel(), sequential() and unordered(). BaseStream also has spliterator() to get a Spliterator
and iterator() to get an Iterator.
BaseStream extends from AutoCloseable, so we can use close() method to close the stream when re-
quired. Streams can also be used in try-with-resources statement. We can also use onClose(Runnable
closeHandler) method to add close handlers which are invoked when close() method is called on
the stream. Close handlers are invoked in the order they were added.
4.7 StreamSource
StreamSource is the low level API to create streams from Spliterators. StreamSource provides
a set of methods to create four kinds of streams, i.e. Stream<T>, IntStream, LongStream and
DoubleStream, from Spliterators or Suppliers of Spliterators. For example, <T> Stream<T>
stream(Spliterator<T> spliterator, boolean parallel) and <T> Stream<T> stream(Supplier<?
Streams 29
extends Spliterator<T>> supplier, int characteristics, boolean parallel) methods are used
to create generic Streams. Let’s take a closer look at these two methods.
Both methods accept parameter parallel to specify whether the stream is parallel or sequential,
which is the stream’s default execution mode. The difference of these two methods is whether
to create the stream directly from a Spliterator, or indirectly from a Supplier function of
Spliterator. It depends on the characteristics of underlying Spliterator to choose the correct way
to create the stream. If the Spliterator is IMMUTABLE or CONCURRENT or late-binding, then it should
be used directly to create the stream, otherwise Supplier function should be used.
When a Supplier function is used, it’s only invoked after the stream pipeline’s terminal operation
starts, so any changes to the stream source before the start of terminal operation are still visible
in the stream result. This can minimize potential interference with the data source. However, we
should always favor IMMUTABLE or CONCURRENT spliterators, or at least support late-binding.
empty()
empty method creates an empty sequential stream.
of(T t)
of method creates a sequential stream with a single element t of type T.
of(T... values)
of method creates a sequential stream with given elements values of type T.
generate(Supplier<T> s)
generate method creates an infinite unordered sequential stream with elements generated by
invoking Supplier function.
iterate(T seed, UnaryOperator<T> f)
iterate method creates an infinite ordered sequential stream with elements generated by
invoking function f with input from last invocation. seed is the initial input for first invocation.
The stream contains elements of seed, f(seed), f(f(seed)) and so on.
concat(Stream<? extends T> a, Stream<? extends T> b)
concat method creates a new stream by concatenating stream a and stream b. Elements in first
stream a come before stream b in the encounter order.
4.9.1 Arrays
To create a stream from an array, we can use new static methods added in java.util.Arrays. There
are eight methods with the same name stream to create streams from arrays. These eight methods
Streams 30
can be divided into four groups based on the types of streams they create, i.e. Stream<T>, IntStream,
LongStream and DoubleStream.
Take Stream<T> as the example, <T> Stream<T> stream(T[] array) method creates a stream from
the whole input array. <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive)
method creates a stream from the range specified by startInclusive and endExclusived index in
the input array.
Listing 4.3 Use Arrays.stream to create new streams
Spliterators of streams created using Arrays.stream method have characteristics IMMUTABLE and
ORDERED.
4.9.2 Collections
New default method stream() and parallelStream() are added to Collection interface to create
a sequential or parallel stream from a collection. Streams are created from spliterators returned
by new default method spliterator(). Collections’ spliterators have characteristics SIZED and
SUBSIZED by default. Sub-interfaces’ implementation of spliterator method may report additional
characteristics. For example, spliterators created from Sets have characteristic DISTINCT, while those
created from Lists have characteristic ORDERED.
Listing 4.4 Create streams from Collections
Stream.builder()
.add("Hello")
.add("World")
.build()
.forEach(System.out::println);
We can also use specialized stream builders for primitive types, including IntStream.Builder,
LongStream.Builder and DoubleStream.Builder.
4.9.4.1 lines
lines(Path path) and lines(Path path, Charset cs) are both used to read all lines from a file
specified by path as a Stream<String> object. Specified charset cs is used for string decoding, or
default charset UTF-8 is used.
Streams 32
4.9.4.2 list
list(Path dir) returns all entries in specified directory dir as a Stream<Path> object. This directory
listing is not recursive. If recursive listing is required, use walk method instead. Returned stream is
weakly consistent with underlying file system. It may not reflect updates to the underlying directory
which occur after this method is returned.
Listing 4.7 List files as stream
public void listFiles() throws IOException {
try (Stream<Path> paths = Files.list(Paths.get("."))) {
paths.forEach((path) -> System.out.println(path.getFileName()));
}
}
4.9.4.3 walk
walk(Path start, FileVisitOption... options) and walk(Path start, int maxDepth, FileVisitOption...
options) are both used to traverse the file system tree rooted at given path start as a Stream<Path>
object. If maxDepth parameter is provided, then it limits the maximum number of levels of directories
to visit, otherwise the whole directory tree is visited.
Listing 4.8 Walk file system directory tree
public void walkDirectory() throws IOException {
Path root = Paths.get("/tmp/test");
try (Stream<Path> paths = Files.walk(root)) {
paths.forEach((path) -> {
int level = path.getNameCount() - root.getNameCount();
String displayPath = String.format("|-%s %s",
StringUtils.repeat('-', level), path.getFileName());
System.out.println(displayPath);
});
}
}
Listing 4.9 Output of walking file system directory tree in Listing 4.8
|- test
|-- 1.txt
|-- a
|--- a1.txt
|--- a2.txt
|-- b
|--- b1.txt
|-- c
|--- c1.txt
4.9.4.4 find
java.util.zip.ZipFile adds a new method stream to return a Stream<? extends ZipEntry> object
with entries in the zip file.
java.util.jar.JarFile adds a new method stream to return a Stream<JarEntry> object with entries
in the jar file.
java.util.regex.Pattern adds a new method splitAsStream(CharSequence input) to split a
CharSequence object by current regular expression pattern and return a Stream<String> object with
matching substrings.
Streams 34
Customer
Customer class has the basic information about a customer.
Order
Order class has different properties about the order, including created time, status, amount and
the customer who placed this order.
LineItem
LineItem class has the related product id, price and quantity.
Streams 35
4.11.1 map
<R> Stream<R> map(Function<? super T,? extends R> mapper) transforms a stream with elements
of type T into a stream with elements of type R. The transformation is done by applying function
mapper to each element of the source stream.
If target type of transformation is primitive type double, int and long, methods DoubleStream
mapToDouble(ToDoubleFunction<? super T> mapper), IntStream mapToInt(ToIntFunction<?
super T> mapper) and LongStream mapToLong(ToLongFunction<? super T> mapper) can be used
to return DoubleStream, IntStream and LongStream, respectively.
In Listing 4.14, the first result is a stream of Integers as the number of line items in each order. The
second result is a stream with the same elements as the first result, but the type is IntStream.
Listing 4.14 Stream map operation
orders.stream()
.map(order -> order.getItems().size())
orders.stream()
.mapToInt(order -> order.getItems().size())
4.11.2 flatMap
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
method looks like map since flatMap also transforms a stream with elements of type T into a stream
with elements of type R, but the difference is that flatMap uses a transformation function which
transforms one element in source stream into another stream. All these transformed streams are
Streams 36
flattened to generate the resulting elements of the new stream. That’s why the return type of mapper
function is ? extends Stream<? extends R>.
Similar with map method, there are also primitive type versions of flatMap method: DoubleStream
flatMapToDouble(Function<? super T,? extends DoubleStream> mapper), IntStream flatMapToInt(Function<?
super T,? extends IntStream> mapper) and LongStream flatMapToLong(Function<? super T,?
extends LongStream> mapper).
4.11.3 filter
Stream<T> filter(Predicate<? super T> predicate) filters a stream by only keeping elements
match given predicate. Predicate is specified using Predicate interface which applies to an element
and returns a boolean value.
In Listing 4.16, the result is a stream of completed orders, i.e. status set to Order.Status.COMPLETED.
Listing 4.16 Stream filter operation
orders.stream()
.filter(order -> order.getStatus() == Order.Status.COMPLETED);
4.11.4 distinct
Stream<T> distinct() removes duplicate elements from a stream by using Object’s equals method
for comparison.
In Listing 4.17, the result is a stream of distinct orders.
Listing 4.17 Stream distinct operation
orders.stream().distinct();
4.11.5 limit
Stream<T> limit(long maxSize) truncates a stream to contain a maximum number of maxSize
elements.
In Listing 4.18, the result is a stream of at most 5 orders.
Streams 37
orders.stream().limit(5);
4.11.6 skip
Stream<T> skip(long n) returns a stream with elements from current stream except the first n
elements.
In Listing 4.19, the result is a stream of orders without first 2 orders.
Listing 4.19 Stream skip operation
orders.stream().skip(2);
4.11.7 sorted
Stream<T> sorted() and Stream<T> sorted(Comparator<? super T> comparator) sort a stream. If
no comparator is provided, elements are sorted using natural order, otherwise, elements are sorted
using given comparator.
In Listing 4.20, the result is a stream of orders sorted in chronological order. When invoking sorted
method, an instance of Comparator interface is passed in to compare orders using ZonedDateTime’s
compareTo method. Comparator.comparing is a new static method in Comparator interface to create
Comparator instances using value extraction methods.
orders.stream()
.sorted(Comparator.comparing(Order::getCreatedAt));
4.11.8 peek
We can use peek method to inspect elements in a stream without modifying the stream. Stream<T>
peek(Consumer<? super T> action) returns a stream with the same elements as in this stream.
When elements in the result stream are consumed, each element will be consumed by action first.
peek method is useful when debugging.
In Listing 4.21, each order will be outputted twice to console in sequence. The first output comes
from peek method invocation, the second output comes from forEach method invocation.
Streams 38
4.12.2 reduce
Optional<T> reduce(BinaryOperator<T> accumulator) performs a reduction on elements of the
stream using given accumulation function and returns an optional value as the result. BinaryOperator
instance accumulator takes current element and previous accumulated value as the input and returns
a new value. New accumulated value is used in next reduction operation. The accumulator function
needs to be associative to generate correct reduction result. The result type of this reduce method
is Optional<T>, because it cannot return a reduction value for an empty stream.
T reduce(T identity, BinaryOperator<T> accumulator) performs a reduction on elements of the
stream using given initial value and accumulation function. For empty streams, the initial value is
returned, otherwise the reduction value is returned.
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U>
combiner) is a more complicated and flexible reduction method. It has following arguments in order:
Streams 39
1. identity - The initial value of reduction. This value is returned for empty streams.
2. accumulator - The function to accumulate new reduction result. This function also does the
map operation from element of type T to the result type U. The input parameters of this function
are current reduction value and current element.
3. combiner - This function is only used for parallel streams. For sequential streams, this function
is not used. When reduction operation is executed on parallel streams, combiner function is
used to combine partial results from different executions into the final result.
The code in Listing 4.23 calculates the subtotal amount of each order using reduce. For reduction of
each order’s line items, the initial value is BigDecimal.ZERO. The accumulator function adds current
accumulated value with multiplication of each item’s unit price and quantity. The combiner function
is BigDecimal’s add method.
Listing 4.23 Stream reduce operation
4.12.4 match
Given a stream, we may want to check its elements to see if all, any or none of these ele-
ments match certain conditions. boolean allMatch(Predicate<? super T> predicate), boolean
anyMatch(Predicate<? super T> predicate) and boolean noneMatch(Predicate<? super T>
predicate) take a predicate as argument to check if all, any or none of stream’s elements match
the predicate.
In Listing 4.25, line 1 uses allMatch to check if all orders have at least one line item. Line 4 uses
anyMatch to check if any order’s grandTotalAmount is larger than 100. Line 8 uses noneMatch to
check if there is no order with status not equals to Order.Status.COMPLETED. Line 8 is the same as
orders.stream().allMatch(order -> order.getStatus() == Order.Status.COMPLETED).
4.12.5 find
Optional<T> findFirst() and Optional<T> findAny() find an element from the stream, or an
empty Optional object if the stream is empty. findFirst returns the first element in the stream if
the stream has defined encounter order, otherwise any element may be returned. findAny returns
any element in the stream. So if a stream has no encounter order, findFirst and findAny actually
do the same thing.
In Listing 4.26, firstOrder and anyOrder represent first and any order in the stream of orders.
Listing 4.26 Stream findFirst and findAny operation
4.12.6 toArray
Object[] toArray() method converts the stream into an array.
<A> A[] toArray(IntFunction<A[]> generator) method uses a generator function to create the
return array. The generator function takes a int value as the input, which is the required size of the
array, then it should return an array of type A[] with the required size.
4.12.7 collect
Terminal operation collect represents a different type of mutable reduction operation. Instead of
returning a value, mutable reduction operation accumulates elements into a mutable result container,
e.g. a List or a Set.
The first form of collect method is <R,A> R collect(Collector<? super T,A,R> collector).
This method takes an input argument of interface java.util.stream.Collector. Collector is a
simple interface which only contains abstract methods to return required components of a collector.
Following are methods of Collector interface:
As Collector interface only contains abstract methods to return different functions, it provides two
static methods of to create an instance of Collector interface easily by providing these functions
directly.
4.12.8 Collectors
Usually we don’t need to implement Collector interface directly, nor use static of method,
java.util.stream.Collectors class provides a lot of helper methods to create different kinds of
Collector instances.
A common usage of collectors is to collect elements into collections. Collectors class has different
methods to create Collector instances for different types of collections.
toConcurrentMap
There are three methods with the same name toConcurrentMap which have the same signature
as toMap methods. The difference is that toConcurrentMap methods return ConcurrentMap
instances instead of Map instances.
In Listing 4.27, line 1 collects the stream of orders into a Set using Collectors.toSet. Line 3 collects
the stream into a Map<Long, Order>. Each entry in the map has the key of the order’ id and has
the value of the order object itself. Function.identity is used here to return the order object itself
without any transformation.
Listing 4.27 Stream collect operation
4.12.8.2 groupingBy
A set of groupingBy methods can be used to create a Collector to group input elements by a
classification function. Elements with the same result after applying the classification function will
be put into the same group.
4.12.8.3 joining
The set of joining methods is used to concatenate input elements of CharSequence together to create
a String. Input elements are concatenated in encounter order.
Collector<CharSequence,?,String> joining()
This is the simplest form of joining method which returns a Collector instance to concatenate
CharSequence elements to create a result String.
Collector<CharSequence,?,String> joining(CharSequence delimiter)
This form of joining method takes an extra argument delimiter as the delimiter to separate
different elements.
Collector<CharSequence,?,String> joining(CharSequence delimiter, CharSequence prefix,
CharSequence suffix)
This is the most complicated form of joining method which also allows to specify the prefix
and suffix of the concatenated string.
Streams 45
Stream.of("Hello", "World").collect(Collectors.joining());
// -> HelloWorld
4.12.8.4 mapping
orders.stream()
.collect(Collectors.mapping(
order -> order.getCustomer().getEmail(), Collectors.joining(", ")
));
4.12.8.5 partitioningBy
A set of partitioningBy methods can be used to create a Collector which partitions input elements
according to a Predicate.
orders.stream()
.collect(Collectors.partitioningBy(
order -> order.getStatus() == Order.Status.COMPLETED
));
4.12.8.6 counting
<T> Collector<T,?,Long> counting() method creates a Collector which counts the number of
input elements.
Listing 4.33 Collector counting
orders.stream().collect(Collectors.counting());
4.12.8.7 averaging
orders.stream()
.collect(Collectors.averagingDouble(
order -> order.getGrandTotalAmount().doubleValue()));
4.12.8.8 summing
orders.stream()
.collect(Collectors.summingDouble(
order -> order.getGrandTotalAmount().doubleValue()));
orders.stream()
.collect(Collectors.maxBy(Comparator.comparing(Order::getGrandTotalAmount)));
orders.stream()
.collect(Collectors.minBy(Comparator.comparing(Order::getGrandTotalAmount)));
4.12.8.10 summarizing
If we want to know more statistics about a stream’s elements, use <T> Collector<T,?,DoubleSummaryStatistics>
summarizingDouble(ToDoubleFunction<? super T> mapper), <T> Collector<T,?,IntSummaryStatistics>
summarizingInt(ToIntFunction<? super T> mapper) and <T> Collector<T,?,LongSummaryStatistics>
summarizingLong(ToLongFunction<? super T> mapper). These three methods create Collectors
which transform input elements to double, int or long values, then return objects about statistics
of those primitive values.
DoubleSummaryStatistics, IntSummaryStatistics and LongSummaryStatistics are the classes to
represent statistics for different types. These classes have methods to get different statistics.
orders.stream()
.collect(Collectors.summarizingDouble(
order -> order.getGrandTotalAmount().doubleValue()))
DoubleSummaryStatistics{count=3, sum=220.000000,
min=50.000000, average=73.333333, max=100.000000}
4.12.8.11 collectingAndThen
4.12.8.12 reducing
There are also Collectors to perform reduction operations which can be created using reducing
methods. These reducing methods are similar with Stream’s reduce method. Since these reducing
methods create Collectors, they are especially useful when used as downstream collectors of other
methods, e.g. groupingBy or partitioningBy.
These three reducing methods <T> Collector<T,?,Optional<T>> reducing(BinaryOperator<T>
op), <T> Collector<T,?,T> reducing(T identity, BinaryOperator<T> op) and <T,U> Collector<T,?,U>
reducing(U identity, Function<? super T,? extends U> mapper, BinaryOperator<U> op) take
the same input arguments as their corresponding reduce methods in Stream.
Arrays.stream(values).max();
Arrays.stream(values).parallel().max();
Stream’s reduce method can perform reduction on elements. reduce method has a parameter
combiner as the function to combine results from parallel executions. reduce method turns a stream
into a single value as the result. reduce method works by applying accumulator and combiner
functions iteratively to elements and partial results. accumulator and combiner functions should
be stateless, then each method invocation doesn’t modify any shared state. So reduce method can
work correctly for parallel streams. However, reduce method may have performance issues for
certain cases. For example, we can use reduce method to collect elements in a stream into a list.
The accumulator function should take current accumulated List object and current element as
the input parameters and return a new List object which contains all accumulated elements and
current element. The combiner function takes two List objects as the input parameters and returns
a new List object which contains all elements in two input List objects. This may create too many
intermediate List objects which requires more memory to process.
On the other hand, Stream’s collect performs a mutable reduction operation by using shared
objects. Comparing to reduce method, collect requires less memory, but needs to be used carefully
with parallel streams. Collector implementation requires synchronization when accessing shared
objects for parallel streams.
4.13 How-tos
Stream.concat(
Stream.concat(list1.stream(),list2.stream()),
list3.stream()
).forEach(System.out::println);
Now we have a list of user IDs and we need to return a list of User objects corresponding to these
IDs. So we need to implement the method List<User> loadAllUsers(List<String> userIds).
We cannot simply use userIds.stream().map(userService::load). It’s a compilation error because
we didn’t handle the check exception UserLoadException. Now we need to decide how to handle
the exception. It’s a simple question and the answer depends on the business logic. For example, if
the list of user IDs contain 10 elements and two of them went wrong when loading, what we should
do?
The first choice is to simply ignore these two user IDs and continue with the rest of successfully
loaded User objects. Then we can have following exception handling logic. Here we map the stream
of user IDs to a stream of Optional<User>, then do a filter operation.
Listing 4.49 Best effort exception handling
The second choice is to fail the whole loading operation and return no results. The logic behind
this is to treat the whole loading operation as an atomic unit. Here we only to wrap the checked
exception as unchecked and throw the unchecked exception.
Listing 4.50 Fail fast exception handling
public List<User> loadAllUsersFailFast(final List<String> userIds) {
return userIds.stream().map(id -> {
try {
return this.userService.load(id);
} catch (UserLoadException e) {
throw new RuntimeException(e);
}
}).collect(Collectors.toList());
}
The last choice is similar with the second one, except that it can also return the exception. The return
type becomes Tuple2<List<User>, Optional<UserLoadException>>. If the load succeeded, the
Tuple2 contains the List<User> object with Optional object is empty; if any exception occurred dur-
ing the loading, multiple exceptions are reduced to a single one using Throwable.addSuppressed().
Listing 4.51 More exception details
public Tuple2<List<User>, Optional<UserLoadException>> loadAllUsersWithException(
final List<String> userIds) {
return userIds.stream().map(id -> {
try {
return Tuple
.of((List<User>) Lists.newArrayList(this.userService.load(id)),
Optional.<UserLoadException>empty());
} catch (UserLoadException e) {
return Tuple.of((List<User>) Lists.<User>newArrayList(), Optional.of(e));
}
}).reduce(Tuple.of(new ArrayList<>(), Optional.empty()), (r1, r2) -> {
r1._1.addAll(r2._1);
if (r2._2.isPresent()) {
if (r1._2.isPresent()) {
r1._2.get().addSuppressed(r2._2.get());
} else {
return Tuple.of(r1._1, r2._2);
}
}
return r1;
});
}
5. Optional
NullPointerExceptions may be the most seen exceptions during Java developers’ daily develop-
ment. Null reference is considered as The billion dollar mistake by its inventor Tony Hoare¹. Maybe
null should not be introduced into Java language in the first place. But it’s already there, so we have
to deal with it.
Suppose we have a method with argument val of a non-primitive type, we should check first to
make sure the value of val is not null.
Listing 5.1 Check null value
Listing 5.1 shows a common pattern of writing code that takes arguments with possible null values.
If a method accepts multiple arguments, all these arguments should be checked. Utility methods like
Objects.requireNonNull() can help, but the code is still long and tedious. Another case is for long
object references, e.g. a.b.c, then all objects in the reference path need to be checked. Groovy has
safe navigation operator² ?., but Java doesn’t the same thing.
The major benefit of using Optional is to force client code to deal with the situation that the actual
object that it wants to use may be null. Instead of using the object reference directly, the Optional
object needs to be queried first.
¹http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare
²http://docs.groovy-lang.org/latest/html/documentation/#_safe_navigation_operator
³https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html
Optional 56
Optional objects are very easy to create. If we are sure the actual object is not null, we can use
<T> Optional<T> of(T value) method to create an Optional object, otherwise we should use <T>
Optional<T> ofNullable(T value) method. If we just want to create an Optional object which
holds nothing, we can use <T> Optional<T> empty().
Listing 5.2 Create Optional objects
Listing 5.3 is similar with Listing 5.1, except it uses Optional. But the code in Listing is still long and
tedious.
T orElse(T other) returns the value if it’s present, otherwise return the specified other object. This
other object acts as the default or fallback value.
T orElseGet(Supplier<? extends T> other) is similar with orElse, except a supplier function is in-
voked to get the value if not present. getPort() method in Listing invokes getNextAvailablePort()
method to get the port if no value is present.
Listing 5.5 Example of Optional.orElseGet
<U> Optional<U> map(Function<? super T,? extends U> mapper) applies specified mapping
function to the Optional’s value if present. If the mapping result is not null, it returns an Optional
object with this mapping result, otherwise returns an empty Optional object.
Listing 5.8 outputs the length of input string.
Listing 5.8 Example of Optional.map
<U> Optional<U> flatMap(Function<? super T,Optional<U>> mapper) is similar with map(), but
the mapping function returns an Optional object. The returned Optional object only contains value
when value if present in both current Optional object and mapping result Optional object.
flatMap() is very useful when handling long reference chain. For example, considering a Customer
class has a method getAddress() to return an Optional<Address> object. The Address class has a
method getZipCode() to return an Optional<ZipCode> object. Listing 5.9 use two chained flatMap()
method invocations to get an Optional<ZipCode> object from an Optional<Customer> object. With
the use of flatMap(), it’s very easy to create safe chained object references.
Listing 5.9 Example of Optional.flatMap
5.3 How-tos
obj.setUpdated(date.orElse(new Date()));
}
class C {
private String value = "Hello";
class B {
private C c = new C();
public Optional<C> getC() {
return Optional.ofNullable(c);
}
}
class A {
private B b = new B();
public Optional<B> getB() {
return Optional.ofNullable(b);
}
}
}
Suppose we want to retrieve a value to be used in the client code and we have different types of
retrievers to use, the code logic is that we try the first retriever first, then the second retriever and
so on, until a value is retrieved. In Listing 5.14, ValueRetriever is the interface of retrievers with
only one method retrieve() which returns an Optional<Value> object. Classes ValueRetriever1,
ValueRetriever2 and ValueRetriever3 are different implementations of interface ValueRetriever.
Listing 5.14 shows two different ways to chain method invocations. First method retrieveResultOrElseGet()
uses Optional’s orElseGet() method to invoke a Supplier function to retrieve the value. Second
method retrieveResultStream() uses Stream.of() to create a stream of Supplier<Optional<Value>>
objects, then filters the stream to only include Optionals with values, then gets the first value.
Listing 5.14 Chain method invocations with return value of Optional objects in sequence
System.out.println(value);
value = chainedOptionals.retrieveResultStream();
System.out.println(value);
}
class Value {
private String value;
public Value(String value) {
this.value = value;
}
@Override
public String toString() {
return String.format("Value -> [%s]", this.value);
}
}
interface ValueRetriever {
Optional<Value> retrieve();
}
@Override
public Optional<Value> retrieve() {
Optional 63
return Optional.empty();
}
}
@Override
public Optional<Value> retrieve() {
return Optional.of(new Value("hello"));
}
}
@Override
public Optional<Value> retrieve() {
return Optional.of(new Value("world"));
}
}
}
As streams are lazily evaluated, Supplier functions in Listing 5.14 won’t be invoked unless
last Supplier function returns an empty Optional object. This can make sure there won’t
be any unnecessary method invocations.
5.3.6 How to use Optional to check for null and assign default
values?
It’s a common case to check if a value is null and assign default value to it if it’s null. Optional can
be used to write elegant code in this case. Listing 5.16 shows the traditional way to check for null
which uses four lines of code.
Listing 5.16 Traditional way to check for null
Listing 5.17 shows how to use Optional to do the same thing as Listing 5.16 with only one line of
code.
Listing 5.17 Use Optional to simplify code