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 - 15.28.3 - 11.2.1 - 11.2.3 - 12.5 - 13.1 - 15.7.5 - Serialization
Version 0.9.1. Copyright © 2013 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 a functional interface type, derived from its target type. To be compatible with the target type, the expression must be congruent with the function type of this functional interface type.

To test that a lambda expression is congruent, the function type's parameter and return types are compared to the expression. The lambda parameter types (if given) must exactly match those of the function type, while the body must be assignment-compatible with the function type's return type. The lambda's expression body (or each result expression of its block body) may be a poly expression.

To test that a method reference is congruent, a compile-time declaration is determined following the process used for method invocations. The function type'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 function type's return type.

For some method references, there is only one possible compile-time declaration with only one possible invocation type, regardless of the targeted function type. These are referred to as exact method references.

In addition to the compatibility requirement, lambda bodies and referenced methods must not throw exceptions that are incompatible with the function type'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]

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 the function type of a ground target type derived from T. [jsr335-15.27.3-15]

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

The ground target type is derived from T as follows: [jsr335-15.27.3-20]

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

Where T' is the type of the lambda expression, it is a compile-time error if any class or interface mentioned by either T' or the function type of T' is not accessible from the class or interface in which the lambda expression appears. [jsr335-15.27.3-50]

Where T' is the type of the lambda expression, for each non-static member method m of T', if the function type of T' has a subsignature of the signature of m, then a notional method with the function type is said to override m, and any error or warning specified in 8.4.8.3 may occur. [jsr335-15.27.3-55]

In addition, a checked exception that can be thrown in the body of the lambda may cause an error, as specified in 11.2.3.

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 of explicitly-typed lambdas 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 entirely unable to distinguish between appropriate and inappropriate target types.

    The approach we've chosen is to check the parameter and return types, but not the throws clause. We avoid exception checking 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 result expressions in the body. It is easiest to cope with these dependencies by avoiding exception checking until after overload resolution and inference are complete.

    Matters are complicated further by implicitly-typed lambdas. Hence, while the compatibility rules for implicitly-typed lambdas are the same, the compatibility check itself is almost entirely sidestepped by overload resolution (see Part F).

  2. We require the parameter types of explicitly-typed lambdas to exactly match those of the function type. 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 result expressions is always allowed—that is, the result expression appears in an assignment context, regardless of the context enclosing the lambda expression.

    However, if an explicitly-typed 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 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);
    // Consumer has a void return
    Consumer<String> c = 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, as described below. [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]

The rule describing when a ClassCastException occurs mimics 15.12.4.5.

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 Compile-Time Declaration of a Method Reference [New]

The compile-time declaration of a method reference is the method to which the expression refers. In special cases, the compile-time declaration does not actually exist, but is a notional method that represents a class instance creation or an array creation. The choice of compile-time declaration depends on a function type targeted by the expression (just as the compile-time declaration of a method invocation depends on the invocation's arguments (15.12)). [jsr335-15.28.1-40]

The identification of a compile-time declaration mirrors the process for method invocations in 15.12.1 and 15.12.2. [jsr335-15.28.1-100]

First, a type to search must be determined (compare 15.12.1): [jsr335-15.28.1-110]

Second, given a targeted function type with n parameters, a set of potentially-applicable methods is identified (compare 15.12.2.1): [jsr335-15.28.1-120]

If there are no potentially-applicable methods, then the method reference does not have a compile-time declaration. [jsr335-15.28.1-125]

Finally, given a targeted function type with parameter types P1..Pn and a set of potentially-applicable methods, the compile-time declaration is selected, according to the process in 15.12.2.2-15.12.2.5, with the following clarifications: [jsr335-15.28.1-130]

For some method references, there is only one possible compile-time declaration with only one possible invocation type (15.12.2.6), regardless of the targeted function type. These are referred to as exact method references.

Specifically, a method reference ending in an Identifier is exact if it satisfies all of the following: [jsr335-15.28.1-200]

A method reference of the form ClassType :: NonWildTypeArgumentsopt new is exact if it satisfies all of the following: [jsr335-15.28.1-210]

A method reference of the form ArrayType :: new is always exact. [jsr335-15.28.1-220]

A method reference that is not exact is referred to as inexact. [jsr335-15.28.1-250]

Discussion and motivation:

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

    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; // error: c.size() or C.size(c)?
      }
    }
    

    The search is smart enough to ignore potential ambiguities in which all the candidate methods of one arity have mismatched "staticness":

    interface Fun<T,R> { R apply(T arg); }
    
    class C {
      int size() { return 3; }
      int size(Object arg) { return 0; }
      int size(C arg) { return arg.size(); }
    
      void test() {
        Fun<C, Integer> f1 = C::size; // no error: must be c.size()
      }
    }
    

    But when an applicable candidate with mismatched staticness is more specific than an applicable candidate at the same arity with correct staticness, the mismatched method will not be ignored.

    interface Fun<T,R> { R apply(T arg); }
    
    class C {
      int size() { return 3; }
      static int size(Object arg) { return 0; }
      int size(C arg) { return arg.size(); }
    
      void test() {
        Fun<C, Integer> f1 = C::size; // error: c.size() or C.size(c)?
      }
    }
    
  2. 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 the raw instance creation would almost never be more useful than its inferred-parameters counterpart.

15.28.2 Type of a Method Reference [New]

A method 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 type of a ground target type derived from T. [jsr335-15.28.1-15]

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

The ground target type is derived from T as follows: [jsr335-15.28.1-18]

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

A compile-time unchecked warning occurs if unchecked conversion was necessary for the compile-time declaration to be applicable and, per 5.1.9, this conversion would cause an unchecked warning in an invocation context. [jsr335-15.28.1-40]

A compile-time unchecked warning occurs if unchecked conversion was necessary for the return type R', described above, to be compatible with the function type's return type, R, and, per 5.1.9, this conversion would cause an unchecked warning in an assignment context. [jsr335-15.28.1-41]

For each checked exception that is listed in the throws clause of the invocation type of the compile-time declaration, a compile-time error occurs unless that exception type or a supertype of that exception type is mentioned in the throws clause of the target type's function type. [jsr335-15.28.1-60]

Where T' is the type of the method reference, it is a compile-time error if any class or interface mentioned by either T' or the function type of T' is not accessible from the class or interface in which the method reference appears. [jsr335-15.28.1-70]

Where T' is the type of the method reference, for each non-static member method m of T', if the function type of T' has a subsignature of the signature of m, then a notional method with the function type is said to override m, and any error or warning specified in 8.4.8.3 may occur. [jsr335-15.28.1-75]

It is a compile-time error if the method reference is of the form ReferenceType :: NonWildTypeArgumentsopt Identifier, and the compile-time declaration is static, but ReferenceType is not expressed as a simple or qualified name (6.2). [jsr335-15.28.1-80]

It is a compile-time error if the method reference is of the form super :: NonWildTypeArgumentsopt Identifier and the compile-time declaration is abstract. [jsr335-15.28.1-90]

It is a compile-time error if the method reference is of the form TypeName . super :: NonWildTypeArgumentsopt Identifier, and either of the following are true: [jsr335-15.28.1-91]

It is a compile-time error if the method reference is of the form ClassType :: NonWildTypeArgumentsopt new and an error would occur when determining an enclosing instance for a ClassType, as described in 15.9.2 (treating the method reference as if it were a class instance creation expression). [jsr335-15.28.1-95]

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. Note that static method invocations have the form TypeName.method(), while the method reference syntax uses the more general ReferenceType; an error occurs if the referenced method ends up being static and the qualifier is a parameterized type.

  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. Unlike a lambda expression, a method reference can be congruent with a generic function type (that is, a function type that has type parameters). This is because the lambda expression would need to be able to declare type parameters, and no syntax supports this; while for a method reference, no such declaration is necessary.

    For example, the following program is legal:

    interface ListFactory { <T> List<T> make(); }
    ListFactory arrayListFactory = ArrayList::new;
    List<String> ls = arrayListFactory.make();
    List<Number> ln = arrayListFactory.make();
    

15.28.3 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 functional interface type or completes abruptly, as described below. [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, for some k ≥ 1, Type []k :: new, the body of the invocation method has the same effect as an array creation expression of the form new Type [ size ] []k-1, where size is the invocation method's single int-valued parameter. (The notation []k indicates a sequence of k bracket pairs.) [jsr335-15.28.2-52]

In any case, if the compile-time declaration of the method reference is signature polymorphic (15.12.3), then: [jsr335-15.28.2-60]

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. It is useful to contrast the treatment of null here with its treatment for a method invocation. 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 type 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:

13.1 Creation of New Class Instances [Modified]

Compare JLS 13.1

...

Given a method invocation expression or a method reference expression (15.28) in a class or interface C referencing a method named m declared (or implicitly declared (9.2)) in a (possibly distinct) class or interface D, we define the qualifying type of the method invocation as follows: [jls-13.1-110-E]

...

Given a class instance creation expression (15.9), or a constructor invocation statement (8.8.7.1), or a method reference of the form ClassType::new in a class or interface C referencing a constructor m declared in a (possibly distinct) class or interface D, we define the qualifying type of the constructor invocation as follows: [jls-13.1-110-F]

...

Discussion and motivation:

These changes simply indicate that the compiled form of a method reference must include the equivalent method or constructor invocation. We also account for the changes to super method invocation syntax described in Part H (15.12).

15.7.5 Evaluation Order for Other Expressions [Modified]

Compare JLS 15.7.5

The order of evaluation for some expressions is not completely covered by these general rules, because these expressions may raise exceptional conditions at times that must be specified. See the detailed explanations of evaluation order for the following kinds of expressions:

Serialization Specification [Addendum]

See Java Object Serialization Specification

As with inner classes, serialization of lambda expressions is strongly discouraged. Names of synthetic methods generated by javac (or other Java compilers) to implement lambda expressions are implementation-dependent, may vary between compilers, and may change due to unrelated modifications in the same source file; differences in such names can disrupt compatibility. Lambda expressions may refer to values from the enclosing scope; when the lambda expressions are serialized, these values will be serialized as well. The order in which values from the enclosing scope are captured is implementation-dependent, may vary between compilers, and any modification of the source file containing the lambda expression may change this capture order, affecting deserialization correctness. Lambda expressions cannot use field- or method-based mechanisms to control their serialized form. If serializable lambdas are used, to minimize compatibility risks, it is recommended that class files identical to those that were present at serialization time be present at deserialization time.

This paragraph is to be added to section 1.10 of the Java Object Serialization Specification. It parallels a similar discussion about inner classes.