23

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.

user2864740
  • 60,010
  • 15
  • 145
  • 220
skiwi
  • 66,971
  • 31
  • 131
  • 216
  • 2
    The place in the JLS where this should be defined is [15.27.3](http://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.27.3). (Haven't looked at it in detail). – Jesper Mar 28 '15 at 22:59
  • 3
    Why do you think `Function` does not match? `?` could be `Void` as well, so it matches. – tomse Mar 28 '15 at 23:00
  • @tomse `Void` as return type has nothing to do with `void` as method type, that `Void` is just an object. – skiwi Mar 28 '15 at 23:04
  • @skiwi @Pshemo OK, I'm wrong, I tried your code and there is no compilation error it calls the `Consumer` method in both cases as you expected. (jdk.1.8.0_25) – tomse Mar 28 '15 at 23:24
  • 4
    I'd say it **must** be some sort of bug: When commenting out the `Consumer` method, it complains that it can *not* call the `Function`-method with the given lambda - thus, it can not have been ambiguous anyhow. Interesting: When declaring the argument as `(int[] data)` (thus, making it an *explicitly typed* lambda), then it properly resolves it as the `Consumer` version. When additionally inserting `return null;` in the body, it resolves to the `Function` version. So it obviously stumbles over the *implicitly typed* and *void compatible* lambda (as defined in the JLS). – Marco13 Mar 29 '15 at 00:41
  • Just noticed: Could be a duplicate of http://stackoverflow.com/questions/23430854/lambda-expression-and-method-overloading-doubts ?! (This bug should be fixed by now, however, it seems to be *very* similar at a first glance...) – Marco13 Mar 29 '15 at 00:44
  • 2
    I get the same error, but since tomse states that the code compiles under 1.8.0_25 this may be an issue specific to 1.8.0_40. Maybe try running under 1.8.0_25 to see if the code compiles? – skomisa Mar 29 '15 at 07:44
  • @tomse I tried compiling the code with JDK 1.8.0_25, 1.8.0_31 and 1.8.0_40, using NetBeans/x64/Windows 7. I got the same compilation error in all cases. What environment were you using when the code compiled? – skomisa Mar 29 '15 at 17:22
  • 1
    @skomisa I used Eclipse Luna, to compile the sources with JDK 1.8.0_25 set for the project, but I just crosschecked it on the console with jdk 1.8.0_25 and 40 and it fails in both cases. I think eclipse misled my by using its own compiler to compile the sources and uses the set JDK for execution only. Sorry... – tomse Mar 29 '15 at 18:36
  • I think there is another "simpler", and more promising way to find out if this is a "bug": write up a defect at http://bugreport.java.com/ This would have several advantages: first of all, you get your answer from those people that "really" define what is javac bug and what not (because they scrub the javac defect list). In addition: if it is really a bug, and they accept it - it will (eventually) be fixed. Starting a discussion here (and that is the only thing that can come out of your question) might get you some **opinions**; but posting a bug at orcale ... might get you a **fixed javac**! – GhostCat Mar 30 '15 at 12:51
  • 4
    @EddyG Given the times you think there is a javac bug versus the times there **actually** is one, I think it is way more appropriate to first write a question on Stackoverflow, and only then write a bug report. For the record, I did submit it a few hours ago, but I'm still awaiting whether it will be accepted. – skiwi Mar 30 '15 at 12:57
  • Eclipse up-to 4.4.1 reported the same ambiguity, but stopped doing so after the following bug got resolved (in 4.5M2 and 4.4.2): https://bugs.eclipse.org/422810 (which also has a link to a javac bug, that got fixed for 8u20). I haven't yet checked on what grounds the program is accepted by ecj. – Stephan Herrmann Jun 01 '15 at 21:38

2 Answers2

13

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.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • Can this also explain why I get the same error if I remove the `` type argument and substitute it by `int[]` in both parameters? That would seem to solve the generics problem, yet still gives that error. – skiwi Mar 30 '15 at 20:28
  • 2
    As elaborated in the linked answer (see the `comparing` example), the return type of the lambda expression/ method reference isn’t considered during overload resolution. Note that recent compilers will give you a warning about the potential ambiguity of the overloaded methods just at the declaration site without the need to find out via actually ambiguous invocations. You know “the target type isn’t known” applies to the compiler (adhering to the formal process) not to us human readers and doesn’t require Generics but just a strict ordering of the resolve steps. – Holger Mar 31 '15 at 08:31
  • The `x->( foo() )` workaround is great. However can you explain what exactly the round brackets mean in this context? Is that lambda syntax? Or is that "normal" wrapping of a java statement? – mkurz Mar 27 '20 at 09:12
  • 2
    @mkurz it's a normal wrapping of an *expression*, as `foo()` can be an expression or a statement whereas `(foo())` can only be an expression. E.g., you can write `var result = (foo());` but you can not write `(foo());` as a statement. Likewise, `{ foo(); }` can only be a statement, as you can write it where a statement is expected, but you can not write `var result = { foo(); }`. – Holger Mar 27 '20 at 09:14
0

With method references, you could have entirely different parameter types, let alone return types, and still get this if you have another method where the arity (number of arguments) matches.

For example:

static class Foo {

    Foo(Consumer<Runnable> runnableConsumer) {}

    Foo(BiFunction<Long, Long, Long> longAndLongToLong) {}
}

static class Bar {

    static void someMethod(Runnable runnable) {}

    static void someMethod(Integer num, String str) {}
}

There's no way Bar.someMethod() could ever satisfy longAndLongToLong, and yet the code below emits the same compile error regarding ambiguity:

new Foo(Bar::someMethod);

Holger's answer explains the logic and pertinent clause in the JLS behind this rather well.

What about binary compatibility?

Consider if the longAndLongToLong version of Foo constructor didn't exist but was added later in a library update, or if the two parameter version of Bar.someMethod() didn't exist but added later: Suddenly previously compiling code can break due to this.

This is an unfortunate side-effect of method overloading and similar problems have affected plain method calls even before lambdas or method references came along.

Fortunately, binary compatibility is preserved. The relevant clause is in 13.4.23. Method and Constructor Overloading:

Adding new methods or constructors that overload existing methods or constructors does not break compatibility with pre-existing binaries. The signature to be used for each invocation was determined when these existing binaries were compiled; ....

While adding a new overloaded method or constructor may cause a compile-time error the next time a class or interface is compiled because there is no method or constructor that is most specific (§15.12.2.5), no such error occurs when a program is executed, because no overload resolution is done at execution time.

antak
  • 19,481
  • 9
  • 72
  • 80