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
i
is the first (and only) argument to eitherConsumer.accept()
orFunction.apply()
. This, per se, might be ambiguous. But given that one lambda evaluates to a "value-compatible" type (Function
) and the other evaluates to a "void-compatible" type (Consumer
), I'd intuitively think that there is no ambiguity – Treatisebeta 102
and earlier). – Gaptoothedrun((Integer i) -> {})
is a Consumer. So although it could be both Function<Integer,Integer> or Consumer<Integer>, Consumer<Integer> is the best match and the compiler uses that. The question is, why does the compiler only do this when you specify(Integer i)
and not justi
. – Encirclei -> {}
can never evaluate toFunction
, because it is "void-compatible".i -> 1
can never evaluate toConsumer
, because it is "value-compatible". For each call, only one of the overloaded methods is even applicable in my opinion. As @Encircle also pointed out, the ambiguity can be resolved by explicitly specifying identical function argument types(Integer i)
. – Treatiserun((Consumer<Integer>) (Integer i) -> {1});
it will not compile. That leads me to believe that this must be a compiler bug since there really is no ambiguity between the two lambdas. – Encirclei->i.thing()
could bevoid
or a value, we don't know unless we know whati
is. It seems like the compiler is unwilling to reason about this, despite both lambdas havingInteger
parameters. – Nnwi -> 1
andi -> {}
), whereas lambdas in general could be ambiguous (i -> intFunction()
andi -> voidFunction()
). Now proove it, and I'll accept your answer :-) – Treatise