I have an overloaded method that takes two different functional interfaces as parameters (Runnble
and Supplier
). System.out.println
is clearly only compatible with Runnable
, because it is a void
method. Yet the compiler still claims that the call is ambiguous. How is that possible?
import java.util.function.Supplier;
public class GenericLambdas {
public static void main(String[] args) {
wrap(System.out::println); // Compiler error here
wrap(() -> {}); // No error
wrap(System.out::flush); // No error
}
static <R> void wrap(Supplier<R> function) {}
static void wrap(Runnable function) {}
}
Compiler output:
Error:Error:line (5)java: reference to wrap is ambiguous
both method <R>wrap(java.util.function.Supplier<R>) in GenericLambdas and method wrap(java.lang.Runnable) in GenericLambdas match
Error:Error:line (5)java: incompatible types: cannot infer type-variable(s) R
(argument mismatch; bad return type in method reference
void cannot be converted to R)
Based on the second error (argument mismatch, void cannot be converted to R
), shouldn't the compiler be able to disambiguate the call? That would then take care of both compiler errors (as it will neither be ambiguous, nor will it try to convert void to R).
And why are () -> {}
and System.out::flush
able to resolve? They have the same signature as System.out.println
. Granted that System.out.println
is overloaded with versions that take an argument, but none of those overloaded versions match either Supplier
or Runnable
, so I don't see how they would be relevant here.
It seems that this does compile and run with the Eclipse compiler. Which compiler is correct, or is either behavior allowed?
The type of System. out. println is not a functional interface, since System.
The Java compiler automatically identifies the functional interface. The lambda expressions are used primarily to define the inline implementation of a functional interface. It eliminates the need for an anonymous class and gives a simple and powerful functional programming capability to Java.
An interface which has Single Abstract Method can be called as Functional Interface. Runnable, Comparator,Cloneable are some of the examples for Functional Interface.
Runnable, ActionListener, Comparable are some of the examples of functional interfaces.
Finding the appropriate version of println
ignores the return type. See JLS 15.13.2. It wouldn't make sense to include it, because there can't be two versions of a method with the same parameters but a different return type. But now the compiler's got a problem: Both Supplier#get
and Runnable#run
expect the same parameters (none). So there's a println
that would match for both. Keep in mind that in this stage, the compiler only tries to find a method. The compiler basically runs into the same problem as in this code:
public static void main(String[] args) {
test(System.out::println);
}
public static void test(Runnable r) {}
public static void test(Consumer<String> r) {}
println()
matches Runnable#run
and println(String)
matches Consumer#accept
. We didn't provide a target type, so the situation is ambiguous.
After a method has been chosen, the target type can be inferred correctly, and in this stage the return type is relevant. See JLS 15.13.2. So this code would fail, of course:
public static void main(String[] args) {
wrap(System.out::println);
}
static <R> void wrap(Supplier<R> function) {}
The compiler immediately throws an error when it detects the ambiguity. Although a bug report has been raised and accepted for this behavior, the comments there indicate that Oracle's JDK may be adhering to the JLS more faithfully than ECJ (despite its nicer behavior). A later bug report, raised on the back of this SO question, was resolved as "Not an issue", indicating that, after internal debate, Oracle has decided that javac
's behavior is correct.
It seems like a bug in javac
compiler. The problem lies in overloaded println()
method. It has different implementations based on type you write: int
, long
, String
, etc. So expression: System.out::println
has 10 methods to choose from. One of them can be deduced to Runnable
, 9 others to Consumer<T>
.
Somehow, javac
compiler is not able to infer correct method implementation from this expression. And wrap(() -> {})
compiles correctly because this expression has only one possible interpretation – Runnable
.
I'm not sure is it allowed to have such expressions under JLS rules. But following code compiles correctly using javac
(and runs without runtime issues):
wrap((Runnable)System.out::println);
It seems like this cast provide required information to the compiler to deduce type correctly, which is kind of strange. I didn't know cast expressions can be used in type inference.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With