Why is this Java method call considered ambiguous?
Asked Answered
B

2

16

I've come across a strange error message that I believe may be incorrect. Consider the following code:

public class Overloaded {
    public interface Supplier {
        int get();
    }

    public interface Processor {
        String process(String s);
    }

    public static void load(Supplier s) {}
    public static void load(Processor p) {}

    public static int genuinelyAmbiguous() { return 4; }
    public static String genuinelyAmbiguous(String s) { return "string"; }

    public static int notAmbiguous() { return 4; }
    public static String notAmbiguous(int x, int y) { return "string"; }

    public static int strangelyAmbiguous() { return 4; }
    public static String strangelyAmbiguous(int x) { return "string"; }
}

If I have a method that looks like this:

// Exhibit A
public static void exhibitA() {
    // Genuinely ambiguous: either choice is correct
    load(Overloaded::genuinelyAmbiguous); // <-- ERROR
    Supplier s1 = Overloaded::genuinelyAmbiguous;
    Processor p1 = Overloaded::genuinelyAmbiguous; 
}

The error we get makes perfect sense; the parameter to load() can be assigned to either, so we get an error that states the method call is ambiguous.

Conversely, if I have a method that looks like this:

// Exhibit B
public static void exhibitB() {
    // Correctly infers the right overloaded method
    load(Overloaded::notAmbiguous);
    Supplier s2 = Overloaded::notAmbiguous;
    Processor p2 = Overloaded::notAmbiguous; // <-- ERROR
}

The call to load() is fine, and as expected, I cannot assign the method reference to both Supplier and Processor because it is not ambiguous: Overloaded::notAmbiguous cannot be assigned to p2.

And now the weird one. If I have a method like this:

// Exhibit C
public static void exhibitC() {
    // Complains that the reference is ambiguous
    load(Overloaded::strangelyAmbiguous); // <-- ERROR
    Supplier s3 = Overloaded::strangelyAmbiguous;
    Processor p3 = Overloaded::strangelyAmbiguous; // <-- ERROR
}

The compiler complains that the call to load() is ambiguous (error: reference to load is ambiguous), but unlike Exhibit A, I cannot assign the method reference to both Supplier and Processor. If it were truly ambiguous, I feel I should be able to assign s3 and p3 to both overloaded parameter types just as in Exhibit A, but I get an error on p3 stating that error: incompatible types: invalid method reference. This second error in Exhibit C makes sense, Overloaded::strangelyAmbiguous isn't assignable to Processor, but if it isn't assignable, why is it still considered ambiguous?

It would seem that the method reference inference only looks at the arity of the FunctionalInterface when determining which overloaded version to select. In the variable assignment, arity and type of parameters are checked, which causes this discrepancy between the overloaded method and the variable assignment.

This seems to me like a bug. If it isn't, at least the error message is incorrect, since there is arguably no ambiguity when between two choices only one is correct.

Benedix answered 19/7, 2019 at 18:53 Comment(4)
I can reproduce, but please can you remove "exhibit A" and "exhibit B"? They don't really add to the understanding of the problem, I don't think. Exhibit C stands fine by itself.Plausive
Bear in mind a human has to maintain this code. Even if the compiler could figure it out, I'd prefer names like loadProcessor and loadSupplier so I don't have to think about it.Excel
@Excel Bear in mind that, as human, we like to understand how it actually worksAbash
@ArnaudClaudel Yes, I know, that's why it's a comment, not an answer. It's fine to ask, but the question is largely academic. I hope no one writes production code this way.Excel
A
6

Your question is very similar to this one.

The short answer is:

Overloaded::genuinelyAmbiguous;
Overloaded::notAmbiguous;
Overloaded::strangelyAmbiguous;

all these method references are inexact (they have multiple overloads). Consequently, according to the JLS §15.12.2.2., they are skipped from the applicability check during overload resolution, which results in ambiguity.

In this case, you need to specify the type explicitly, for example:

load((Processor) Overloaded::genuinelyAmbiguous);
load(( Supplier) Overloaded::strangelyAmbiguous);
Apperception answered 19/7, 2019 at 20:7 Comment(0)
I
4

Method references and overloading, just... don't. Theoretically, you are more than correct - this should be fairly easy for a compiler to deduce, but let's not confuse humans and compilers.

The compiler sees a call to load and says : "hey, I need to call that method. Cool, can I? Well there are 2 of them. Sure, let's match the argument". Well the argument is a method reference to an overloaded method. So the compiler is getting really confused here, it basically says that : "if I could tell which method reference you are pointing to, I could call load, but, if I could tell which load method you want to call, I could infer the correct strangelyAmbiguous", thus it just goes in circles, chasing it's tale. This made up decision in a compilers "mind" is the simplest way I could think to explain it. This brings a golden bad practice - method overloading and method references are a bad idea.

But, you might say - ARITY! The number of arguments is the very first thing a compiler does (probably) when deciding if this is an overload or not, exactly your point about:

Processor p = Overloaded::strangelyAmbiguous;

And for this simple case, the compiler could indeed infer the correct methods, I mean we, humans can, should be a no brainer for a compiler. The problem here is that this is a simple case with just 2 methods, what about 100*100 choices? The designers had to either allow something (let' say up to 5*5 and allow resolution like this) or ban that entirely - I guess you know the path they took. It should be obvious why this would work if you would have used a lambda - arity is right there, explicit.

About the error message, this would not be anything new, if you play enough with lambdas and method references, you will start to hate the error message : "a non static method cannot be referenced from a static context", when there is literally nothing to do with that. IIRC these error messages have improved from java-8 and up, you never know if this error message would improve also in java-15, let's say.

Iranian answered 20/7, 2019 at 13:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.