Lambda Specification, Part E: Typing and Evaluation

Navigation: Overview - Part A - Part B - Part C - Part D - Part E - Part F - Part G - Part H - Part J
Sections: 15.27 - 15.27.3 - 15.27.4 - 15.28 - 15.28.1 - 15.28.2 - 11.2.1 - 12.5
Version 0.6.1. Copyright © 2012 Oracle America, Inc. Legal Notice.

Summary

Lambda expressions, method references, and constructor references are always poly expressions. It is a compile-time error if one of these occurs in a program in someplace other than an assignment context, an invocation context, or a casting context.

The type of a lambda expression, method reference, or constructor reference is its target type, assuming the expression is compatible with its target type; otherwise, a compile-time error occurs. Compatibility depends on a function descriptor; to derive this descriptor, a functional interface target type is required.

For a lambda expression, the descriptor's parameter and return types are compared to the expression. The lambda parameter types must exactly match those of the descriptor, while the body must be assignment-compatible with the descriptor's return type. The lambda's expression body (or each result expression of its block body) may be a poly expression.

For a method or constructor reference, a most-specific applicable declaration is determined following the process used for method and constructor invocations. The descriptor's parameter types are used as argument types in this search, where the first parameter type may sometimes act as the receiver for an instance method. The selected declaration's return type is then checked to be assignment-compatible with the descriptor's return type.

In addition to the compatibility requirement, lambda bodies and referenced methods and constructors must not throw exceptions that are incompatible with the function descriptor's throws clause.

Evaluation of a lambda expression, method reference, or constructor reference produces an instance of a functional interface type. Evaluation does not cause the execution of a lambda body or the invocation of a referenced method; instead, this may occur at a later time when an appropriate method of the interface is invoked.

To evaluate the expression, either a new instance of an appropriate synthetic class is allocated and initialized, or an existing instance of an appropriate class is referenced. The evaluation rules are minimally restrictive, thus allowing VMs freedom for optimization. For example, a separate synthetic class need not be defined for each distinct expression, nor must a new object be allocated on every evaluation.

15.27 Lambda Expressions [Addendum]

See 15.27

Lambda expressions are always poly expressions (15.2). [jsr335-15.27-10]

It is a compile-time error if a lambda expression occurs in a program in someplace other than an assignment context (5.2), an invocation context (5.3), or a casting context (5.5). [jsr335-15.27-20]

Evaluation of a lambda expression produces an instance of a functional interface (9.8). Lambda expression evaluation does not cause the execution of the expression's body; instead, this may occur at a later time when an appropriate method of the functional interface is invoked.

15.27.3 Type of a Lambda Expression [New]

If a lambda expression is compatible with its target type, T, then the type of the expression is T. [jsr335-15.27.3-10]

A lambda expression is compatible in an assignment, invocation, or casting context with type T if T is a functional interface type (9.8) and the expression is congruent with a function descriptor derived from T. [jsr335-15.27.3-15]

The targeted function descriptor is derived from the target type as follows: [jsr335-15.27.3-20]

A lambda expression is congruent with a function descriptor if the following are true: [jsr335-15.27.3-30]

A lambda expression may be illegal even if it has a type. Where T' is the type of the lambda expression:

Discussion and motivation:
  1. There is some flexibility in the design of lambda expression compatibility: a well-formedness check may either occur as part of the definition, or as an extra check after compatibility is established. Since overload resolution depends on compatibility but not subsequent checks, this distinction is important. In one extreme, every check could be part of compatibility—in that case, subtle differences in, say, the exceptions thrown by the lambda body, could trigger different overloading choices. In the other extreme, the expressions could always be "compatible," and then overload resolution would be unable to distinguish between methods with different descriptor argument types. The goal here is to find a balance that is useful, is reasonably consistent with existing behavior, and minimizes surprises.

  2. We require lambda parameter types to exactly match those of the functional interface descriptor. While it would be possible to be more flexible—allow boxing or contravariance, for example—this kind of generality seems unnecessary, and is inconsistent with the way overriding works in class declarations. A programmer ought to know exactly what functional interface type is being targeted when writing a lambda expression, so he should thus know exactly what signature must be overridden. (In contrast, this is not the case for method references, and so more flexibility is allowed when they are used.) In addition, more flexibility with parameter types would add to the complexity of type inference and overload resolution.

  3. While boxing is not allowed in a strict invocation context, boxing of lambda return expressions is always allowed—that is, the return expression appears in an assignment context, regardless of the context enclosing the lambda expression.

    However, if a lambda expression is an argument to an overloaded method, a method signature that avoids boxing or unboxing the lambda result is preferred by the most-specific check (see 15.12.2.5).

    Similarly, lambda returns are allowed to perform narrowing of constant expressions, as in all assignment contexts.

  4. If the body of a lambda is a statement expression (that is, an expression that would be allowed to stand alone as a statement), it is compatible with a void-producing functional interface type; any result is simply discarded. So, for example, both of the following are legal:

    // Predicate has a boolean return
    Predicate<String> p = s -> list.add(s);
    // Block has a void return
    Block<String> b = s -> list.add(s);
    

    Generally speaking, a lambda of the form () -> expr, where expr is a statement expression, is interpreted as either () -> { return expr; } or () -> { expr; }, depending on the target type.

15.27.4 Run-time Evaluation of Lambda Expressions [New]

At run time, the evaluation of a lambda expression (distinct from execution of a lambda body) either produces a reference to an object of the targeted functional interface type or completes abruptly. [jsr335-15.27.4-10]

The value of a lambda expression is a reference to an instance of a synthetic class with the following properties: [jsr335-15.27.4-20]

To evaluate the lambda expression, either a new instance of an appropriate class is allocated and initialized, or an existing instance of an appropriate class is referenced. [jsr335-15.27.4-30]

This implies that the behavior of an equality operator (15.21) is unpredictable when applied to the result of evaluation of a lambda expression: the equality test may produce different results in different implementations, or even upon different lambda expression evaluations in the same implementation.

If a new instance is to be created, but there is insufficient space to allocate the object, evaluation of the lambda expression completes abruptly by throwing an OutOfMemoryError (15.9.6). [jsr335-15.27.4-40]

This section was influenced by 15.9.4 "Run-time Evaluation of Class Instance Creation Expressions."
Discussion and motivation:
  1. This section is meant to be minimally restrictive, thus allowing VMs freedom for optimization. Some flexibility it provides:
    • A new object need not be allocated on every evaluation
    • Objects produced by different lambda expressions need not belong to different classes (if the bodies are identical, for example)
    • Every object produced by evaluation need not belong to the same class (captured local variables might be inlined, for example)
    • If an "existing instance" is available, it need not have been created at a previous lambda evaluation (it might have been allocated during the enclosing class's initialization, for example).
  2. While it is outside the scope of the language specification to prescribe compiler behavior, it is expected that all compilers will encode lambda bodies as methods, typically of the enclosing class. The evaluation behavior specified above will then be achieved via an invokedynamic instruction that mentions the compiled method and invokes a standard API. The VM, independent of the compiler, provides the implementation of this API and is free to use whatever strategy it prefers to manage class and object creation.
  3. If the targeted functional interface type is a subtype of Serializable, the resulting object will automatically be an instance of a serializable class. Making an object derived from a lambda expression serializable can have extra runtime overhead and negative security implications, so we do not require all lambda objects to be serializable.

    The standard API for lambda evaluation provides special support for serialization which is more robust than the automatic graph-traversal behavior, and which is supported across different VM implementations.

  4. It is expected that the synthetic class will typically override the toString method, since the implementation provided by Object is quite unhelpful—any useful information about the lambda expression, if the Object implementation were used, could only be deduced from the synthetic class's name.

15.28 Method and Constructor Reference Expressions [Addendum]

See 15.28

Method and constructor reference expressions are always poly expressions (15.2). [jsr335-15.28-10]

It is a compile-time error if a method or constructor reference expression occurs in a program in someplace other than an assignment context (5.2), an invocation context (5.3), or a casting context (5.5). [jsr335-15.28-20]

Evaluation of a method or constructor reference expression produces an instance of a functional interface (9.8). Method or constructor reference evaluation does not cause the execution of the corresponding method or constructor; instead, this may occur at a later time when an appropriate method of the functional interface is invoked.

15.28.1 Type of a Method or Constructor Reference [New]

If a method reference expression or a constructor reference expression is compatible with its target type, T, then the type of the expression is T. [jsr335-15.28.1-10]

A method or constructor reference expression is compatible in an assignment, invocation, or casting context with type T if T is a functional interface type (9.8) and the expression is congruent with the function descriptor of T. [jsr335-15.28.1-15]

A method or constructor reference is congruent with a function descriptor if the following are true: [jsr335-15.28.1-30]

Given a function descriptor with parameter types P1..Pn, the compile-time declaration for a method reference is determined as follows. [jsr335-15.28.1-40]

Given a function descriptor with parameter types P1..Pn, the compile-time declaration for a constructor reference is determined as follows. [jsr335-15.28.1-50]

If the constructor reference has the form ClassType :: NonWildTypeArgumentsopt new, a declaration is resolved as if it were a class instance creation expression for type ClassType with argument expressions of types P1..Pn. The invocation's type arguments, if any, are given by the constructor reference; if ClassType is a raw type, the class type arguments are inferred as for an invocation that uses the <> syntax. A search for an enclosing instance and a most-specific applicable constructor is performed following the process described in 15.9.2 and 15.9.3. If an error would result from this search, there is no compile-time declaration; otherwise, the compile-time declaration is the most-specific applicable constructor. [jsr335-15.28.1-52]

If the constructor reference has the form ArrayType :: new, an implicit declaration is resolved in the same way, treating the type ArrayType as if it were a class with a constructor that accepts one parameter of type int. [jsr335-15.28.1-53]

A method or constructor reference may be illegal even if it is compatible with its expected type. In particular, where T' is the type of the expression:

Parts of this section mimic 15.27.3.
Discussion and motivation:
  1. The key idea driving this compatibility definition is that a method reference is compatible if and only if the equivalent lambda expression (x, y, z) -> exp.<T1, T2>method(x, y, z) is compatible. (This is informal, and there are issues that make it difficult or impossible to formally define the semantics in terms of such a rewrite.)
  2. Method references of the form ReferenceType::id can be interpreted in different ways. It is ambiguous whether the identifier refers to a static method or an instance method; in the latter case, the implicit lambda expression has an extra parameter. It's possible, of course, for both kinds of applicable methods to exist, and the search for an applicable method via 15.12.2 must identify them separately, since there are different parameter types for each case.

    Also note that static method invocations have the form TypeName.method(), while here we're using the more general ReferenceType; an error occurs if the reference ends up being a static method reference and the qualifier is a parameterized type.

    An example of ambiguity:

    interface Fun<T,R> { R apply(T arg); }
    
    class C {
      int size() { return 3; }
      static int size(C arg) { return arg.size(); }
    
      void test() {
        Fun<C, Integer> f1 = C::size; // c.size() or C.size(c)?
      }
    }
    
  3. These compatibility rules provide a convenient facility for converting from one functional interface to another:
    Task t = () -> System.out.println("hi");
    Runnable r = t::invoke;
    

    The implementation may be optimized so that when a lambda object is passed around and converted to various types, this does not result in many levels of adaptation logic around the core lambda body.

  4. For convenience, when a generic type is used to refer to an instance method (where the receiver becomes the first parameter), the target type can used to determine the type arguments. This facilitates usage like Pair::first in place of Pair<String,Integer>::first.

15.28.2 Run-time Evaluation of Method and Constructor References [New]

At run time, the evaluation of a method reference or a constructor reference (distinct from invocation of the method or constructor itself) either produces a reference to an object of the targeted functional interface type or completes abruptly. [jsr335-15.28.2-10]

The value of a method or constructor reference is a reference to an instance of a synthetic class with the following properties: [jsr335-15.28.2-20]

To evaluate the method or constructor reference, the following steps are taken: [jsr335-15.28.2-30]

For a method reference, invocation of the referenced method in the synthetic method's body proceeds as follows: [jsr335-15.28.2-40]

For a constructor reference of the form ClassType :: NonWildTypeArgumentsopt new, class instance creation in the synthetic method's body proceeds as follows: [jsr335-15.28.2-50]

For a constructor reference of the form ArrayType :: new, array creation in the synthetic method's body proceeds as for an expression of the form new ArrayType [ size ], where size is the synthetic method's single parameter. [jsr335-15.28.2-52]

This section borrows heavily 15.27.4, above. The rules for invocation also reproduce, in simplified form, some of 15.9.4 and 15.12.4.
Discussion and motivation:
  1. The timing of method reference evaluation is a little more complex than that of lambda expressions: when a method reference is qualified with an expression (rather than a type), the expression is evaluated immediately. The result of evaluation is then stored until the functional interface's method is invoked; at that point, the value is used as the target reference for the invocation. This means that the portion of the method reference preceding the :: delimiter is only evaluated when the program encounters the method reference; subsequent invocations of the functional interface do not re-evaluate the subexpression.
  2. When a method invocation is evaluated, it is possible for the Primary that qualifies the invocation to produce null, but for no NullPointerException to occur. This occurs when the invoked method is static (despite the syntax of the invocation suggesting an instance method). Since we explicitly prohibit a matched method for this kind of method reference from being static (15.27.4), the evaluation behavior described here is simpler—a null primary always triggers a NullPointerException.
  3. As was the case for lambda expression evaluation, it is outside the scope of the language specification to prescribe compiler behavior, but it is expected that all compilers will generate an invokevirtual instruction that delegates to a standard API the wrapping of a referenced method as an instance of a particular interface.

11.2.1 Exception Analysis of Expressions [Modified]

Compare JLS 11.2.1

A class instance creation expression (15.9) can throw an exception class E iff ... [jls-11.2.1-100]

A method invocation expression (15.12) can throw an exception class E iff ... [jls-11.2.1-110]

A lambda expression (15.27) can throw no exception types. [jsr335-11.2.1-120]

For every other kind of expression, the expression can throw an exception type E iff one of its immediate subexpressions can throw E. [jls-11.2.1-200]

12.5 Creation of New Class Instances [Modified]

Compare JLS 12.5

A new class instance may be implicitly created in the following situations: