OK, so method overloading is-a-bad-thing™. Now that this has been settled, let's assume I actually want to overload a method like this:
static void run(Consumer<Integer> consumer) {
System.out.println("consumer");
}
static void run(Function<Integer, Integer> function) {
System.out.println("function");
}
In Java 7, I could call them easily with non-ambiguous anonymous classes as arguments:
run(new Consumer<Integer>() {
public void accept(Integer integer) {}
});
run(new Function<Integer, Integer>() {
public Integer apply(Integer o) { return 1; }
});
Now in Java 8, I'd like to call those methods with lambda expressions of course, and I can!
// Consumer
run((Integer i) -> {});
// Function
run((Integer i) -> 1);
Since the compiler should be able to infer Integer
, why don't I leave Integer
away, then?
// Consumer
run(i -> {});
// Function
run(i -> 1);
But this doesn't compile. The compiler (javac, jdk1.8.0_05) doesn't like that:
Test.java:63: error: reference to run is ambiguous
run(i -> {});
^
both method run(Consumer<Integer>) in Test and
method run(Function<Integer,Integer>) in Test match
To me, intuitively, this doesn't make sense. There is absolutely no ambiguity between a lambda expression that yields a return value ("value-compatible") and a lambda expression that yields void
("void-compatible"), as set out in the JLS §15.27.
But of course, the JLS is deep and complex and we inherit 20 years of backwards compatibility history, and there are new things like:
Certain argument expressions that contain implicitly typed lambda expressions (§15.27.1) or inexact method references (§15.13.1) are ignored by the applicability tests, because their meaning cannot be determined until a target type is selected.
The above limitation is probably related to the fact that JEP 101 wasn't implemented all the way, as can be seen here and here.
Question:
Who can tell me exactly what parts of the JLS specifies this compile-time ambiguity (or is it a compiler bug)?
Bonus: Why were things decided this way?
Update:
With jdk1.8.0_40, the above compiles and works fine
This bug has already been reported in the JDK Bug System: https://bugs.openjdk.java.net/browse/JDK-8029718. As you can check the bug has been fixed. This fix syncs javac with the spec in this aspect. Right now javac is correctly accepting the version with implicit lambdas. To get this update, you need to clone javac 8 repo.
What the fix does is to analyze the lambda body and determine if it's void or value compatible. To determine this you need to analyze all return statements. Let's remember that from the spec (15.27.2), already referenced above:
This means that by analyzing the returns in the lambda body you can know if the lambda body is void compatible but to determine if it's value compatible you also need to do a flow analysis on it to determine that it can complete normally (14.21).
This fix also introduces a new compiler error for cases when the body is neither void nor value compatible, for example if we compile this code:
the compiler will give this output:
I hope this helps.
Lets assume we have method and method call
What methods can we legally add?
Here the parameter arity is different, specifically the
i->
part ofi->i
does not fit the parameters ofapply(T,U)
inBiFunction
, orget()
inSupplier
. So here any possible ambiguities are defined by parameter arity, not types, and not the return.What methods can't we add?
This gives a compiler error as
run(..) and run(..) have the same erasure
. So as the JVM can't support two functions with the same name and argument types, this can't be compiled. So the compiler never has to resolve ambiguities in this type of scenario as they are explicitly disallowed due the rules preexisting in the Java type system.So that leaves us with other functional types with a parameter arity of 1.
Here
run(i->i)
is valid for bothFunction
andIntUnaryOperator
, but this will refuse to compile due toreference to run is ambiguous
as both functions match this lambda. Indeed they do, and an error here is to be expected.Here this fails to compile, again due to ambiguities. Without knowing the type of
i
in this lambda it is impossible to know the type ofi.thing()
. We therefore accept that this is ambiguous and rightly fails to compile.In your example:
Here we know that both of functional types have a single
Integer
parameter, so we know that thei
ini->
must be anInteger
. So we know that it must berun(Function)
that is called. But the compiler doesn't try to do this. This is the first time that the compiler does something that we don't expect.Why does it not do this? I'd say because it is a very specific case, and inferring the type here requires mechanisms that we have not seen for any of the other above cases, because in the general case they are unable to correctly infer the type and choose the correct method.
I think you found this bug in the compiler: JDK-8029718 (or this similar one in Eclipse: 434642).
Compare to JLS §15.12.2.1. Identify Potentially Applicable Methods:
Note the clear distinction between “
void
compatible blocks” and “value-compatible blocks”. While a block might be both in certain cases, the section §15.27.2. Lambda Body clearly states that an expression like() -> {}
is a “void
compatible block”, as it completes normally without returning a value. And it should be obvious thati -> {}
is a “void
compatible block” too.And according to the section cited above, the combination of a lambda with a block that is not value-compatible and target type with a (non-
void
) return type is not a potential candidate for the method overload resolution. So your intuition is right, there should be no ambiguity here.Examples for ambiguous blocks are
as they don’t complete normally, but this is not the case in your question.