19

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

Winter
  • 3,894
  • 7
  • 24
  • 56
  • It is interesting that the Eclipse java compiler in version Mars.2 Release (4.5.2) has no problems with your code. – Erwin Bolwidt Nov 14 '17 at 03:14
  • @ErwinBolwidt I get a different error message meaning the same thing when I switch to eclipse compiler in IntelliJ. I don't know what the version of the compiler is. – Winter Nov 14 '17 at 03:19
  • I tried by comment this method it compiled. // public static

    void foo(Foo

    function, P param) { // }

    – janith1024 Nov 14 '17 at 03:36
  • 2
    @ErwinBolwidt Eclipse Mars (4.5) [had a bug](https://bugs.eclipse.org/485057) in this very area. Since Neon (4.6) also ECJ reports ambiguity. Winter must be using a version >= 4.6 when switching to ECJ. – Stephan Herrmann Nov 16 '17 at 11:13

3 Answers3

14

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)
Erwin Bolwidt
  • 30,799
  • 15
  • 56
  • 79
  • interesting, if you exchange the order of your defined `foo` methods , the error message is different. – holi-java Nov 14 '17 at 04:54
  • 5
    I don’t see how the cited sentence matches you statement “the initial method selection code only looks at the number of type arguments”. It doesn’t say anything about the “number of type arguments”. §15.13 clearly says “*When more than one member method of a type has the same name, or when a class has more than one constructor, the appropriate method or constructor is selected based on the functional interface type targeted by the expression, as specified in §15.13.1.*” – Holger Nov 14 '17 at 07:35
  • 1
    This explanation doesn't make sense, as `void sleep(long, int)` does not _potentially match functional interface `Foo

    `_ – or at least not more than `void sleep(long)`. And it is also unclear what the number of type arguments have anything to do in this.

    – Didier L Nov 14 '17 at 16:57
  • Coming back on this several years later following [this question](https://stackoverflow.com/q/71209897/525036) with a better understanding. The part about “_the number of type arguments_” is actually mentioned in [15.12.2.1](https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2.1). This only refers to “_potentially applicable methods_” as defined in [15.13.1](https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.13.1). This does not check the return type, so `void sleep(long)` is _potentially applicable_ for `Foo

    `.

    – Didier L Feb 23 '22 at 23:15
  • Both `foo()` methods are thus _potentially applicable_. Moreover the exact signature of `void sleep(long, int)` does not matter: its mere existence makes `Thread::sleep` an _inexact_ method reference as per the definition at the end of section 15.13.1. Based on this, the method reference becomes not _pertinent for applicability_ in [15.12.2.2](https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2.2), and both `foo()` methods are considered as _potentially applicable by strict invocation_. 15.12.2.5 then does not allow to choose one as most specific, as per @Holger’s answer. – Didier L Feb 23 '22 at 23:27
14

The problem is that both, Thread.sleep and foo, are overloaded. So there is a circular dependency.

  • In order to find out which sleep method to use, we need to know the target type, i.e. which foo method to invoke
  • In order to find out which foo method to invoke, we need to know the functional signature of the argument, i.e. which sleep method we have selected

While it’s clear to a human reader that for this scenario only one of the 2×2 combinations is valid, the compiler must follow formal rules that work for arbitrary combinations, therefore, the language designers had to make a cut.

For the sake of usefulness of method references, there is a special treatment for unambiguous references, like your Test::sleep:

JLS §15.13.1

For some method reference expressions, there is only one possible compile-time declaration with only one possible invocation type (§15.12.2.6), 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.

Note that this distinction is similar to the distinction between implicitly typed lambda expressions (arg -> expression) and explicitly typed lambda expressions ((Type arg) -> expression).

When you look at JLS, §15.12.2.5., Choosing the Most Specific Method, you’ll see that the signature of a method reference is only used for exact method references, as when choosing the right foo, the decision for the right sleep method has not made yet.

If e is an exact method reference expression (§15.13.1), then i) for all i (1 ≤ i ≤ k), Ui is the same as Vi, and ii) one of the following is true:

  • R₂ is void.
  • R₁ <: R₂.
  • R₁ is a primitive type, R₂ is a reference type, and the compile-time declaration for the method reference has a return type which is a primitive type.
  • R₁ is a reference type, R₂ is a primitive type, and the compile-time declaration for the method reference has a return type which is a reference type.

The above rule has been stated in §15.12.2.5. for non-generic methods, redirecting to §18.5.4 for generic methods (which applies here as your foo methods are generic), containing exactly the same rule with a slightly different wording.

Since the method reference’s signature is not considered when choosing the most specific method, there is no most specific method and the invocation of foo is ambiguous. The second compiler error is the result of the strategy to continue processing the source code and potentially reporting more errors, instead of stopping the compilation right at the first error. One of the two invocations of foo caused an “incompatible types” error, if that invocation was happening, but actually that has been ruled out due to the “ambiguous invocation” error.

Community
  • 1
  • 1
Holger
  • 285,553
  • 42
  • 434
  • 765
6

Personally I see this as some sort of recursion, somehow like this: we need to resolve the method in order to find the target type, but we need to know the target type in order to resolve the method. This has something to do with a special void compatibility rule, but I admit I do not entirely get it.

Things are even funner when you have something like this:

public static void cool(Predicate<String> predicate) {

}

public static void cool(Function<String, Integer> function) {

}

And try to call it via:

cool(i -> "Test"); // this will fail compilation 

And btw if you make your lambda explicit, this will work:

foo((Long t) -> Thread.sleep(t), 1000L);
Eugene
  • 117,005
  • 15
  • 201
  • 306
  • 3
    Basically, you identified the issue. – Holger Nov 14 '17 at 09:05
  • The reason why `cool(i -> "Test")` fails to compile is because implicitly typed lambda expressions are _not pertinent to applicability_ as per [15.12.2.2](https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2.2), so both methods are considered as _applicable by strict invocation_, and the following step does not allow to choose one as more specific. – Didier L Feb 23 '22 at 23:36