Introduction to Java Lambdas
October 26th, 2013The main theme of Java 8 is lambdas. I have noticed that for many Java programmers lambdas are pretty tough material. So let’s try to get a basic understanding of them.
First of all, what exactly is a lambda? A lambda is an anonymous function that is, unlike a regular function, not bound to an identifier (i.e. it has no name). These functions can be passed as arguments to other functions (known as higher-order functions).
Suppose our application must write to a bunch of files from different places of the system. We don’t want to handle the checked exceptions every time [see Exceptions: checked and unchecked for more about checked exceptions]. So we decide to write a low-level writeToFile
function that opens a FileWriter
and passes it to a function that can then safely write to the file.
Using this low-level function we write the following code.
The object we pass into writeToFile
is an anonymous implementation of FileWriteFunction
[anonymous because we didn’t name it as a class]. It has a single function making this effectively passing an anonymous function. In the world of Java these are sometimes referred to as callbacks. Probably you’ve used this at least a few times before, maybe without taking notice.
This anonymous object is effectively a lambda. But obviously this doesn’t look like passing a function. The syntax is very unwieldy. And this is exactly what changes in Java 8.
With the syntactic support for lambdas in Java 8 the code reads like we pass a function. Using a Java 8 lambda we rewrite the above code as follows.
That’s better. It emphasizes the code that matters and hides most of the unwieldy parts.
Closures
Often lambdas are used interchangeably with lexical functions (closures). While they are both anonymous functions, the definition of a closure is that it is a function containing bound variables. I.e. the closure includes a referencing table that contains references to local variables.
For example, if we accept a parameter data
that we want to write to a file, we use a closure.
While anonymous inner-classes restrict access to final variables, closures provide access to any variable. However, the variable is effectively final to the closure, so it cannot be reassigned.
Compilation
What about compilation of lambdas? Does Java 8 only provide a spoonful of syntactic sugars upon anonymous inner-classes with only one method?
Not really, no. It’s true that it allows the lambda syntax for any single-method anonymous inner-class. But lambdas are not compiled into inner-classes. Instead the compiler outputs lambda$
methods in the defining class and uses invokedynamic
to dispatch the call.
So now you know how to use a lambda in Java 8. While by themselves lambdas are pretty useful, they are even more so when applying them to collections.
Stream API
The new Stream API provides an alternative to iterators by offering a more functional API to collections: java.util.stream.Stream
. The most noteworthy functions on a Stream
are: collect
, filter
, map
, and reduce
.
To start with a simple example, here’s how to sum all the numbers in a list.
This reduces the sequence by adding each value to an accumulator, starting at zero. For comparison, classically you’d write a loop.
Moving on to sum only odd numbers. First we filter
odd numbers, then we reduce
.
The argument to filter
is a function reference to a static odd
function in a Predicates
class I used. It is a boolean function that, as the name suggests, tests if a number is odd.
So far, so good. Now suppose we want to transform a list of centimeter sizes to their equivalent size in inches. We use map
for this.
The centimeters are mapped to inches by applying the toInches
function to every item in the centimeters
collection.
By its nature, Stream
is continuous. It is used to describe the operations to apply on a collection. But to acquire the results the data must be collected. This is what the collect
function is for. It reduces the elements of the stream to a mutable container [e.g. a list].
Using the Stream API and lambdas can greatly simplify the code you use to work with collections, and make the code much more expressive. Preferring using non-destructive operations (e.g. map
, filter
) over destructive operations (e.g. forEach
) makes the code more easy to reason about.
That’s it! These are the basics you need to know about lambdas (and closures) in Java 8. Of course there’s much more to write about lambdas, but that’s something for another post.