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 - 11.2.3 - 12.5
Version 0.6.2. Copyright © 2012 Oracle America, Inc. Legal Notice.

Summary

Lambda expressions and method 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 or method 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 function type target 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 reference, a compile-time declaration is determined following the process used for method 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 must not throw exceptions that are incompatible with the function descriptor's throws clause.

Evaluation of a lambda expression or method reference produces an instance of a functional interface. 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 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 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 function 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 (see Part F), 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 approach we've chosen is to allow most errors in the lambda body to indicate incompatibility, except for exception checking. We don't rely on exceptions for two reasons:

    • Exception checking is subtle, and the common practice is to refine throws clauses based on compiler feedback. We would not want this "feedback" to take the form of subtle overload resolution changes (by making the lambda compatible with different sets of target types depending on the body's exceptions).
    • Inference may influence the target type's throws clause and the exceptions thrown by return expressions in the body. It is easiest to cope with these dependencies by avoiding exception checking until after overload resolution and inference are complete.
  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 function 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 function descriptor; 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 function type or completes abruptly. [jsr335-15.27.4-10]

The value of a lambda expression is a reference to an instance of a 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 runtime library, 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 function 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-derived 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 lambda implementation 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 class's name.

15.28 Method Reference Expressions [Addendum]

See 15.28

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

It is a compile-time error if a method 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 reference expression produces an instance of a functional interface (9.8). Method reference evaluation does not cause the execution of the corresponding method; 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 Reference [New]

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

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

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

The compile-time declaration of a method reference is either a declared method or, in special cases, a notional method that represents a class instance creation or an array creation. 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]

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

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-derived 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.

    Similarly, a method reference like Pair::new is treated like a "diamond" instance creation (new Pair<>()). There is no need for the <> syntax here; in fact, it is not allowed by the grammar. Note that this form does not instantiate a raw type, and there is no way to express a reference to a raw type constructor—this is because it seems that the raw instance creation would almost never be more useful than its inferred-parameters counterpart.

15.28.2 Run-time Evaluation of Method References [New]

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

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

If the method reference has the form ExpressionName :: NonWildTypeArgumentsopt Identifier or Primary :: NonWildTypeArgumentsopt Identifier, the body of the invocation method has the effect of invoking the compile-time declaration of the method reference, as described in 15.12.4.3, 15.12.4.4, 15.12.4.5. [jsr335-15.28.2-41]

If the method reference has the form super :: NonWildTypeArgumentsopt Identifier or TypeName . super :: NonWildTypeArgumentsopt Identifier, the body of the invocation method similarly has the effect of invoking the compile-time declaration of the method reference, as described in 15.12.4.3, 15.12.4.4, 15.12.4.5. [jsr335-15.28.2-42]

If the method reference has the form ReferenceType :: NonWildTypeArgumentsopt Identifier, the body of the invocation method similarly has the effect of invoking the compile-time declaration of the method reference, as described in 15.12.4.3, 15.12.4.4, 15.12.4.5. [jsr335-15.28.2-43]

If the method reference has the form ClassType :: NonWildTypeArgumentsopt new, the body of the invocation method has the same effect as a class instance creation expression of the form new NonWildTypeArgumentsopt ClassType(param1, ..., paramn) (15.9.4), where param1, ..., paramn are the parameters of the invocation method. [jsr335-15.28.2-50]

If the method reference has the form ArrayType :: new, the body of the invocation method has the same effect as an array creation expression of the form new ArrayType [ size ], where size is the invocation method's single parameter. [jsr335-15.28.2-52]

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

This section borrows heavily 15.27.4, above. The rules for invocation also reproduce, in simplified form, some of 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.28.1), 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]

11.2.3 Exception Checking [Modified]

Compare JLS 11.2.3

It is a compile-time error if a method or constructor body can throw some exception class E when E is a checked exception class and E is not a subclass of some class declared in the throws clause of the method or constructor. [jls-11.2.3-100]

It is a compile-time error if a lambda body can throw some exception class E when E is a checked exception class and E is not a subclass of some class declared in the throws clause of the function descriptor targeted by the lambda expression. [jsr335-11.2.3-105]

It is a compile-time error if a class variable initializer (8.3.2) or static initializer (8.7) of a named class or interface can throw a checked exception class. [jls-11.2.3-110]

It is a compile-time error if an instance variable initializer or instance initializer of a named class can throw a checked exception class unless that exception class or one of its superclasses is explicitly declared in the throws clause of each constructor of its class and the class has at least one explicitly declared constructor. [jls-11.2.3-120]

...

It is a compile-time error if a catch clause can catch checked exception class E1 and it is not the case that the try block corresponding to the catch clause can throw a checked exception class that is a subclass or superclass of E1, unless E1 is Exception or a superclass of Exception. [jls-11.2.3-200]

It is a compile-time error if a catch clause can catch (11.2) checked exception class E1 and a preceding catch clause of the immediately enclosing try statement can catch E1 or a superclass of E1. [jls-11.2.3-210]

...

12.5 Creation of New Class Instances [Modified]

Compare JLS 12.5

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