I've come across a weird problem where a method reference to Thread::sleep
is ambiguous but a method with the same signature is not.
package test;
public class Test
{
public static void main(String[] args)
{
foo(Test::sleep, 1000L); //fine
foo((FooVoid<Long>)Thread::sleep, 1000L); //fine
foo(Thread::sleep, 1000L); //error
}
public static void sleep(long millis) throws InterruptedException
{
Thread.sleep(millis);
}
public static <P, R> void foo(Foo<P, R> function, P param) {}
public static <P> void foo(FooVoid<P> function, P param) {}
@FunctionalInterface
public interface Foo<P, R> {
R call(P param1) throws Exception;
}
@FunctionalInterface
public interface FooVoid<P> {
void call(P param1) throws Exception;
}
}
I get those 2 errors:
Error:(9, 17) java: reference to foo is ambiguous
both method <P,R>foo(test.Test.Foo<P,R>,P) in test.Test and method <P>foo(test.Test.FooVoid<P>,P) in test.Test match
Error:(9, 20) java: incompatible types: cannot infer type-variable(s) P,R
(argument mismatch; bad return type in method reference
void cannot be converted to R)
The only difference I see is that Thread::sleep
is native
. Does it change anything? I don't think the overload Thread::sleep(long, int)
comes into play here. Why does it happen?
EDIT: Using javac version 1.8.0_111
You can recreate the problem in your own class by adding a method sleep
with two arguments to class Test like below:
public static void sleep(long millis) {
}
public static void sleep(long millis, int nanos) {
}
So the problem is really caused by the fact that the method sleep is overloaded.
The JLS indicates that the initial method selection code only looks at the number of type arguments to the functional interface - only in the second phase does it look at the signature of the method inside the functional interface.
JLS 15.13:
It is not possible to specify a particular signature to be matched, for example, Arrays::sort(int[]). Instead, the functional interface provides argument types that are used as input to the overload resolution algorithm (§15.12.2).
(the second-to-last paragraph of this section)
So in the case of Thread::sleep
, void sleep(long)
potentially matches functional interface FooVoid<P>
, while overload void sleep(long, int)
potentially matches functional interface Foo<P, R>
. That's why you get the "reference to foo is ambiguous" error.
When it tries to go further and see how to match Foo<P, R>
with functional method R call(P param1)
to the method void sleep(long, int)
, it finds out that this is not actually possible, and you get another compile error:
test/Test.java:7: error: incompatible types: cannot infer type-variable(s) P,R
foo(Thread::sleep, 1000L); // error
^
(argument mismatch; bad return type in method reference
void cannot be converted to R)