I highly recommend this book to Java programmers who want to get started with functional programming in Java 8. I express my sincere gratitude to the smart developers who volunteered their personal time to review this book.
Who’s This Book For
What’s in This Book
We discuss functional style design techniques in Chapter 4, Designing with Lambda Expressions, on page 63. In Chapter 9, Bringing It All Together, on page 147, we discuss the key concepts and the practices needed to adopt them. techniques.
Java Version Used in This Book
How to Read the Code Examples
Online Resources
CHAPTER 1
The new way of programming in Java has existed for decades in other languages. With these facilities in Java, we can write concise, elegant and expressive code with fewer errors.
Change the Way You Think
This can be used to easily enforce policies and implement common design patterns with fewer lines of code. In this book, we will explore the functional programming style using direct examples of the everyday tasks we do as programmers.
The Habitual Way
Before we jump into this elegant style, and this new way of designing and programming, let's discuss why it's better. If we have found the city we are looking for, then we set the flag and break out of the loop.
A Better Way
Tangible Improvements
Beyond Simple Cases
The Old Way
A Better Way, Again
The code is concise, but we use quite a few new things from Java 8. Instead of explicitly repeating the price list, we use a few special methods, such as filter() and map().
The Improvements
This opens the door to a special repeater with a host of convenience features, which we'll discuss later. Unlike the methods we are used to in Java and the Java Development Kit (JDK), these methods take an anonymous function - a lambda expression - as a parameter, inside the parentheses (). We'll explore this further shortly.) We call the reduce() method to calculate the total on the result of the map() method.
Lambdas to the Rescue
The Big Gains of Functional-Style Code
With the support for lambda expressions, we can fully utilize the power of the functional programming style in Java. We can continue to use the OOP style to model domain entities, their states, and their relationships.
Why Code in the Functional Style?
If we embrace this style, we can create more expressive, concise code with less variability and fewer bugs. In addition, we can model behavior or state transformations, business workflow, and data processing as a series of functions to form a function composition.
Joe asks
Does concise just mean less code?
Iteration on Steroids
Java now provides specialized internal iterators for various operations: one to simply loop, one to map data values, one to filter out selected values, one to reduce, and several convenience functions to select min, max, average, and so on. We will see more examples of this in Chapter 2, Using Collections, on page 19 and throughout the book.
Enforcing Policies
Once finished, the code is easier to understand as it logically follows the sequence described in the task. In Chapter 5, Working with Resources, on page 87, we discuss how to use lambda expressions to enforce such policies.
Extending Policies
Hassle-Free Concurrency
The JDK library is designed to make switching between serial and parallel execution only a small and explicit but unobtrusive code change, as we'll see in Taking a Leap to Parallelize, on page 143.
Telling the Story
Separation of Concerns
Delaying Evaluation
Improving Testability
Evolution, Not Revolution
Be Declarative
Promote Immutability
Avoid Side Effects
Prefer Expressions Over Statements
The code will flow logically, in the same order in which we would formulate the problem.
Design with Higher-Order Functions
Passing anonymous functions is not a completely new concept in Java; we are used to passing instances of anonymous classes. We are used to passing objects to methods, but now we can store functions and pass them.
A Little Sugar to Sweeten
When we pass a lambda expression to a method, the compiler will convert the lambda expression into an instance of the corresponding functional interface. The synthesized method of this instance conforms to the abstract method of the functional interface corresponding to the argument.
Recap
CHAPTER 2
After this chapter, our Java code for manipulating collections will never be the same—it will be concise, expressive, elegant, and more extensible than ever before.
Iterating through a List
The forEach() method will call the given consumer's accept() method for each element in the collection and let it do whatever it wants with it. Once the forEach method is fired, unlike the other two versions, we cannot break the iteration.
Transforming a List
As a first step toward a functional style, we could use the internal iterator forEach() method from Iterating through a list on page 19 to replace the for loop, as we'll see next. We used the internal iterator, but that still required the empty list and the effort of adding elements to it.
Using Lambda Expressions
In this imperative style, we created an empty list, then filled it with all-caps names, one element at a time, while iterating through the original list. In this case, the input would still be a sequence of strings and the output would still be a sequence of numbers, as in the following example.
Using Method References
These versions also did not need any initial empty collection or garbage variable; that variable quietly retreated into the shadows of the underlying implementation.
When should we use method references?
Finding Elements
It is clear from the output that the method retrieved the correct number of elements from the input set. It can produce a result set with a number of elements ranging from zero to the maximum number of elements in the input set.
Reusing Lambda Expressions
The filter() method, the recipient of the lambda expression in the previous example, references a functional interface java.util.function.Predicate. On the three calls to the filter() method, the Java compiler happily adopted the lambda expression stored in the variable under the guise of the Predicate instance.
Using Lexical Scoping and Closures
Let's refactor the previous code to make it DRY.1 (See Don't Repeat Yourself . —DRY—principle in The Pragmatic Programmer: From Journeyman to Master [HT00], by Andy Hunt and Dave Thomas.). Instead of copying the lambda expression multiple times, we create it once and store it in a reference called startsWithN of type Predicate.
Duplication in Lambda Expressions
Removing Duplication with Lexical Scoping
In the return name -> name.startsWith(letter) it is clear what the name is: it is the parameter passed to this lambda expression. Because this lambda expression closes within the scope of its definition, it is also called a closure.
Are there restrictions to lexical scoping?
Since this is not in the scope of this anonymous function, Java reaches the scope of the definition of this lambda expression and finds the variable letter in that scope. This call immediately returns a lambda expression that is then passed to the filter() method.
Refactoring to Narrow the Scope
A predicate
Picking an Element
Let's exercise the pickName() function with the sample collection of friends we've used in the examples so far. The search for the first matching element turned up a few nicer options in the JDK.
Reducing a Collection to a Single Value
Lambda expression calls continue for the remaining elements in the collection. The result of the last call is returned as the result of the reduce() method call.
Joining Elements
CHAPTER 3
And wherever we used an anonymous inner class with only one method, we can now use lambda expressions to reduce clutter and ceremony. In this chapter we will use lambda expressions and method references to iterate through a string, implement comparators, list files in a directory, and observe changes to files and directories.
Iterating a String
We can use either a class name or an expression to the left of the double colon in method references. Again, instead of the lambda expressions we passed to the filter() method and the forEach() method, we can use references to the respective methods.
Implementing the Comparator Interface
The one for an instance method and the one for a static method look structurally the same: for example, String::toUppercase and Character::isDigit. To decide how to send the parameter, the Java compiler will check whether the method is an instance method or a static method.
Sorting with a Comparator
In the lambda expression that we pass to the sort() method, we simply route the two parameters: the first parameter as the target to the ageDifference() method and the second as its argument. Let's replace the lambda expression in the previous call to the sorted() method with a short and sweet reference to the ageDifference() method.
Reusing a Comparator
In the output we should now see the people with names listed in ascending alphabetical order. Let's look at the output for the name and age of the oldest in the list.
Multiple and Fluent Comparisons
The output from this code shows the net result of sorting first by age and then by name.
Using the collect Method and the Collectors Class
Here is the code to list the names of all the files in the current directory. We retrieve the list of files in the current directory and loop through each of the files.
Watching a File Change
CHAPTER 4
We'll use lambda expressions to easily separate logic from functions, making them more extensible. Then we'll apply it to delegate responsibilities and implement the decorator pattern in just a few lines of code.
Separating Concerns Using Lambda Expressions
We need fewer lines of code to get the same job done, and we can quickly try out new ideas. In this chapter, lambda expressions bring some neat design ideas to life; where we often use objects, we'll use lightweight functions instead.
Exploring Design Concerns
OOP has become the de facto standard, but with lambda expressions in Java, we can pull a few more techniques out of our bag of design tricks. Suppose we are asked to sum the values of all the given assets - let's write a method for that in an AssetUtil class.
Getting Entangled with the Concerns
Not quite; if our geek friends discover the duplicates, they won't hang out with us anymore.
Refactoring to Separate a Key Concern
We have used the open/closed principle in this refactored design.2 We can easily change the selection criteria without changing the method, as we will see next. However, they can store these lambda expressions in variables and reuse them if they wish.
Delegating Using Lambda Expressions
Let's reuse the function to calculate the total of just the bonds and then the total of just the stocks. Let these last three calls to the refactored totalAssetValues() function run quickly to ensure the output is the same as the previous version.
Creating a Delegate
Stubbing the Web Service
We are ready to use CalculateNAV, but we need to implement a call to the web service. Testing the code was fast; we easily removed the dependency on the web service, which helped in rapid code development and testing.
Integrating with the Web Service
The lambda expression now simply passes the exception, and upstream in the code we will have to handle it. We have delegated part of the responsibility of our class using lambda expressions and method references in this example, as the following figure demonstrates.
Decorating Using Lambda Expressions
Delegation is great, but we can take it further if we can chain delegates to add behaviors. This will help us see how we can create flexible and extensible lightweight design with just a few lines of code.
Designing Filters
In addition to the abstract apply() method, the Function interface has a default compose() method for combining or chaining multiple functions. We provided a lambda expression as a parameter to the orElse() method of the selector returned by the reduce() method.
Adding a Filter
Adding Multiple Filters
Adding more than two filters is no different; we simply pass more comma-separated filters, either as method references or as lambda expressions. We designed object chains and implemented the decoration pattern without having to create a hierarchy of classes.
A Peek into the default Methods
For example, from the cruise() method we can see how to call the default methods of both the FastFly and the Sail interfaces. To see the behavior of the default methods in action, let's create an instance of SeaPlane and call the methods on it.
Creating Fluent Interfaces Using Lambda Expressions
And now, thanks to the ability to have default methods, interfaces are even more attractive and powerful than before. Now that we understand the behavior of default methods, let's shift our attention back to lambda expressions.
Starting with a Design
So far in this chapter, we've seen the different forms lambda expressions can take and the many design goals we were able to achieve by using them. Second, at the end of the call, what do we do with the mailer instance.
Using Method Chaining
The design does not prevent someone from saving the reference of a new one and then linking from that reference. In the latter case we would still have the problem with object longevity, the second smell I mentioned earlier.
Making the API Intuitive and Fluent
The scope of the object is limited within the block, and once we return from the send() method, the reference is gone. We can also take advantage of chaining the flow method inside the block, without going outside the new keyword.
Dealing with Exceptions
CHAPTER 5
It's true that we can let the JVM handle it if we just use internal resources. Java provides a few options to properly clean up resources, but as we'll see in this chapter, none are as effective as what we can do with lambda expressions.
Cleaning Up Resources
We may have been led to believe that the JVM automates all garbage collection (GC). We'll use lambda expressions to implement the execute around method (EAM) pattern, which gives us better control over the sequencing of operations.1 Then we'll use this pattern to do even more: manage locks and write exception tests.
Peeking into the Problem
Closing the Resource
Ensuring Cleanup
Using ARM
We can see that the close() method was called as soon as we left the try block. If we're looking for a way to really ensure timely cleanup and avoid programmer errors, we need to look beyond ARM, as we'll do next.
Using Lambda Expressions to Clean Up Resources
Let's run the code and look at the peekaboo.txt file and the console for the code output. The code won't complain if we ignore this elegant construction; it will simply create an instance and call methods like writeStuff() outside of any try blocks.
Preparing the Class for Resource Cleanup
The instance we created when entering the try block is not accessible after the point we leave the block. The private constructor and private close methods are present, along with the public writeStuff() method.
Using Higher-Order Functions
The main action here is to use the instance inside the pran() method, but the create and cleanup operations surround this call nicely. Before we can exercise this code, let's take care of the last missing piece, the UseInstance interface.
Using the Design for Instance Cleanup
Long methods are bad, but long lambda expressions are bad - we would lose the benefit of code that is concise, easy to understand and simple to maintain. If we were interested in returning some result to the caller of the use() method, we would have to modify the signature of this method to set an appropriate return type, such as a generic R parameter.
Managing Locks
From our discussions so far in this chapter, we can see that lambda expressions and the execution around method pattern help quite a bit here. Let's turn to lambda expressions for help, and create a small class to manage the lock.
Creating Concise Exception Tests
When designing with lambda expressions, we need to ensure that the intent of the code and its consequences are clearly visible. Also, when creating lambda expressions that capture local state, we need to be aware of the limitations we discussed in Are There Limits on Lexical Scope?, on page 31.
Exception Test with try and catch
The methods are fairly concise and use the runLocked() static method from the Locker helper class we created (we need an import static Locker.runLocked for this code to compile). Let's look at one more advantage that execution around method pattern offers in unit testing with JUnit.
Exception Test Using Annotation
Using Lambda Expressions for Exception Tests
In the TestHelper we wrote a static assertThrows() method that expects an exception class and a code block to be executed. If no exception was thrown or if an exception other than the type specified in the first parameter was received, the JUnit fail() method call fails.
Exercising the Tests
CHAPTER 6
In Java 8, we don't have to; we can relax because lambda expressions make running our programs both lazy and fast. The tricks from this chapter can help make our programs run faster, make our code more concise, and make us look smarter.
Delayed Initialization
In this chapter, we start with a task to delay the creation of a heavy object, then turn some eager calculations into lazy evaluations. The design decision to postpone the creation of part of an object should not burden the users of the object; it must be seamless.
A Familiar Approach
Let's use this class to create an instance of HolderNaive and see if it defers the creation of the Heavy instance.
Providing Thread Safety
In fact, the possibility of a race condition is so short that it can only occur when the heavy reference is first set, and the synchronization approach is a rather cumbersome solution. We need thread safety until the reference is created, and after that free unhindered access to the reference.
Adding a Level of Indirection
The heavy field in this version is an instance of Supplier
Lazy Evaluations
Here we assume that Heavy itself is thread safe, and we focus only on Holder's thread safety. We have taken care of the race condition, but because the body has been made lazy, we no longer need to be so protective.
Starting with Eager Evaluation
If we run this code, we'll see that both evaluate() calls are made well before we enter the eagerEvaluator() method. Due to the cumulative delay from the calls to the evaluate() method, this would take at least four seconds.
Designing for Lazy Evaluation
This technique is quite useful when we need to evaluate a large number of methods or if method evaluations are time/resource intensive. This technique can significantly increase performance, but its drawback is that it burdens the caller with wrapping the calls in a lambda expression.
Leveraging the Laziness of Streams
Intermediate and Terminal Operations
The filter() method does not plow through all the elements in the collection in one shot. As soon as it finds an element, it passes it on to the next method in the chain.
Peeking into the Laziness
If the terminal operation is not satisfied, it will require a chain of operations to be performed on multiple items in the collection. From the output, we can clearly see that the intermediate operations delayed the real work until the last responsible moment when the terminal operation was called.
Creating Infinite, Lazy Collections
And even then, they did only the minimum work necessary to satisfy the terminal operation.
A Desperate Attempt
Let's dig a little deeper into the code to see if we can get anything out of it.
Reaching for the Stars
CHAPTER 7
In this chapter, we look at the tail-call optimization (TCO) technique to make recursion feasible for large inputs. Next, we'll look at problems that can be expressed using highly recursive overlapping solutions and explore how to create them very quickly using the memoization technique.
Using Tail-Call Optimization
It's very expressive - using recursion, we can provide a solution to a problem by applying the same solution to its subproblems, an approach known as divide and conquer. Unfortunately, problems that really benefit from recursion tend to be quite large, and a simple implementation will quickly result in a stackoverflow.
Starting with an Unoptimized Recursion
Ideally we'd like to rely on the compiler to provide such optimization, but since it doesn't, we can use lambda expressions to do this manually, as we'll see next.
Turning to Tail Recursion
Creating the TailCall Functional Interface
To create a lazy list of pending TailCall instances, we use the static iterate() method of the Stream interface. To ensure that the generator returns the next pending TailCall instance, it can use the apply() method of the current TailCall.
Creating the TailCalls Convenience Class
The technique we used in Creating Lazy Infinite Collections on page 115 will help us here to lazily produce the next waiting TailCall instance. In the done() method we return a specialized version of TailCall to indicate the end of the recursion.
Using the Tail-Recursive Function
IsComplete() of the specialized version reports the end of the recursion by returning a real value. Finally, the apply() method throws an exception because this method will never be called on this terminal implementation of TailCall, signaling the end of the recursion.
Cleaning Up the Recursion
We prevented the stack from blowing up, but the result was 0 due to an arithmetic overflow; the factorial result is a very large number. We ran the latest version with a small value and an absurdly large value; let's check the result.
Fixing the Arithmetic Overflow
The rest of the code is almost the same, using the TailCall interface, the TailCalls class, and the TCO technique. We see the exact value of the factorial for the number 5, and the truncated output value for the large input.
Speeding Up with Memoization
With only a few lines of code, we turned an unoptimized recursion into a tail recursion and staved off stack overflows, thanks to lambda expressions, functional interfaces, and infinite streams. With this technique at hand, we can confidently implement recursive solutions, with a minor redesign to turn them into tail calls.
An Optimization Problem
Plain-Vanilla Recursion
The output looks reasonable, but the calculation for this would take a long time, like 45 seconds, depending on the speed of the system. This is because the time complexity of this calculation is exponential - O(2n-1) - we are performing the calculations redundantly for different lengths.
Memoizing the Results
CHAPTER 8
If we combine OOP with the functional style, we can instead transform objects by passing lightweight objects through a series of coherent functions. We can use the functions, in addition to the objects, as components to program with.
Using Function Composition
We can see the difference between a pure OOP and a mixed OOP functional style in the following figure. We can change a few links in the chain and easily change the behavior along the way.
Using MapReduce
Let's visit the stock market to see which stocks on the list are valued at more than $100. The lack of variability reduces the possibility of errors and makes the code easier to parallelize.
Preparing the Computations
Takes a price value and returns a predicate that can be evaluated later to check if the given StockInfo instance is less than the price value cached in the lambda expression. Let's create an emergency version of the code to get the stock with the highest price in the offer.
Moving from the Imperative Style…
In that code, we see three different steps, from symbols to stocks, then to selected stocks, and finally to the highest price among the selected stocks. Furthermore, if we want to change the logic - e.g. if we want to select the highest priced stock under $1,000 - we need to change this code.
To the Functional Style
Let's take a minute and visualize the operations we performed in this example on the following image. In the figure, we see that the map operation uses a function to retrieve the stock prices for each element in the symbol collection.
Taking a Leap to Parallelize
Let's run the parallelized version of the code and look at the time it takes to run. Third, the correctness of the solution should not depend on the order of execution of the lambda expressions scheduled to run simultaneously.
Should We Choose Parallel Streams?
CHAPTER 9
We'll review the practices we need to hone in order to take full advantage of functional style, then discuss the performance impact of this style and conclude with some recommendations on how we can successfully adopt the functional style.
Essential Practices to Succeed with the Functional Style
More Declarative, Less Imperative
For example, let's say we have a list of stock prices and asked to select the maximum value. We have fewer chances to introduce bugs - the code we don't write has the least bugs.
Favor Immutability
Reduce Side Effects
On the other hand, calls to functions can be moved around without side effects and rearranged more freely. When working with lambda expressions, we need to ensure that the code is free of side effects.
Performance Concerns
We refactor this code into our preferred functional style: code that is declarative, built for immutability, has no side effects, and consists of higher-order functions chained together. Let's see how long this version takes to run on the same pool as the previous version.
Adopting the Functional Style
APPENDIX 1
Here we review the starter set - the interfaces we often encounter and need to be familiar with.
Consumer<T>
Supplier<T>
Predicate<T>
Function<T, R>
APPENDIX 2
Defining a Functional Interface
Creating No-Parameter Lambda Expressions
Creating a Single-Parameter Lambda Expression
This appendix is a quick reference to syntax using example code selected from various parts of the book. In some situations where the context is not sufficient to conclude, or we want better clarity, we can specify the type in front of the parameter names.
Inferring a Lambda Expression’s Parameter Type
Dropping Parentheses for a Single-Parameter Inferred Type
Creating a Multi-Parameter Lambda Expression
Calling a Method with Mixed Parameters
Storing a Lambda Expression
Creating a Multiline Lambda Expression
Returning a Lambda Expression
Returning a Lambda Expression from a Lambda Expression
Lexical Scoping in Closures
Passing a Method Reference of an Instance Method
Passing a Method Reference to a static Method
Passing a Method Reference to a Method on Another Instance
Passing a Reference of a Method That Takes Parameters
A lambda expression can be replaced with a method reference if it directly points to the first parameter as the target of the method call, and the remaining parameters as arguments to that method.
Using a Constructor Reference
Function Composition
Dependency inversion principle. http://c2.com/cgi/wiki?DependencyInversionPrinciple Describes a way to realize extensibility by associating a class with an abstraction (interface) rather than its implementation. The direct URL is https://oracleus.activeevents.com/connect/search.ww?event=javaone#loadSearch-event=javaone&searchPhrase=Goetz&searchType=session.