Skip to content

4. Java 8 Lambda Expressions

4.1. Example-01 - Functional Interfaces and Lambdas

  

Consider the following code:


package dvp.java8.lambdas;

public class Example01 {
  public static void main(String[] args) {

    // anonymous classes
    I1 ia1 = new I1() {
      @Override
      public void doSomething() {
        System.out.println("ia1.doSomething");
      }
    };

    I2 ia2 = new I2() {
      @Override
      public String getSomething(double value) {
        return String.format("ia2.getSomething(%s)", value);
      }
    };

    // lambdas
    I1 ib1 = () -> System.out.println("ib1.lambda");
    I2 ib2 = (value) -> String.format("ib2.lambda(%s)", value);
    I1 ib3 = () -> {
      System.out.println("ib3.lambda");
    };
    I2 ib4 = (double value) -> {
      return String.format("ib4.lambda(%s)", value);
    };

    // apply
    ia1.doSomething();
    System.out.println(ia2.getSomething(4.3));
    ib1.doSomething();
    System.out.println(ib2.getSomething(5.8));
    ib3.doSomething();
    System.out.println(ib4.getSomething(10.1));
  }
}

@FunctionalInterface
interface I1 {
  void doSomething();
}

@FunctionalInterface
interface I2 {
  String getSomething(double value);
}
  • lines 41–44: define a functional interface I1. A functional interface is an interface with only one method. It is not dependent on the presence of the [@FunctionalInterface] annotation on line 41, which is optional;
  • lines 46–49: a second functional interface I2;
  • lines 6–19: interfaces I1 and I2 are implemented using anonymous classes, the most common solution before the introduction of lambda functions;
  • lines 21–29: interfaces I1 and I2 are implemented using lambda functions;
  • lines 7–12: implementation of interface I1 with an anonymous class. The syntax for implementing an interface I with an anonymous class is as follows:
I i = new I() {
    @Override
    public T1 m1(...){
...
    }
    public T2 m2(...){
...
    }
}

where m1, m2, ... are the methods defined by the interface I.

  • lines 14–19: implementation of interface I2 with an anonymous class;
  • line 22: implementation of interface I1 with a lambda function. Here, we take advantage of the fact that the functional interface has only one method. The lambda function then implements this single method M. Its syntax is as follows:
(T1 param1, T2 param2, ...) -> {implementation of method M;}

The types T1, T2, Tn can be omitted if the compiler can infer them from the context (type inference).

  • line 22: implementation of the method [I1.doSomething] with signature:

void doSomething();

[doSomething] is a method that has no parameters and returns no result. Its lambda implementation can be written as in line 22 or as in lines 24–26, i.e., curly braces can be placed around the lambda function’s code. If this code contains only a single statement, as here, these curly braces can be omitted;

  • Line 23: Implementation of the [I1.getSomething] method with the following signature:

String getSomething(double value);

[getSomething] takes a parameter of type [double] and returns a result of type [String]. Its lambda implementation can be the one in line 23 or the one in lines 27–29. In the implementation in line 23:

  • the type of the [value] parameter is omitted. The [double] type found in the signature of [getSomething] will then be used;
  • the lambda code is not enclosed in parentheses. The result of the lambda is then the value of the single expression in that code, here: String.format("ib2.lambda(%s)", value);

In the implementation of lines 27–29:

  • the type of the [value] parameter is explicitly declared;
  • we use a [return] to return the result of the lambda. In this case, curly braces must be used;
  • Lines 32–37: we call the various anonymous functions and lambdas;

The result obtained is as follows:

1
2
3
4
5
6
ia1.doSomething
ia2.getSomething(4.3)
ib1.lambda
ib2.lambda(5.8)
ib3.lambda
ib4.lambda(10.1)

4.2. Example-02 - The Predicate<T> functional interface

  

Most of the time, we deal with functional interfaces from libraries rather than functional interfaces that we define ourselves. Here, we are interested in the functional interface [Predicate] defined in the [java.util.function] package, which contains most of the functional interfaces in Java 8. It is defined as follows:

Image

We mentioned that a functional interface has only one method. Here, however, there are four. Another innovation introduced by Java 8 was the concept of a default method in an interface, marked by the [default] keyword. Here we have three such methods. These methods have the particularity of having a default implementation. There is therefore no requirement for a class implementing an interface with default methods to implement them. Thus, a class wishing to implement the [Predicate] interface has only one method it must implement: the [test] method. The [Predicate] interface is therefore indeed a functional interface. We can thus say that a functional interface is an interface whose implementation contains only one mandatory method. If it has more than one method, the others must have the [default] keyword.

The [Predicate<T>.test] method takes a parameter of type T and returns a boolean. This interface is generally used to filter collections. To illustrate its use, we will use the following data:


package dvp.data;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class Person {

  public enum Gender {MALE, FEMALE};

  // data
  private String name;
  private int age;
  private double weight;
  private Gender gender;

  // constructors
  public Person() {

  }

  public Person(String name, Sex gender, int age, double weight) {
    this.name = name;
    this.gender = gender;
    this.age = age;
    this.weight = weight;
  }

  // getters and setters
...

  // toString
  @Override
  public String toString(){
    try {
      return new ObjectMapper().writeValueAsString(this);
    } catch (JsonProcessingException e) {
      return e.getMessage();
    }
  }
}
  • lines 32–38: the [toString] method returns the person’s JSON string;

The [People] class defines a list of 3 people:


package dvp.data;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.Arrays;
import java.util.List;

public class People {
  private static List<Person> people = Arrays.asList(new Person("jean", Person.Gender.MALE, 20, 70),
    new Person("marie", Person.Gender.FEMALE, 10, 30), new Person("camille", Person.Gender.FEMALE, 30, 55));

  public static List<Person> get() {
    return people;
  }

  public static String toString(List<Person> list) {
    try {
      return new ObjectMapper().writeValueAsString(list);
    } catch (JsonProcessingException e) {
      return e.getMessage();
    }
  }
}
  • lines 10–11: the list of 3 people;
  • lines 13–15: a static method to retrieve this list;
  • lines 17–23: a static method to obtain the JSON string of a list of people passed as a parameter;

This data will be used by the following code:


package dvp.java8.lambdas;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

import dvp.data.Person;
import dvp.data.People;

public class Example02 {
  public static void main(String[] args) {
    // predicate implemented by an anonymous class
    Predicate<Person> filterWeight = new Predicate<Person>() {
      @Override
      public boolean test(Person person) {
        return person.getWeight() < 50;
      }
    };
 // predicate implemented by a lambda
    Predicate<Person> filterAge = p -> p.getAge() < 28;
    // list of people
    List<Person> people = People.get();
    // output
    System.out.println(People.toString(filterPeople(people, filterAge)));
    System.out.println(People.toString(filterPeople(people, filterAge)));
  }

  private static List<Person> filterPeople(List<Person> people, Predicate<Person> filter) {
      // [filter] filters the [people] list
    List<Person> filteredPeople = new ArrayList<>();
    for (Person p : people) {
      if (filter.test(p)) {
        filteredPeople.add(p);
      }
    }
    return filteredPeople;  }
}
  • lines 13–18: implementation of the Predicate<Person> interface using an anonymous class. This is a filter based on the person’s weight;
  • line 20: implementation of the Predicate<Person> interface using a lambda function. This is a filter based on the person's age. Based on what has been said, it could also have been written as follows:

        Predicate<Person> filterAge = (Person p) -> {
            return (p.getAge() < 28);
        };

but the version in line 20 is more concise. The type of the parameter p is inferred from the context. Here, we are constructing a type [Predicate<Person>]. The implemented method then has the signature [boolean test(Person param)]. Therefore, the implicit type of p in line 20 is the type [Person];

  • line 22: we retrieve a predefined list of people;
  • line 24: we filter them by age;
  • line 25: we filter them by weight. In both cases, we display the JSON string of the filtered list;
  • lines 28–37: a static method that
    • takes as parameters: a list of people to filter and the filter. The filter is an instance of the [Predicate<Person>] interface. For convenience, we refer here and elsewhere in the document to an instance of an interface I as an instance of a class C that implements I;
    • returns the filtered list as the result;
  • line 32: we use the [test] method of the [Predicate] interface. Depending on the filter passed to the method, the [test] method will be:

return person.getWeight() < 50;

or


return p.getAge() < 28

Executing the [Exemple02] class yields the following result:

[{"name":"jean","age":20,"weight":70.0,"gender":"MALE"},{"name":"marie","age":10,"weight":30.0,"gender":"FEMALE"}]
[{"name":"marie","age":10,"weight":30.0,"gender":"FEMALE"}]

4.3. Example-03 - The Functional Interface Function<T,R>

  

The functional interface Function<T,R> is defined as follows:

Image

The interface’s only method has the signature R apply(T t). It is generally used to create a new Collection<R> type from a Collection<T> type. To illustrate this interface, we will use the following code:


package dvp.java8.lambdas;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import dvp.data.Person;
import dvp.data.People;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

public class Example03 {
  public static void main(String[] args) throws JsonProcessingException {
    // implementation using an anonymous class
    Function<Person, String> mapToName = new Function<Person, String>() {
      @Override
      public String apply(Person person) {
        return person.getName();
      }
    };
    // implementation with lambda
    Function<Person, Integer> mapToAge = p -> p.getAge();
    // list of people
    List<Person> people = People.get();
    // JSON
    ObjectMapper jsonMapper = new ObjectMapper();
    // output
    System.out.println(jsonMapper.writeValueAsString(mapPeople(people, mapToName)));
    System.out.println(jsonMapper.writeValueAsString(mapPeople(people, mapToAge)));
  }

  // transformation List<Person> --> List<T>
  private static <T> List<T> mapPeople(List<Person> people, Function<Person, T> mapper) {
    List<T> maps = new ArrayList<>();
    for (Person p : people) {
      maps.add(mapper.apply(p));
    }
    return maps;
  }
}
  • lines 15–20: we implement the interface [Function<Person, String>] with an anonymous class that performs the Person → String transformation;
  • line 22: we implement the interface [Function<Person, Integer>] with a lambda function that performs the Person --> Integer transformation;
  • Lines 33–39: a static method that
    • takes two parameters: the first, of type List<Person>, is a list of people to be transformed. The second, of type Function<Person, T>, is a function that takes each person from the list and creates an object of type T;
    • returns a List<T> where each element results from the Person -> T transformation;
  • lines 35–37: the Person -> T transformation is applied. If the method’s second parameter is the [mapToName] object, a Person -> String transformation is performed. If it is the [mapToAge] object, a Person -> Integer transformation is performed;

The result is as follows:

["jean","marie","camille"]
[20,10,30]

4.4. Example-04 - The Consumer<T> functional interface

  

The functional interface Consumer<T> is defined as follows:

Image

The interface’s only method has the signature: void accept(T t). This method processes (consumes) its parameter and returns no result. To illustrate this, we will use the following code:


package dvp.java8.lambdas;

import java.util.List;
import java.util.function.Consumer;

import dvp.data.Person;
import dvp.data.People;

public class Example04 {
    public static void main(String[] args) {
        // list of people
        List<Person> people = People.get();
        // anonymous implementation
        Consumer<Person> consumerAge = new Consumer<Person>() {
            @Override
            public void accept(Person person) {
                System.out.printf("age of %s = %s%n", person.getName(), person.getAge());
            }
        };
        // lambda implementation
        Consumer<Person> consumerWeight = p -> System.out.printf(" weight of %s = %s%n", p.getName(), p.getWeight());        
        // output
        for (Person p : people) {
            consumerAge.accept(p);
        }
        System.out.println("--------");
        for (Person p : people) {
            consumerWeight.accept(p);
        }
    }
}
  • lines 14–19: the interface [Consumer<Person>] is implemented by an anonymous class whose [accept] method displays the person’s name and age;
  • line 21: the interface [Consumer<Person>] is implemented by a lambda function whose implicit [accept] method displays the person's name and weight;
  • lines 23–25: the list of people is consumed by the implementation [consumerAge];
  • lines 27–29: the list of people is consumed by the [consumerWeight] implementation;

The results are as follows:

1
2
3
4
5
6
7
 Jean's age = 20
 Marie's age = 10
 Camille's age = 30
--------
 Jean's weight = 70.0
 Marie's weight = 30.0
Camille's weight = 55.0

4.5. Example-05 - The BiConsumer<T,U> functional interface

  

The functional interface BiConsumer<T,U> is defined as follows:

Image

The interface’s only method has the signature: void accept(T t, U u). It consumes type T with the additional information U u. We will illustrate its use with the following code:


package dvp.java8.lambdas;

import dvp.data.Person;
import dvp.data.People;

import java.util.List;
import java.util.function.BiConsumer;

public class Example05 {
  public static void main(String[] args) {
    // list of people
    List<Person> people = People.get();
    // anonymous implementation
    BiConsumer<Person, Integer> biconsumerAge = new BiConsumer<Person, Integer>() {
      @Override
      public void accept(Person person, Integer integer) {
        person.setAge(person.getAge() + integer);
        System.out.printf("%s's age = %s%n", person.getName(), person.getAge());
      }
    };
    // lambda implementation
    BiConsumer<Person, Integer> biconsumerWeight = (p, i) -> {
      p.setWeight(p.getWeight() + i);
      System.out.printf("weight of %s = %s%n", p.getName(), p.getWeight());
    };
    // display
    for (Person p : people) {
      biconsumerAge.accept(p, 100);
    }
    System.out.println("--------");
    for (Person p : people) {
      biconsumerWeight.accept(p, 200);
    }
  }
}
  • lines 14–20: implementation of the BiConsumer<T,U> interface using an anonymous class. The [apply] method uses its second parameter to update the age of the person passed as the first parameter. It then displays the result;
  • lines 22–25: implementation of the BiConsumer<T,U> interface using a lambda function. The implicit [apply] method uses its second parameter to update the weight of the person passed as the first parameter. It then displays the result;
  • lines 27–29: the list of people is consumed using the [biconsumerAge] implementation;
  • lines 31–33: the list of people is consumed using the [biconsumerWeight] implementation;

The results obtained are as follows:

1
2
3
4
5
6
7
Jean's age = 120
Marie's age = 110
Camille's age = 130
--------
Jean's weight = 270.0
Marie's weight = 230.0
Camille's weight = 255.0

4.6. Example-06 - The BiFunction<T,U,R> functional interface

  

The functional interface BiFunction<T,U,R> is defined as follows:

Image

The interface’s only method has the signature: R apply(T t, U u). This method is similar to the [BiConsumer.apply] method, but while the latter does not return a result, the [BiFunction.apply] method does return a result. We will illustrate its use with the following code:


package dvp.java8.lambdas;

import java.util.List;
import java.util.function.BiFunction;

import dvp.data.Person;
import dvp.data.People;

public class Example06 {
    public static void main(String[] args) {
        // list of people
        List<Person> people = People.get();
        // anonymous implementation
        BiFunction<Person, Integer, Integer> biFunctionAge = new BiFunction<Person, Integer, Integer>() {
            @Override
            public Integer apply(Person person, Integer integer) {
                return person.getAge() + integer;
            }
        };
        // lambda implementation
        BiFunction<Person, Integer, Double> biFunctionWeight = (p, i) -> {
            return p.getWeight() + i;
        };
        // display
        for (Person p : people) {
            System.out.printf("age of %s = %s%n", p.getName(), biFunctionAge.apply(p, 100));
        }
        System.out.println("--------");
        for (Person p : people) {
            System.out.printf("%s's weight = %s%n", p.getName(), biFunctionWeight.apply(p, 200));
        }
    }
}
  • lines 14–19: The BiFunction<Person, Integer, Integer> interface is implemented using an anonymous class. Its [apply] method returns the age of the person passed as the first parameter, increased by the value of the second parameter;
  • lines 21–23: The BiFunction<Person, Integer, Double> interface is implemented using a lambda function. The [apply] method returns the weight of the person passed as the first parameter increased by the value of the second parameter;
  • lines 25–27: the [biFunctionAge] implementation is applied to the people;
  • lines 29–31: the [biFunctionWeight] implementation is applied to the people;

The results obtained are as follows:

Jean's age = 120
Marie's age = 110
Camille's age = 130
--------
Jean's weight = 270.0
Marie's weight = 230.0
Camille's weight = 255.0

In addition to lambda functions, Java 8 introduced the Stream<T> type, which models a stream of elements of type T. These elements can undergo successive transformations implemented by lambda functions. When possible, and when there are multiple processors, these transformations can sometimes be performed in parallel.

4.7. Example-07 - The Supplier<T> functional interface

  

The functional interface Supplier<T> is defined as follows:

Image

The interface’s sole method has the signature: T get(), whose role is to return an object of type T.

We will illustrate this functional interface with the following code:


package dvp.java8.lambdas;

import java.util.List;
import java.util.Random;
import java.util.function.Supplier;

import dvp.data.Person;
import dvp.data.People;

public class Example07 {
    public static void main(String[] args) {
        // anonymous implementation
        Supplier<Person> supplier = new Supplier<Person>() {
            // list of people
            List<Person> people = People.get();

            // interface implementation
            @Override
            public Person get() {
                int i = new Random().nextInt(people.size());
                return people.get(i);
            }
        };
        // processing
        for (int i = 0; i < 5; i++) {
            display(supplier);
        }
    }

    // display Person
    public static void display(Supplier<Person> supplier) {
        System.out.println(supplier.get());
    }
}
  • lines 13–28: implementation of a Supplier<Person> type;
  • lines 31–33: the static method [display] expects a parameter of type Supplier<Person>;
  • lines 25–27: using the Supplier<Person> instance;

The following results are obtained:

1
2
3
4
5
{"name":"camille","age":30,"weight":55.0,"gender":"FEMALE"}
{"name":"marie","age":10,"weight":30.0,"gender":"FEMALE"}
{"name":"jean","age":20,"weight":70.0,"gender":"MALE"}
{"name":"jean","age":20,"weight":70.0,"gender":"MALE"}
{"name":"camille","age":30,"weight":55.0,"gender":"FEMALE"}