A method is ambiguous upon passing a lambda expression in Java

Nikolas :

Let's have a functional interface Functional (for sake of brevity, I omitted the implementation and simplified the case):

@FunctionalInterface 
public interface Functional<E> { 

    void perform(E e);

    default <T extends Number> void method(E e, T t)  { }
    default <T extends Number> void method(E e, Function<E, T> function) { }
} 

And a simple piece of code:

Functional<String> functional = (string) -> {};
functional.method("string", (string) -> 1);

Why is the method method() ambiguous since there is lambda passed as a parameter? This should be easily distinguished.

Eclipse:

The method method(String, Function<String,Integer>) is ambiguous for the type Functional<String>

This is reproducible on IntelliJIdea as well.

Javac output (thanks to @AndyTurner):

Main.java:21: error: reference to method is ambiguous
        functional.method("string", (string) -> 1);
                  ^
  both method <T#1>method(E,T#1) in Functional and method <T#2>method(E,Function<E,T#2>) in Functional match
  where T#1,E,T#2 are type-variables:
    T#1 extends Number declared in method <T#1>method(E,T#1)
    E extends Object declared in interface Functional
    T#2 extends Number declared in method <T#2>method(E,Function<E,T#2>)

and

Main.java:21: error: incompatible types: cannot infer type-variable(s) T
        functional.method("string", (string) -> 1);
                         ^
    (argument mismatch; Number is not a functional interface)
  where T,E are type-variables:
    T extends Number declared in method <T>method(E,T)
    E extends Object declared in interface Functional

Edit: An interesting fact. When I replace default <T extends Number> with <T>, it works. The T seems cannot extend Number, Throwable etc...

default <T> void method(E e, T t)  { }
default <T> void method(E e, Function<E, T> function) { }

Edit 2: When I make the generic type T to the interface declaration, it works as well:

@FunctionalInterface 
public interface Functional<E, T extends Number> { 

    void get(E e);

    default void method(E e, Function<E, T> function) { }
    default void method(E e, T t)  { }
} 
Oleksandr Pyrohov :

There are multiple tickets (here, here and here) that contain similar code snippets. These tickets are resolved as "Not an issue" and the explanation is as follows:

JLS 15.12.2.1:

An expression is potentially compatible with a target type according to the following rules:

  • [...]
  • A lambda expression or a method reference expression is potentially compatible with a type variable if the type variable is a type parameter of the candidate method.

So, both methods method are potentially compatible in this case.

Also, the lambda (string) -> 1 is not pertinent to applicability, because:

JLS 15.2.2.2:

An argument expression is considered pertinent to applicability for a potentially applicable method m unless it has one of the following forms

  • [...]
  • If m is a generic method and the method invocation does not provide explicit type arguments, an explicitly typed lambda expression or an exact method reference expression for which the corresponding target type (as derived from the signature of m) is a type parameter of m.

Finally:

Since the method has a type-parameter where the lambda is passed as argument, the lambda is skipped from the applicability check - meaning both are applicable - hence the ambiguity.

Possible workaround - cast the argument when calling the method:

functional.method("string", (Function<String, Number>) (string) -> 1);

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=36410&siteId=1