Type Checking and Execution of Lambda Expressions – Functional-Style Programming

Type Checking and Execution of Lambda Expressions

A lambda expression can only be defined in a context where a functional interface can be used: for example, in an assignment context, a method call context, or a cast context (p. 733). The compiler determines the target type that is required in the context where the lambda expression is defined. This target type is always a functional interface type. In the assignment context below, the target type is Predicate<Integer>, as it is the target of the assignment statement. Note that the type parameter T of the functional interface is Integer.

Click here to view code image

Predicate<Integer> p1 = i -> i % 2 == 0;  // (1) Target type: Predicate<Integer>

The method type of a method declaration comprises its type parameters, formal parameter types, return type, and any exceptions the method throws.

The function type of a functional interface is the method type of its functional method. The target type Predicate<Integer> has the following method, where type parameter T is Integer:

Click here to view code image

public boolean test(Integer t);          // Method type: Integer -> boolean

The function type of the target type Predicate<Integer> is the method type of the this test() method:

Integer -> boolean

The type of the lambda expression defined in a given context must be compatible with the function type of the target type. If the lambda expression has inferred-type parameters, their type is inferred from the context, and if necessary, from the function type. From the lambda body at (1), it is possible to infer that the type of i is int (from the expression i % 2), and the lambda body evaluates to a boolean value (from the evaluation of the == operator). Just from the context, it is thus possible to infer that the type of the lambda expression at (1) is:

int -> boolean

From the function type of the target type Predicate<Integer>, the compiler is able to determine that if the inferred int type of the parameter i in the lambda expression at (1) is promoted to Integer, the type of the lambda expression at (1) would then be compatible with the function type of the target type Predicate<Integer>, that is:

Integer -> boolean

The lambda expression defined at (1) above is equivalent to the following implementation using an anonymous class.

Click here to view code image

Predicate<Integer> p1 = new Predicate<>() {  // Anonymous class
  public boolean test(Integer i) {
    return i % 2 == 0;
  }
};

It is possible to determine that the type of the lambda expression at (1) is compatible with the method type of the only abstract method test() defined by the Predicate<T> functional interface because it is not ambiguous which method the lambda expression should implement in the functional interface. However, this would not work for an interface that had more than one abstract method, as it would not be clear which abstract method to implement.

In the following assignment, the target type is java.util.function.IntPredicate:

Click here to view code image

IntPredicate p2 = i -> i % 2 == 0;        // (2) Target type: IntPredicate

The IntPredicate functional interface has the following abstract method:

Click here to view code image

public boolean test(int i);              // Method type: int -> boolean

The function type of the target type IntPredicate is the method type of its abstract method:

int -> boolean

The compiler infers that the type of the inferred-type parameter i in the lambda expression at (2) should be int. As the lambda body returns a boolean value, the type of the lambda expression at (2) is

int -> boolean

The type of the lambda expression is compatible with the function type of the target type IntPredicate.

Note that in both examples, the lambda expression is the same, but their types are different in the two contexts. They represent two different values. The type of a lambda expression is determined by the context in which it is defined.

Click here to view code image

System.out.println(p1 == p2);            // false

The process of type checking a lambda expression in a given context is called target typing. The presentation here is simplified, but suffices for our purpose to give an idea of what is involved.

The compiler does the type checking for using lambda expressions. The runtime environment provides the rest of the magic to make it all work. At runtime, the lambda expression is executed when the sole abstract method of the functional interface is invoked.

Click here to view code image

boolean result1 = p1.test(2021);          // false
boolean result2 = p2.test(2020);          // true

As mentioned earlier, this is an example of deferred execution. Lambda execution is similar to invoking a method on an object. We define a lambda expression as a function and use it like a method, letting the compiler and the runtime environment put it all together.

Leave a Comment