Page 2 of 2
Author: Robert Fischer
Audience: Intermediate Java programmers
Reviewer: Nikos Vaggalis
Chapter 4: I/O with Lambdas looks at Streams and lambdas from a working with files and exception handling perspective while Chapter 5: Data Access with Lambdas, looks at it from an accessing a database perspective. In both cases developing is simplified by decomposing the action of accessing the data store into more manageable and smaller steps. In the case of the database we have to map the result set from an SQL query into a stream of tab-separated value strings. All four ways of doing that are laid out one by one and in detail:
- Using Stream.Builder to construct a stream up front
- Using Stream.of and Stream.flatMap to construct the stream on demand
- Implementing the Stream built on Spliterators.AbstractSpliterator
- Mapping the ResultSet into an Iterator and then into a Spliterator and then into a Stream.
We find that the most optimal way is using the new Spliterator type, which is like an iterator but has further the ability to split itself into two, with some portion of the elements returned by the original spliterator and some portion of the elements returned by a new spliterator.Things like that make Java 8 look like more of a new language than an update.
It is explained that In Java 8, spliterators provide the elements that will become the stream elements and that all stream operations are actually operations done on the spliterators and the elements that come out of them. The last page of the chapter puts everything together and reveals how more shorter and more versatile the code has become in comparison to imperative programming approaches.
The database access example is further explored in Appendix A, (a rare case that an appendix could actually form part of the main material and narrative) where the same concept is re-worked and contrasted in three different ways, the imperative, the object-oriented, and the functional, illustrating the evolution towards the more powerful FP model.
In Chapter 6: Lambda Concurrency, we leave I/O and data bound streams behind and focus on the CPU bound ones that break a task into smaller ones running each one on a separate thread, thus taking advantage of the capabilities of modern multi-core processors.
The author ignores the usual restrictions on book space availability, by presenting another full code listing, two pages long, that goes through generating and printing an increasing number of prime numbers. The example starts single threaded, but constantly gets retouched to fit into multiple threads. At the most basic level, a lambda simplifies starting a thread by passing itself into the thread's class constructor, a space until now occupied by an inner class or another interface implementation. Thus lambdas enable you to express yourself in a straightforward way without having to go through the loops and hoops and side effects of treating everything as an object.
Executors, another new construct that has found it way into Java 8's core, are in essence are wrappers around the Thread class and are primarily used for when more fine-grained control over the thread pool is needed.
The most optimal and frequent way of going concurrent in Java 8 is through the Fork/Join pools where a task is created that can subsequently fork additional work and later on join that work back to the main task. The prime numbers example is reworked to reflect this, something that reduces the code we have to write to just 12-13 lines. The chapter closes with running the example under parallel streams. Two things have become evident as we move through the examples; the coherence of the content and its streamlined way of presentation, and that the code's size is getting shorter and shorter.
Chapter 7: Lambdas and Legacy Code, is more oriented towards Java language facilities, elaborating on the ways lambdas can be incorporated into legacy or pre Java 8 code but without breaking backwards compatibility.
One such attempt is to map the collections to streams and vice versa, and while the former is easily done with a call to the collection.stream() or collection.parallelStream(), the opposite is not.
Another piece of the book that could be read in isolation, and not in conjunction with the rest of the Java code presented in the chapter, is that of using a single lambda to cater for the definition of multiple methods. How can a single lamda call cater for calling all those methods?
The chapter closes by exhibiting how many keystrokes can be saved because of lamdas. This makes the code we write not only shorter but much more efficient :
(impl == null ? defaultImpl : impl).apply(arg);
This looks very Perl like and would confuse any Java programmer not yet up to date with the new developments (impland and defaultImpl are both Function values) of Java 8.
The final chapter, Chapter 8: Lambdas in Java Bytecode, offers a very educational run-down of how everything is represented under the hood of the JVM. It starts with a general overview of the bytecode's creation process, going through how a static or instance method is turned into a functional interface implementation via a method references, as well as how lambdas look at the lowest level of the JVM.
In the author's own words
the Java SDK has a method which will take in your lambda and return an instance of whatever type your lambda is targeting. It performs that binding just once using the invokedynamic bytecode instruction, and after the first execution, it is equivalent to instantiating the interface implementation directly
Wrapping up, I think that a title of "Java Lambdas and Streams" would fit the book much better, since it is those two topics that dominate.
There are two distinct approaches to reading this book. The first is suited to those who want to make an in-depth study and follow the code examples. For this the intermediate to advanced Java experience is necessary as despite, the clear commentary provided with the code the author moves swiftly from one example to the next.
Alternatively you can read it it without paying particular attention to the code, but instead focus on absorbing the principles and subtleties of FP, which are very well thought out by the author does a good job of distilling them for us. Even if it's not your intention to write Java code, the book makes you aware of the nature of functional programming so you can observe and shift those patterns into your language of choice, getting into a mind frame necessary for writing more compact, descriptive, succinct and easier to maintain code.
So despite not being a Java programmer, or intending to become one in the near future, I think that I've gained a lot by witnessing a static and purely OO programming language getting injected with a more smart way of thinking about the craft of programming.
Oh, and it reminded me, once more, to appreciate the things in Perl we take for granted...
To keep up with our coverage of books for programmers, follow @bookwatchiprog on Twitter or subscribe to I Programmer's Books RSS feed for each day's new addition to Book Watch and for new reviews.