Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reference to method is ambiguous when using lambdas and generics

Tags:

I am getting an error on the following code, which I believe should not be there... Using JDK 8u40 to compile this code.

public class Ambiguous {
    public static void main(String[] args) {
        consumerIntFunctionTest(data -> {
            Arrays.sort(data);
        }, int[]::new);

        consumerIntFunctionTest(Arrays::sort, int[]::new);
    }

    private static <T> void consumerIntFunctionTest(final Consumer<T> consumer, final IntFunction<T> generator) {

    }

    private static <T> void consumerIntFunctionTest(final Function<T, ?> consumer, final IntFunction<T> generator) {

    }
}

The error is the following:

Error:(17, 9) java: reference to consumerIntFunctionTest is ambiguous both method consumerIntFunctionTest(java.util.function.Consumer,java.util.function.IntFunction) in net.tuis.ubench.Ambiguous and method consumerIntFunctionTest(java.util.function.Function,java.util.function.IntFunction) in net.tuis.ubench.Ambiguous match

The error occurs on the following line:

consumerIntFunctionTest(Arrays::sort, int[]::new);

I believe there should be no error, as all Arrays::sort references are of type void, and none of them return a value. As you can observe, it does work when I explicitly expand the Consumer<T> lambda.

Is this really a bug in javac, or does the JLS state that the lambda cannot automatically be expanded in this case? If it is the latter, I would still think it is weird, as consumerIntFunctionTest with as first argument Function<T, ?> should not match.

like image 278
skiwi Avatar asked Mar 28 '15 22:03

skiwi


People also ask

Why method reference is better than lambda?

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.

Which can be used instead of lambda expression?

How to replace lambda expression with method reference in Java 8. If you are using a lambda expression as an anonymous function but not doing anything with the argument passed, you can replace lambda expression with method reference.


1 Answers

In your first example

consumerIntFunctionTest(data -> {         Arrays.sort(data);     }, int[]::new); 

the lambda expression has a void-compatible block which can be identified by the structure of the expression without the need to resolve the actual types.

In contrast, in the example

consumerIntFunctionTest(Arrays::sort, int[]::new); 

the method reference has to be resolved to find out, whether it conforms to either, a void function (Consumer) or a value returning function (Function). The same applies to the simplified lambda expression

consumerIntFunctionTest(data -> Arrays.sort(data), int[]::new); 

which could be both, void- compatible or value- compatible, depending on the resolved target method.

The problem is that resolving the method requires knowledge about the required signature, which ought to be determined via target typing, but the target type isn’t known until the type parameters of the generic method are known. While in theory both could be determined at once, the (still being awfully complex) process has been simplified in the specification in that method overload resolution is performed first and type inference is applied last (see JLS §15.12.2). Hence, the information that type inference could provide cannot be used for solving overload resolution.

But note that the first step described in 15.12.2.1. Identify Potentially Applicable Methods contains:

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

  • A lambda expression (§15.27) is potentially compatible with a functional interface type (§9.8) if all of the following are true:

    • The arity of the target type's function type is the same as the arity of the lambda expression.

    • If the target type's function type has a void return, then the lambda body is either a statement expression (§14.8) or a void-compatible block (§15.27.2).

    • If the target type's function type has a (non-void) return type, then the lambda body is either an expression or a value-compatible block (§15.27.2).

  • A method reference expression (§15.13) is potentially compatible with a functional interface type if, where the type's function type arity is n, there exists at least one potentially applicable method for the method reference expression with arity n (§15.13.1), and one of the following is true:

  • The method reference expression has the form ReferenceType :: [TypeArguments] Identifier and at least one potentially applicable method is i) static and supports arity n, or ii) not static and supports arity n-1.

  • The method reference expression has some other form and at least one potentially applicable method is not static.

The definition of potential applicability goes beyond a basic arity check to also take into account the presence and "shape" of functional interface target types. In some cases involving type argument inference, a lambda expression appearing as a method invocation argument cannot be properly typed until after overload resolution.

So your in first example one of the methods is sorted out by the lambda’s shape while in case of a method reference or a lambda expression consisting of a sole invocation expression, both potentially applicable methods endure this first selection process and yield an “ambiguous” error before type inference can kick in to aid finding a target method to determine if it’s a void or value returning method.

Note that like using x->{ foo(); } to make a lambda expression explicitly void-compatible, you can use x->( foo() ) to make a lambda expression explicitly value-compatible.


You may further read this answer explaining that this limitation of combined type inference and method overload resolution was a deliberate (but not easy) decision.

like image 93
Holger Avatar answered Sep 23 '22 12:09

Holger