TIL that method references have complicated rules.

Todays topic is inspired by a Twitter post from Josh Bloch asking why the following Java code doesn’t compile:

void puzzler () { ExecutorService exec = Executors . newCachedThreadPool (); exec . submit ( System . out :: println ); }

First of all, I’ll take Josh’s word for it. But it looks to me like it could compile - System.out.println invoked with no args will do some stdio and return null, so exec.submit() can return a Future<Void> . Hmmm.

Lets see the error then. I don’t usually role-play as a compiler when I have a good one to hand in jshell :

jshell > ExecutorService exec = Executors . newCachedThreadPool (); exec ==> java . util . concurrent . ThreadPoolExecutor @ 56 ef9176 [ ... = 0 , completed tasks = 0 ] jshell > exec . submit ( System . out :: println ); | Error: | reference to submit is ambiguous | both method < T > submit ( java . util . concurrent . Callable < T >) in java . util . concurrent . ExecutorService and method submit ( java . lang . Runnable ) in java . util . concurrent . ExecutorService match | exec . submit ( System . out :: println ); | ^---------^ | Error: | incompatible types: cannot infer type - variable ( s ) T | ( argument mismatch ; bad return type in method reference | void cannot be converted to T ) | exec . submit ( System . out :: println ); | ^------------------------------^

Two errors. The first is confusing - hence it being a puzzle ;-) Here it is again:

reference to submit is ambiguous both <T>submit(java.util.concurrent.Callable<T>) ... and submit(java.lang.Runnable) ... match

The compiler is complaining that it can’t tell whether method reference System.out::println should have target type of Runnable or Callable .

But in other circumstances it can clearly tell that only Runnable matches:

jshell > Runnable r = System . out :: println // YEP! r ==> $Lambda $ 17 / 267760927 @ 25 bbe1b6 jshell > Callable c = System . out :: println // NOPE! | Error: | incompatible types: bad return type in method reference | void cannot be converted to java . lang . Object | Callable c = System . out :: println ; | ^-----------------^ jshell > Callable < Void > c = System . out :: println // ALSO NOPE!! | Error: | incompatible types: bad return type in method reference | void cannot be converted to java . lang . Void | Callable < Void > c = System . out :: println ; | ^-----------------^

So that’s weird, isn’t it?

Fiddling about with this it seems to be triggered by System.out::println being variadic. Here’s a minimal repro:

jshell > class Banana { void foo (){}; void foo ( int s ){} } | created class Banana jshell > exec . submit ( new Banana ():: foo ) ( same error as before )

The Answer

As figured out by @lhochstein:

There is a concept of exactness which can be applied to a method reference. Quoting JLS 15.13.1:

For some method reference expressions, there is only one possible compile-time declaration with only one possible invocation type, regardless of the targeted function type. Such method reference expressions are said to be exact. A method reference expression that is not exact is said to be inexact.

Given this, it’s clear that System.out::println is inexact as there are 10 overloads.

Elsewhere in the JLS we find that inexact method references are excluded from being considered in method overload resolution:

An argument expression is considered pertinent to applicability for a potentially applicable method m unless it has one of the following forms: […] An inexact method reference expression (§15.13.1).

Exactly…

Well, TIL that method references don’t work quite how I thought they did. I can see this as a simple case where it looks like it ought to work, but more complex examples are easy to imagine.

JDK8 improved a lot of things, but in Josh’s words “Compromises were required”. Here and here people have raised this behaviour as being a bug.