A function with no side effects is not affected by anything outside and does not affect anything outside of its bounds. Functions or methods with side effects are hard to understand, hard to maintain, more error prone, and diffi- cult to parallelize.
If we remove side effects, then as long as the input to a function remains unchanged, the output will always be the same. This makes it easier to Chapter 9. Bringing It All Together
•
148understand the code and makes us need fewer test cases to ensure the proper behavior.
Having no side effects is critical for referential transparency, which means an invocation or a call to a function can be replaced by its result value without affecting a program’s correctness. The functional style greatly favors creating functions with no side effects, and the benefits are far reaching.
The javac compiler and the JVM just-in-time compiler can easily optimize calls to functions with no side effects. Functions that have side effects impose ordering and restrict optimization. On the other hand, calls to functions with no side effects can be moved around and reordered more freely. For example, in the next figure F1 and F2 are two independent function calls. The compiler can change the order of their sequential execution or even schedule them to run concurrently on multiple cores, thanks to their referential transparent behavior.
F1 F2
Time
F2 F1
called sequentially called sequentially called concurrently
F2 F1
Figure 12—We can easily reorder functions that have no side effects.
When working with lambda expressions, we should ensure that the code is without side effects. Doing so will not only reduce the chance of errors, but also help us easily parallelize the code, as we saw in Taking a Leap to Paral- lelize, on page 143. It’s critical to eliminate side effects if we want to use techniques like the tail-call optimization we saw in Using Tail-Call Optimization, on page 121.
Prefer Expressions Over Statements
Both expressions and statements are commands we write in programs to instruct the computer to perform some action or do some work. Statements perform actions but don’t return anything, whereas expressions perform actions and return a result. When programming with lambda expressions we can reap benefits by leaning toward creating expressions more than statements.
First, since statements don’t return anything, they have to cause side effects and mutate memory to fulfill their purpose. Expressions, on the other hand, can be designed to favor referential transparency, giving us the benefits we discussed previously.
The other benefit is that unlike statements, expressions can be composed.
This can help us use a very powerful pattern in the functional style of pro- gramming—function chaining. We can create a chain of functions so the results of computations flow smoothly from one function into the next. The code begins to read like the problem statement, making it easier to follow.
We saw a benefit of this in …To the Functional Style, on page 141, where we sent a list of stock-ticker symbols through a chain of functions to determine the highest-priced stock and its price. This pattern can also help us create fluent interfaces, as we saw in Creating Fluent Interfaces Using Lambda Expressions, on page 80.
Design with Higher-Order Functions
In Java 8, one of the biggest changes we have to make is to design with higher-order functions. We’re used to passing objects to methods, but now we also have the ability to pass functions as arguments. This gives us more concise code: anywhere we passed anonymous inner classes to single method interfaces, we can now pass lambda expressions or method references.
For example, to register a simple event handler for a Swing button, we had to jump through hoops before, like in the next example.
button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) {
JOptionPane.showMessageDialog(frame, "you clicked!");
} });
We can trade such clunky code in for more concise code, like this:
button.addActionListener(event ->
JOptionPane.showMessageDialog(frame, "you clicked!"));
Chapter 9. Bringing It All Together
•
150The ceremony and the clutter are gone, leaving behind just the essence. Not only did we write fewer lines of code here, but we also needed fewer imports in the code. That’s because we no longer have to refer to the ActionListener interface by name, and the reference to ActionEvent is optional since we used type inference.
Once we get used to lambda expressions, they will have a lot of impact on our designs. We can design our methods to receive functional interfaces as parameters. This will enable the callers to pass in either lambda expressions or method references as arguments, which will help us take a lightweight approach to separating concerns from methods and classes, like we discussed in Chapter 4, Designing with Lambda Expressions, on page 63. The common, familiar design patterns are more approachable when we design with lambda expressions; we need fewer lines of code, classes, and interfaces, and far less ceremony to implement our designs.