Core Java

Introduction to Java lambdas

The 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.

writeToFile("todo.txt", new FileWriteFunction() {
    @Override
    public void apply(Writer file) throws IOException {
        file.write("learn about lambdas\n");
        file.write("learn stream API\n");
    }
})

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.

writeToFile("todo.txt", file -> {
    file.write("learn about lambdas\n");
    file.write("learn stream API\n");
})

That’s better. It emphasizes the code that matters and hides most of the unwieldy parts.

Often lambdas are used interchangeably with closures (i.e. lexical functions). 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.

void save(String data) {
    writeToFile("file.db", file -> file.write(data) );
}

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.

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.

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.

asList(1,2,3,4,5).stream()
    .reduce(0, (acc, value) -> acc + value) // => 15

This reduces the sequence by adding each value to an accumulator, starting at zero. For comparison, classically you’d write a loop.

int acc = 0;
for (int n : asList(1,2,3,4,5))
    acc += n;
acc // => 15

Moving on to sum only odd numbers. First we filter odd numbers, then we reduce.

asList(1,2,3,4,5).stream()
    .filter(Predicates::odd)
    .reduce(0, (acc, n) -> acc + n) // => 9

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.

List<Inch> inches = centimeters.stream()
    .map(Centimeter::toInches)
    .collect(Collectors.toList())

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.
 

Reference: Introduction to Java lambdas from our JCG partner Bart Bakker at the Software Craft blog.

Bart Bakker

Bart is a technologist who specializes in agile software development. He is passionate about creating working software that is easy to change and to maintain.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button