Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does this Java 8 method reference compile?

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)
like image 849
pklndnst Avatar asked Sep 12 '15 12:09

pklndnst


People also ask

What is Java 8 method reference?

Java provides a new feature called method reference in Java 8. Method reference is used to refer method of functional interface. It is compact and easy form of lambda expression. Each time when you are using lambda expression to just referring a method, you can replace your lambda expression with method reference.

What is the advantage of using method reference in Java 8?

That's all about what is method reference is in Java 8 and how you can use it to write clean code in Java 8. The biggest benefit of the method reference or constructor reference is that they make the code even shorter by eliminating lambda expression, which makes the code more readable.

How does method reference work?

The method references can only be used to replace a single method of the lambda expression. A code is more clear and short if one uses a lambda expression rather than using an anonymous class and one can use method reference rather than using a single function lambda expression to achieve the same.

What is method reference and constructor references in Java 8?

A method reference requires a target type similar to lambda expressions. But instead of providing an implementation of a method, they refer to a method of an existing class or object while a constructor reference provides a different name to different constructors within a class.


2 Answers

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.

like image 187
Mark Rotteveel Avatar answered Oct 08 '22 19:10

Mark Rotteveel


Apart from what it says in @Mark Rotteveel's exact answer, it compiles because in Java you can ignore the result of any method invocation, such as in the following example:

Map<String, String> map = new HashMap<>();

map.put("1", "a");

map.put("1", "A"); // Who cares about the returned value "a"?

As you're not returning anything in the forEach() consumer's block, then it is valid as per the specification.

like image 40
fps Avatar answered Oct 08 '22 21:10

fps