Why does this Java 8 method reference compile?

pklndnst picture pklndnst · Sep 12, 2015 · Viewed 10.8k times · Source

I'm currently diving deeper into Java 8 features like Lambdas and method references. Playing around a bit brought me to the following example:

public class ConsumerTest {

  private static final String[] NAMES = {"Tony", "Bruce", "Steve", "Thor"};

   public static void main(String[] args) {
      Arrays.asList(NAMES).forEach(Objects::requireNonNull);
   }
}

My question is:

Why does the line inside the main method compile ?

If I understood the thing correctly, the referenced method's signature has to correspond to the functional interface's SAM signature. In this case, the Consumer requires the following signature:

void accept(T t);

However, the requireNonNull method returns T instead of void:

public static <T> T requireNonNull(T obj)

Answer

Mark Rotteveel picture Mark Rotteveel · Sep 12, 2015

The Java Language Specification version 8 says in 15.13.2:

A method reference expression is compatible in an assignment context, invocation context, or casting context with a target type T if T is a functional interface type (§9.8) and the expression is congruent with the function type of the ground target type derived from T.

[..]

A method reference expression is congruent with a function type if both of the following are true:

  • The function type identifies a single compile-time declaration corresponding to the reference.
  • One of the following is true:
    • The result of the function type is void.
    • The result of the function type is R, and the result of applying capture conversion (§5.1.10) to the return type of the invocation type (§15.12.2.6) of the chosen compile-time declaration is R' (where R is the target type that may be used to infer R'), and neither R nor R' is void, and R' is compatible with R in an assignment context.

(emphasis mine)

So the fact the result of the function type is void, is enough for it to allow it to match.

JLS 15.12.2.5 also specifically mentions the use of void when matching methods. For lambda expression there is the notion of a void-compatible block (15.27.2) which is referenced in 15.12.2.1, but there is no equivalent definition for method references.

I haven't been able to find a more specific explanation (but the JLS is a tough nut to crack so maybe I am missing some relevant sections), but I assume this has to do with the fact that you are also allowed to invoke non-void methods as a statement on its own (without assignment, return etc)).

JLS 15.13.3 Run-Time Evaluation of Method References also says:

For the purpose of determining the compile-time result, the method invocation expression is an expression statement if the invocation method's result is void, and the Expression of a return statement if the invocation method's result is non-void.

The effect of this determination when the compile-time declaration of the method reference is signature polymorphic is that:

  • The types of the parameters for the method invocation are the types of the corresponding arguments.
  • The method invocation is either void or has a return type of Object, depending on whether the invocation method which encloses the method invocation is void or has a return type.

So the generated method invocation will be void to match the functional type.