This week we will take a look at lambda expressions. We will take a look at some basics, show some of the standard functional interfaces, show how lambdas can be used with Streams and at the end it is shown how method references can be used. After reading this, you will have some basic knowledge of lambda expressions. The examples can be found at GitHub in the repository https://github.com/mydeveloperplanet/mylambdaplanet

Functional interface

First, let’s introduce the concept functional interface. A functional interface is an interface which has one abstract method. It may contain one or more default or static methods. Because a functional interface has exactly one abstract method, you could omit the name of the method when you implement it.

Lambda basics

Let’s take a look at an example and how we would implement an interface by means of an anonymous class.

We have a Car business object with some properties.

public class Car {

  private String brand;

  private String type;

  private int buildYear;

  public String getBrand() {
    return brand;
  }

  public void setBrand(String brand) {
    this.brand = brand;
  }

  public String getType() {
    return type;
  }

  public void setType(String type) {
    this.type = type;
  }

  public int getBuildYear() {
    return buildYear;
  }

  public void setBuildYear(int buildYear) {
    this.buildYear = buildYear;
  }
}

We have a ValidateCar interface which has one validate method which must be implemented.

public interface ValidateCar {

  boolean validate(Car car);

}

We have a CarUtilities class which has a filterCars method which takes a list of cars to filter, a condition to filter on and the method returns a list of filtered cars.

public class CarUtilities {

  /**
   * Filters a list of cars, based on the given condition
   * @param cars The list of cars
   * @param validateCar The condition to filter
   * @return The filtered list of cars
   */
  public List<Car> filterCars(List<Car> cars, ValidateCar validateCar) {
    List<Car> filteredCars = new ArrayList<Car>();

    for(Car car : cars) {
      if (validateCar.validate(car)) {
        filteredCars.add(car);
      }
    }

    return filteredCars;
  }

}

In order to filter a list of cars based on the brand BMW, we can write the following, where cars is a list of Car objects. We create an anonymous class and implement the validate method in order that only the cars with brand BMW are preserved.

CarUtilities carUtilities = new CarUtilities();
List<Car> filteredCars = carUtilities.filterCars(cars, new ValidateCar() {
        @Override
        public boolean validate(Car car) {
          return "BMW".equals(car.getBrand());
        }
 });

The validate method is a functional interface according to the definition. This means that we can use a lambda expression in order to shorten this.

The first way is rewriting it completely as a lambda expressionCar car represents the input parameter, the arrow indicates that it is a lambda expression, between brackets the implementation.

List<Car> filteredCars = carUtilities.filterCars(cars, (Car car) -> { return "BMW".equals(car.getBrand()); });

We can also omit the class Car in the lambda expression, it is already known which type has to be used.

List<Car> filteredCars = carUtilities.filterCars(cars, car -> { return "BMW".equals(car.getBrand()); });

Even shorter, without the brackets gives us the following statement.

List<Car> filteredCars = carUtilities.filterCars(cars, car -> "BMW".equals(car.getBrand()));

Standard Functional Interfaces

The ValidateCar interface is a quite basic interface which often will be created. The JDK has defined several basic interfaces in the package java.util.function. In the next section, some examples using these standard interfaces are shown.

Predicate<T>

In our example, we can replace the ValidateCar interface with the Predicate<T> interface.

We create a method filterCarsWithPredicate into the CarUtilities class equal to the method filterCars but using Predicate. The method to implement is the test methhod.

public List<Car> filterCarsWithPredicate(List<Car> cars, Predicate<Car> validateCar) {
  List<Car> filteredCars = new ArrayList<Car>();

  for(Car car : cars) {
    if (validateCar.test(car)) {
      filteredCars.add(car);
    }
  }

  return filteredCars;
}

Using this method changes nothing to the last example of the Lambda basics paragraph. We just do not need the ValidateCars interface anymore.

List<Car> filteredCars = carUtilities.filterCarsWithPredicate(cars, car -> "BMW".equals(car.getBrand()));

Consumer<T>

Another standard Functional Interface is the Consumer interface which has an accept method which takes an object argument.

We add the method doSomethingWithCars to the CarUtilities class.

public void doSomethingWithCars(List<Car> cars, Consumer<Car> consumer) {

  for(Car car : cars) {
    consumer.accept(car);
  }

}

Following, we need to be able to perform an action on the Car object. In order to test this, we add a printCar method to the Car object which prints the brand of the Car.

public void printCar() {
  System.out.println("Car is " + this.getBrand());
}

In order to test this, we invoke the doSomethingWithCars method of the CarUtilities class. The first argument is the list of cars, the second argument is the lambda expression which implements the Consumer accept method. We tell to invoke the printCar method of the Car object. Running this example prints the brands of the Cars which are present in the cars list.

CarUtilities carUtilities = new CarUtilities();
carUtilities.doSomethingWithCars(cars, car -> car.printCar());

Function<T,R>

The Function interface has an apply method wich takes an argument and returns a value of type R.

We add the doSomethingWithCars method to the CarUtilities class. As you can see, the input Function argument will be applied to a Car object and retrieves a String value. This String value will be used to perform an action on it via the Consumer interface as previous described.

public void doSomethingWithCars(List<Car> cars, Function<Car, String> function, Consumer<String> consumer) {

  for(Car car : cars) {
    String brand = function.apply(car);
    consumer.accept(brand);
  }

}

In order to test this, we invoke the doSomethingWithCars method of the CarUtilities class. The first argument is again the list of cars. The second argument shows us that we want to retrieve the brand of the car, and with the third argument we want to print the brand.

CarUtilities carUtilities = new CarUtilities();
carUtilities.doSomethingWithCars(cars, car -> car.getBrand(), brand -> System.out.println(brand));

Executing this, will print the brands of the cars in the list, as expected.

Streams

The last example can be rewritten by using a stream of the collection cars. We are able to chain operations invoked on the stream (aggregate operations). The last example is written as follows:

cars.stream().map(car -> car.getBrand()).forEach(brand -> System.out.println(brand));

The map method takes the Function interface, returns the brands and the forEach method tells us what to do with the brands by using the Consumer interface.

Let’s take a look again at our first example. We reduced the syntax to the following:

List<Car> filteredCars = carUtilities.filterCars(cars, car -> "BMW".equals(car.getBrand()));

With the knowledge we gained now, and by using a Stream and its filtering capabilities, we do not need the CarUtilities class at all. We can rewrite it as follows:

List<Car> filteredCars = cars.stream().filter(car -> "BMW".equals(car.getBrand())).collect(Collectors.toList());

We take the cars list as a Stream, we filter the Stream by using a Predicate, and then collect the results as a List. We have now rewritten multiple lines of code within one line of code without losing readability. Moreover, it is more readable then before!

Method references

Method references can be used when a lambda expression only invokes a method. Lambda expressions are even more readable by using method references.

Let’s take a look at the first example we used with streams.

cars.stream().map(car -> car.getBrand()).forEach(brand -> System.out.println(brand));

By using method references, we can rewrite this as follows:

cars.stream().map(car -> car.getBrand()).forEach(System.out::println);

We notice here that the invocation of the method is preceded by :: and that the brackets after the method are left out.

We can apply the same concept to the example with the Consumer interface. It was:

carUtilities.doSomethingWithCars(cars, car -> car.printCar());

And with method references, it becomes:

carUtilities.doSomethingWithCars(cars, Car::printCar);

Summary

In this blog we have taken a look at what lambda expressions are, how to use them with standard functional interfaces, how to use them with Streams and we discussed method references. It’s now up to you to use them in your daily coding. Enjoy!