Chapter 8 - Java for Beginners Course

Lambdas

When we have interfaces that only define one abstract method, we call them functional interfaces. Our Filter<E> interface is an example of a functional interface:

public interface Filter<E> {
    boolean filter(E element);
}

In these cases, Java allows us to define implementations of these interfaces as what are called Lambda expressions. In contrast to anonymous classes, lambda expressions are much more succinct and convey the same functionality with less amount of code.

Let’s take a look at an example and implement our GreaterThanFiveFilter as a lambda expression.

In our previous section we saw that we could implement it as an anonymous class, like this:

//...
Filter<Integer> filterToUse = new Filter<Integer>() {
    @Override
    public boolean filter(Integer element) {
        if (element > 5) {
            return true;
        }
        return false;
    }
};

List<Integer> filtered = filterNumbers(numbers, filterToUse);
//...

Implementing our example filter as a lambda expression

We can achieve the same behaviour in a more succinct way. As our Filter<E> interface defines a single abstract method that needs to be implemented, we can define that method’s implementation as a lambda expression.

This code achieves the same result as the anonymous class above and as the GreaterThanFiveFilter examples:

In the example code run the JavaListWithLambdaFilterApp
// define the filter we want to use as a lambda expression
Filter<Integer> filterToUse = (Integer element) -> {
    if (element > 5) {
        return true;
    }
    return false;
};

// call our new `filterNumbers` static method to do the filtering
List<Integer> filtered = filterNumbers(numbers, filterToUse);

In our case, our filter method receives one parameter (the element to evaluate), and should return a boolean value, true if we want the element to be included, and false if we want to exclude it.

Syntax of a lambda expression

A lambda expression is conformed by 3 components:

  1. The parameters of the function: In our case, the (Integer element) part of the expression.

  2. The arrow token: Lambda expressions separate the parameters from their body using an arrow .

  3. The body of the lambda: In the example, our body is enclosed by the curly braces {…​}.

Java correctly converts our lambda expression into an implementation of our Filter<Integer> interface, as the lambda conforms to the specification of the filter method it defines.

Simplifying further

Now, the example above is a small improvement over our anonymous class case, but you could argue it isn’t a great benefit.

When using lambdas, the compiler can infer several things from context and we don’t need to provide all of the information we’re providing above. In the next code examples, we’ll start removing bits and pieces we don’t need from our lambda expression.

Parameter types

As the lambda expression must comply with the signature of the method in the interface, in our case boolean filter(Integer element), the compiler can infer the parameter types, so we can omit them:

Filter<Integer> filterToUse = (element) -> {
    if (element > 5) {
        return true;
    }
    return false;
};

Parameter list

When there is only one parameter, we don’t need to enclose it in parenthesis. Bear in mind though, that the parenthesis are mandatory when there are two or more parameters:

Filter<Integer> filterToUse = element -> {
    if (element > 5) {
        return true;
    }
    return false;
};

Reducing our body

This isn’t specific to lambda expressions, but is a step required for our example. This same change can be applied in the GreaterThanFiveFilter class and in our anonymous class example.

We could rewrite our method to be shorter and fit in a single return statement, like so:

Filter<Integer> filterToUse = element -> {
    return element > 5;
};

Single expression lambdas

When a lambda is expressed as a single expression, like in our case above element > 5, the braces and return statement aren’t required, which takes us to:

Filter<Integer> filterToUse = element -> element > 5;

Putting it all together

To clarify, all of the lambda expressions above are equivalent, but it’s good to understand the different shapes and forms they can take.

Now that we have a very simple lambda expression, let’s put it all together in our code:

//...
// define the filter we want to use as a lambda expression
Filter<Integer> filterToUse = element -> element > 5;

// call our new `filterNumbers` static method to do the filtering
List<Integer> filtered = filterNumbers(numbers, filterToUse);
//...

In this example, we’re defining the lambda expression in a separate line to where we are using it. However, this doesn’t need to be the case. In cases where readability isn’t compromised, it is common to find the lambda passed in directly as a method parameter, for example:

List<Integer> filtered = filterNumbers(numbers, element -> element > 5);

Now, our final example looks like:

In the example code run the JavaListWithSimplifiedLambdaFilterApp
public class JavaListWithSimplifiedLambdaFilterApp {

    public static void main(String[] args) {
        // our input list of numbers
        List<Integer> numbers = Arrays.asList(1, 2, 3, 10, 11, 12, 0);

        // call our new `filterNumbers` static method to do the filtering
        List<Integer> filtered = filterNumbers(numbers, element -> element > 5);

        // print out the filtered list
        for (Integer number : filtered) {
            System.out.println(number);
        }
    }

    private static List<Integer> filterNumbers(List<Integer> numbers, Filter<Integer> filterToUse) {
        List<Integer> filtered = new ArrayList<>();
        // filter the list using the provided `filterToUse`
        for (Integer number : numbers) {
            if (filterToUse.filter(number)) {
                filtered.add(number);
            }
        }
        return filtered;
    }
}

Output:

10
11
12

Method references

When using lambda expressions, you’ll find that in some cases you’ll have a single expression that calls a method, either in the same class or in a different one.

In these cases, Java allows the use of Method references to identify what the implementation of the lambda expression is. For example, assume we had a method in our example code to help us with the filtering, like so:

private static boolean greaterThanFive(Integer element) {
    return element > 5;
}

And we use it in our lambda expression:

List<Integer> filtered = filterNumbers(numbers, element -> greaterThanFive(element));

In this case, it is clearer to pass a reference to the greaterThanFive method directly, instead of defining a lambda expression. So our code could be written as follows:

In the example code run the JavaListWithMethodRefFilterApp
public class JavaListWithMethodRefFilterApp {
    public static void main(String[] args) {
        // our input list of numbers
        List<Integer> numbers = Arrays.asList(1, 2, 3, 10, 11, 12, 0);

        // call our new `filterNumbers` static method to do the filtering
        List<Integer> filtered = filterNumbers(numbers, JavaListWithMethodRefFilterApp::greaterThanFive);

        // print out the filtered list
        for (Integer number : filtered) {
            System.out.println(number);
        }
    }

    private static List<Integer> filterNumbers(List<Integer> numbers, Filter<Integer> filterToUse) {
        List<Integer> filtered = new ArrayList<>();
        // filter the list using the provided `filterToUse`
        for (Integer number : numbers) {
            if (filterToUse.filter(number)) {
                filtered.add(number);
            }
        }
        return filtered;
    }

    private static boolean greaterThanFive(Integer element) {
        return element > 5;
    }
}

In this example, the method reference is defined by JavaListWithMethodRefFilterApp::greaterThanFive. Method references are identified by the use of the double colon sign (::) and can be used to identify both static and instance methods.

As the signature of the greaterThanFive method complies with the expected signature of our filter method in terms of the return type and parameter types, Java allows this method to be used as an implementation of the Filter<Integer> functional interface.