Given the following identity functions:
<T> Consumer<T> f(Consumer<T> c) { return c; } // (1) <T,R> Function<T,R> f(Function<T, R> c) { return c; } // (2)
I observe the following behaviour in JDK 11 and JDK 17:
void _void() {} f(x -> {}); // okay, dispatches to (1) f(x -> { return; }); // okay, dispatches to (1) f(x -> { _void(); }); // okay, dispatches to (1) f(x -> _void()); // should dispatch to (1) | Error: | reference to f is ambiguous | both method f(java.util.function.Function<java.lang.Object,java.lang.Object>) in and method f(java.util.function.Consumer<java.lang.Object>) in match int _one() { return 1; } f(x -> 1); // okay, dispatches to (2) f(x -> { return 1; }); // okay, dispatches to (2) f(x -> { return _one(); }); // okay, dispatches to (2) f(x -> _one()); // should dispatch to (2) | Error: | reference to f is ambiguous | both method <T,R>f(java.util.function.Function<T,R>) in and method <T>f(java.util.function.Consumer<T>) in match
Why can’t the compiler resolve these symbols by using the return type of the expression? The curly brace versions work fine, and I would have thought they would be the more difficult cases. I understand that you can explicity cast the lambda function, but that defeats the purpose of what I am trying to achieve.
Advertisement
Answer
x -> _void()
and x -> one()
are expected to be compatible with Consumer<T>
(with the result of one()
to be discarded).
When the lambda body is of a block type, the compiler additionally checks the “return” compatibility. The JLS is rather explicit about void/value compatibility for block bodies:
A block lambda body is void-compatible if every return statement in the block has the form return;. A block lambda body is value-compatible if it cannot complete normally (ยง14.21) and every return statement in the block has the form return Expression;.
While that doesn’t say why the single-expression bodies fail, it says exactly why block bodies compile: the compiler looks at the return
forms to judge on those bodies’ compatibility with Consumer
or Function
(in this case).
For the method invocation expressions, the fact that this is allowed:
Consumer<Integer> c = x -> one(); //discarded result Function<T, Integer> f = x -> one(); //returned result
doesn’t enable the compiler to resolve the conflict that you observed. You can rewrite the same lambda expression with block bodies to resolve the conflict, and that’s simply because block bodies are checked differently, by spec.
I guess I’m trying to say that the more natural question is “why block bodies compile at all in this case”, given that we normally don’t expect return types (forms?) to participate in overload resolution. But lambda expressions’ congruence with types is something else, isn’t it… I think this (that block type helps target type inference) is the special behavior.