Java Closures and Lambdas
Article Index
Java Closures and Lambdas
Chapters 4 - 8; Conclusion

Author: Robert Fischer
Publisher: Apress
Pages: 220
ISBN: 978-1430259985
Print: 1430259981
Kindle: B00DREFYTS
Audience: Intermediate Java programmers and those wanting to get to know functional programming through Java
Rating: 4
Reviewer: Nikos Vaggalis

A book that sets out to introduce programmers to the Functional Programming mindset looking through the eyes of Java. What will you get out of it?

Although the emphasis is on lambdas, in this book Robert Fischer places them into the wider perspective of Java 8 which revolutionizes Java, adjusting its foundations by shifting from the pure OOP model to incorporate the functional one as well. Lambdas of course lie at the epicenter of that revolution, responsible for the move from a conservative Java to a more succinct, versatile and performant programming paradigm. 


This might be uncharted territory for Java, but not so for other languages, such as Perl. The author himself is a veteran Perl programmer who had long ago discovered Perl's functional programming (FP). His book is therefore a testimony to Perl's clairvoyance and innovative thinking that others re-discover many years, even decades, later. And that is the book's aim, to familiarize the Java programmer with this kind of mindset.

It all begins, in Chapter 1: Java 8: It's a Whole New Java, with a classic example and all too common scenario of having to iterate over a collection, and on the way filter its elements based on some condition. In this case we just want to keep the elements that are not null. The example compares the "old", "outdated" and fragmented pre-version 8 Java ways for going about it, to the new way using lambdas. It is just the first breadcrumb of the story, with many more to follow. Putting all of them together reveals the complete picture of what FP is about.

It's not just about lambdas, lambdas are the means towards that end.

Little summaries comparing OOP's to FP's view of the world, doen't take long to make there first appearance :

Whereas an object-oriented paradigm thinks about objects, and objects have behaviours, functional programming thinks in terms of verbs (functions), which act on nouns (arguments).

Whereas object-oriented programming builds up a mechanism of interacting objects, functional programming builds up a function that is resolved through composite functions.

Whereas the object-oriented subordinates may contain their own state, mathematical functions are stateless: they will always return the same value and have no side effects.

A mathematical function is not so much a behaviour as a statement of eternal truth: "f(2)"does not mean “apply 2 to f”, but rather “the value when f’s argument is bound to 2”. In functional programming, the goal is to have all the functions in the application behave like these mathematical functions. This function may be defined in terms of other functions, but the program is ultimately a single function application, not a series of executive commands.

 

The theory is illustrated with short code snippets that give an introduction to lambdas' applications, contrasting their newly found capabilities to their OOP counterpart's shortcomings.

For example, storing a loop into a variable, and then passing it around, executing it later at some faraway location:

// This is not Java code as 
// it wasn't possible till now
Loop printObjects = for(Object it : list) {
     System.out.println(it);
}
//and real Java 8 code 
Consumer printObjects = list -> {
      for(Object it : list) {
          System.out.println(it);
      }
 };
//or the same in just one line
Consumer printObjects = list ->
list.forEach(System.out::println);

In the chapters that follow, nothing is left to the imagination as everything is clearly detailed and analyzed both from conceptual and technical points of view.

Chapter 2: Understanding Lambdas in Java 8, is probably the central pillar of the whole book. It provides the essentials that need to be understood for making sense of the rest of the book. It lays out the syntax and the ways lambdas can simplify and substitute or enhance legacy code.

In Java, a lambda implements the Function type, a new interface which represents a single-argument lambda with a return value. The returned Function takes an object of variable type V and provides an object of the same type, so the type is <V, V>:

public static  Function<V, V>
identityFunction() {
	return value -> value;
}

Of course, a different function might take one type and return another. Until now, one way to satisfy an interface's requirements was to implement it with an anonymous inner class:

public static  Function<V, V>
identityFunctionAIC() {
	return new Function<V, V>() {
		@Override
			public V apply(V value) {
			return value;
		}
	};
}

Now, however, the same can be done by using a lambda in its place.

Also in defining "closure", Fischer explains that the terms closure and lambda are used interchangeably in the Java world; not so in other languages, however. Afterwards we move onto the multi-argument form of the lambdas through the introduction of the BiFunction interface, used as in:

BiFunction<String, String, String> 
concat = (a, b) -> a + b;

This is further elaborated by applying BiFunction in the Partial Function Application and Currying example. Afterwards, it's all about Lambdas in all shapes and kinds:

  • Lambdas with Complex Bodies
  • Lambdas with Explicit Typing
  • Lambdas as Operators
  • Lambdas as Predicates
  • Lambdas with Primitive Arguments
  • Making Methods into Lambdas
  • Lambdas as Interface Implementations
  • Lambda Best Practices

If you thought that this list is exhaustive and fully covers the subject, think again as there are six more chapters to follow...

 

 



Chapter 3: Lambda’s Domain: Collections, Maps, and Streams, is where we meet, for the first time, a fully fledged code listing and not just disjointed code snippets as been happening so far, since the chapter is dedicated to the real world applications of lambdas. With the introduction to lambdas now out of the way, the conditions are expedient for introducing functional Streams as a better replacement for Collections. Streams' main advantage over Collections is that there is no need for having all the data of the collection upfront, something that frees the runtime in performing optimizations that result in enhanced performance.

Before tackling Streams though, we get a chance of watching lambdas performing on Collections, with a full code listing of a Library that stores a List, List<Book>, and a Map, Map<Book,String>, of books that we somehow need to manipulate. The accompanying discussion revolves around iterators, the deprecation of the for operator and its replacement by the post functional foreach operator.

Another example of manipulating Collections, is that of filtering with the removeif filter that takes a predicate and runs it against each element of the collection, removing all those elements that test true. As the filter acts on all the elements it removes the need to use an iterator, resulting in much cleaner and more descriptive code.

On the same wavelength, we need to apply a transformation and not just a filter when some criteria is true. We do that with utility function applyif that takes a predicate and a function and applies the function only when the predicate is true.

All of the material presented so far was designed to ready you to tackle Streams, now morphed into the functional and boosted counterpart of the imperative iterator.

Why boosted?
Because it is thread safe, can be split and run in parallel, can be transformed, filtered and plugged into legacy code. Therefore the various ways of generating streams are briefly laid out, drawing parallels with I/O streams. The operations that can be performed on them are broken down into intermediate and terminal, both of them playing distinct roles in the stream's life cycle :

1.creation
2.intermediate steps aka mapping and filtering
3.terminal step aka collecting,processing or reducing

You can chain any number of intermediate step before the stream reaches terminal at which point the actual processing starts.
This allows for pulling off code like the following:

Stream genres = library.getBooks().
parallelStream().
map(Book::getGenre);

 

That single line of code, generates a stream of the library’s books, that are easily mapped onto a stream of genres. It is further explained that map produces output by applying a function to each element of the stream, while filtering occurs when performing a test on each element.

After mixing and matching any number of intermediate mapping and filtering steps, as the code example above illustrates, it's time to execute the stream through the final termination step (you only get one such step), in one of the following ways:

  • collecting elements
  • generating side effects
  • performing calculations on elements

This nicely wraps up the chapter, once more highlighting the advantage of the stream's ability to abstract away the processing domain.

Banner

 



Last Updated ( Friday, 10 June 2016 )