Java SE 8 for the Really Impatient: Programming with Lambdas
In the first two chapters, you saw the basic syntax and semantics of lambda expressions as well as the stream API that makes extensive use of them. In this chapter, you will learn how to create your own libraries that make use of lambda expressions and functional interfaces.
The key points of this chapter are:
- The main reason for using a lambda expression is to defer the execution of the code until an appropriate time.
- When a lambda expression is executed, make sure to provide any required data as inputs.
- Choose one of the existing functional interfaces if you can.
- It is often useful to write methods that return an instance of a functional interface.
- When you work with transformations, consider how you can compose them.
- To compose transformations lazily, you need to keep a list of all pending transformations and apply them in the end.
- If you need to apply a lambda many times, you often have a chance to split up the work into subtasks that execute concurrently.
- Think what should happen when you work with a lambda expression that throws an exception.
- When working with generic functional interfaces, use ? super wildcards for argument types, ? extends wildcards for return types.
- When working with generic types that can be transformed by functions, consider supplying map and flatMap.
3.1. Deferred Execution
The point of all lambdas is deferred execution. After all, if you wanted to execute some code right now, you’d do that, without wrapping it inside a lambda. There are many reasons for executing code later, such as
- Running the code in a separate thread
- Running the code multiple times
- Running the code at the right point in an algorithm (for example, the comparison operation in sorting)
- Running the code when something happens (a button was clicked, data has arrived, and so on)
- Running the code only when necessary
It is a good idea to think through what you want to achieve when you set out programming with lambdas.
Let us look at a simple example. Suppose you log an event:
logger.info("x: " + x + ", y: " + y);
What happens if the log level is set to suppress INFO messages? The message string is computed and passed to the info method, which then decides to throw it away. Wouldn’t it be nicer if the string concatenation only happened when necessary?
Running code only when necessary is a use case for lambdas. The standard idiom is to wrap the code in a no-arg lambda:
() -> "x: " + x + ", y: " + y
Now we need to write a method that
- Accepts the lambda
- Checks whether it should be called
- Calls it when necessary
To accept the lambda, we need to pick (or, in rare cases, provide) a functional interface. We discuss the process of choosing an interface in more detail in Section 3.3, “Choosing a Functional Interface,” on page 50. Here, a good choice is a Supplier<String>. The following method provides lazy logging:
public static void info(Logger logger, Supplier<String> message) { if (logger.isLoggable(Level.INFO)) logger.info(message.get()); }
We use the isLoggable method of the Logger class to decide whether INFO messages should be logged. If so, we invoke the lambda by calling its abstract method, which happens to be called get.