Weird compilation issue

Viewed 62

The following class will not compile:

public class Thing {

    public static <T> T foo(java.util.function.Supplier<T> supplier) {
        return supplier.get();
    }

    public static <T> T bar(java.util.function.Function<Integer, T> function) {
        return function.apply(42);
    }

    public static void main(String... args) {
        System.out.println(foo(() -> "hello")); // 1
        System.out.println(bar(any -> "hello")); // 2 !!!
        System.out.println(bar((Integer any) -> "hello")); // 3
        System.out.println(Thing.<String>bar(any -> "hello")); // 4
        println(bar(any -> "hello")); // 5
    }

    private static void println(String string) {
        System.out.println(string);
    }
}

The issue is on line [2] in the main() method (all other lines are fine):

[ERROR] .../Thing.java:[13,19] reference to println is ambiguous
    both method println(char[]) in java.io.PrintStream and method println(java.lang.String) in java.io.PrintStream match
[ERROR] .../Thing.java:[13,31] incompatible types: inference variable T has incompatible bounds
    lower bounds: char[],java.lang.Object
    lower bounds: java.lang.String

I don't understand why the compiler thinks there is an ambiguity on the return type of bar() (char[] or String) and can't decide which flavor of println() method should be used. On line [1], it can infer that the return type of foo() is String and on line [3], I don't understand how specifying the type of any (Integer) helps because it could not be anything else given the signature of the bar() method.

1 Answers

This is because the implicitly typed lambda any -> "hello" is not pertinent to applicability, and so it is practically ignored when determining what type the call bar(any -> "hello") should be (See Invocation Type Inference sections of the JLS), in order to select the correct println call.

The justification can be found from the first link:

The meaning of an implicitly typed lambda expression or an inexact method reference expression is sufficiently vague prior to resolving a target type that arguments containing these expressions are not considered pertinent to applicability; they are simply ignored (except for their expected arity) until overload resolution is finished.

So other than the fact that we are passing it to System.out.println, there are practically no bounds on the return type of bar at all - it could be any reference type. The compiler tries to infer which overload we are trying to call using the rules in that second link above, but println(String) and println(char[]) both seem like valid overloads!

The reasons why the other lines work are:

  • System.out.println(foo(() -> "hello"));

  • System.out.println(bar((Integer any) -> "hello"));

    these lambdas are all pertinent to applicability, because they are explicitly typed lambdas (though this rule has some exceptions). Note that a lambda with no parameters counts as an explicitly typed lambda.

  • System.out.println(Thing.<String>bar(any -> "hello"));

    Since you have provided the type parameter of bar, the compiler does not need to determine it using type inference. It can now be trivially determined that bar returns String, so you must be calling println(String).

  • println(bar(any -> "hello"));

    There is only one overload of println to pick, so even though we can't know that bar returns String from the lambda, we can still do it by seeing that the bar call is being passed to println(String).

I recommend that you have a look at the list of expressions that are not pertinent to applicability and play around with them :)

Related