λ-programming (lambda-programming) has finally been introduced in the Java world as of version 8. It is the feature that will mostly change the way Java developers program and a new ‘weapon’ against boilerplate code. Java 8 has mostly applied functional programming in the Collections API by introducing the new Stream API. Additionally, this new feature promises to offer us a painless step into the multicore world without the need to bother about threads, fork-joins and the like.
The integration of lambda features into a traditionally object-oriented programming language has been a challenge, but Oracle seems to have done a very good job providing a semi-functional-object-oriented language, to exaggerate a bit.
A number of books have already been published on Java 8 making the life of Java developers easier. However, only few books tackle only λ-programming in Java 8. Maurice’s book is one of them.
Maurice Naftalin is the author of another famous book “Java Generics and Collections” and maintains the lambda FAQ from which he has gained a lot of experience about the new λ-API (JSR-335). The result of this long experience has been this new book on Java 8 lambdas.
While other books on the topic go into describing the new API providing simple examples, Maurice’s book follows a more pragmatic way, describing “Best practices for Using Lambda expressions and Streams”.
The book tackles difficult topics and doesn’t provide simple examples to just demonstrate the API usage like other books do. The author tries to introduce the reader on the new way of thinking in a functional way by using his experience in solving complex problems.
In more detail:
In Chapter 1 Maurice lays the pillars for the reasoning about these new Java 8 features that will be analysed in the rest of his book:
- from internal to external iteration
- from collections to streams
- from sequential to parallel
He goes into the details of explaining the reasoning behind the design solutions and selected syntax of the above topics convincing the reader of how naturally these changes were introduced into the language.
Chapter 2 is devoted to lambda expressions. It compares lambdas to anonymous inner classes, he talks about variable capture (a.k.a. closures), and moves on to functional interfaces. The sub-chapters are as concise as they should be. He explains the difference between static, bound and unbound instance method references (do you know the difference?) and ends up with (function) type checking and the rules of overloading resolution (both with lambda expressions and method references).
So why e.g.
String::concat is unbound while
str::replace is bound? According to Maurice, bound method references are so-called because the method reference comes with the receiver already fixed. So
str::replace is equivalent to
(x,y) -> str.replace(x,y). You don’t get any choice about what the receiver is. It is bound to its
Unbound method references are undecided about the receiver until they are called. So
String::concat is equivalent to
(receiver,str) -> receiver.concat(str). It expects to get a receiver supplied as its first parameter.
Chapter 3 provides an introduction to streams by comparing them to pipelines. He describes how to start a stream (pipeline), how to transform it (e.g. filtering, mapping, sorting, truncating etc.) and how to end it (e.g. reduce, collect, search etc.). He touches parallelism and debugging. He provides useful and pragmatic examples.
Chapter 4 talks about how to end streams, i.e. about reductions and collections. He also goes into the pain of explaining how to write your own collector. Everything is explained with diagrams on how they work as well as with working examples.
Chapter 5 talks about how to create streams, i.e. sources and spliterators. Here he introduces the worked example of a recursive grep command and describes what errors one has to tackle to achieve it. Maurice, through-out his book, doesn’t offer the solution in your plate. Often, in his reasoning, you will find statements like the following: “Stop reading for a moment and think about the design of this data structure” or “If you have not already worked it out, stop now to write or outline the code”.
Chapter 6 tackles stream performance. He uses jmh to microbenchmark sequential and parallel streams, sorting, distinct, spliterators, collectors etc. Parallel streams aren’t always faster than sequential streams but they must satisfy some conditions to perform better than sequential streams.
Chapter 7, finally, talks about static and default methods in interfaces and on how default methods allow the API to evolve keeping backwards compatibility at the same time. He talks about where default methods are being used and compares interfaces with default methods to abstract classes.
He covers inheritance providing two easy to remember rules:
a) “instance methods are chosen in preference to default methods”
b) “if more than one competing default method is inherited by a class, the non-overridden default method is selected.”
This chapter contains the most complete coverage on the topic of default and static methods that I have seen.
Five reasons why you should buy this book:
- It is small and concise; personally, I have more chances to finish a book of 175 pages (and I did) than a book of 500 or 1000 pages.
- The author tries to get you into thinking as a functional programmer; he doesn’t offer you the solution in your plate.
- It is well structured and easy to find what you want
- It is a book that you will return to again and again
- It tackles performance issues and provides useful advice on performance pitfalls and anti-patterns.
“Expensive perfumes come in small bottles” they say. Small and to the point, all in all a very useful book on the subject, one that you ‘ll revisit again and again while programming with the new lambdas and stream APIs in Java 8. Maurice’s new book should be under the pillow of every Java developer that wants to learn about λ and streams in Java 8.