Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compiler not inferring System.out::println functional interface

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.

EDIT:

It seems that this does compile and run with the Eclipse compiler. Which compiler is correct, or is either behavior allowed?

like image 412
Yosef Weiner Avatar asked Oct 14 '15 19:10

Yosef Weiner


People also ask

Is system out a functional interface?

The type of System. out. println is not a functional interface, since System.

How does the Java compiler recognize a functional interface?

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.

Is cloneable a functional interface?

An interface which has Single Abstract Method can be called as Functional Interface. Runnable, Comparator,Cloneable are some of the examples for Functional Interface.

Which of these interfaces are functional interfaces?

Runnable, ActionListener, Comparable are some of the examples of functional interfaces.


2 Answers

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.

like image 55
a better oliver Avatar answered Sep 28 '22 18:09

a better oliver


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.

like image 24
Denis Bazhenov Avatar answered Sep 28 '22 19:09

Denis Bazhenov