Почему этот вызов метода Java считается неоднозначным?

Я обнаружил странное сообщение об ошибке, которое, по моему мнению, может быть неверным. Рассмотрим следующий код:

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"; }
}

Если у меня есть метод, который выглядит следующим образом:

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

Ошибка, которую мы получаем, имеет смысл; параметру load() можно присвоить любой из них, поэтому мы получаем ошибку, в которой говорится, что вызов метода неоднозначен.

И наоборот, если у меня есть метод, который выглядит следующим образом:

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

Вызов load() в порядке, и, как и ожидалось, я не могу назначить ссылку на метод как для Supplier и для Processor потому что он не является неоднозначным: Overloaded::notAmbiguous не может быть назначено для p2.

А теперь странный. Если у меня есть такой метод:

// 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
}

Компилятор жалуется, что вызов load() неоднозначен (error: reference to load is ambiguous), но, в отличие от приложения A, я не могу присвоить ссылку на метод как Supplier и Processor. Если бы это было действительно неоднозначно, я чувствовал бы, что я мог бы назначить s3 и p3 для обоих перегруженных типов параметров так же, как в Приложении A, но я получаю ошибку на p3 заявляющую, что error: incompatible types: invalid method reference. Эта вторая ошибка в Приложении C имеет смысл: Overloaded::strangelyAmbiguous не присваивается Processor, но если он не присваивается, почему он все еще считается неоднозначным?

Казалось бы, что вывод ссылки на метод только смотрит на арность FunctionalInterface при определении, какую перегруженную версию выбрать. В назначении переменной проверяется арность и тип параметров, что вызывает это несоответствие между перегруженным методом и назначением переменной.

Это кажется мне ошибкой. Если это не так, то, по крайней мере, сообщение об ошибке неверно, поскольку, возможно, нет никакой двусмысленности, когда между двумя вариантами верен только один.

Ответы

Ответ 1

Ваш вопрос очень похож на этот.

Краткий ответ:

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

все эти ссылки на методы неточны (они имеют несколько перегрузок). Следовательно, в соответствии с JLS §15.12.2.2. они пропускаются из проверки применимости во время разрешения перегрузки, что приводит к неоднозначности.

В этом случае вам нужно явно указать тип, например:

load((Processor) Overloaded::genuinelyAmbiguous);
load(( Supplier) Overloaded::strangelyAmbiguous);

Ответ 2

Ссылки на методы и перегрузка, просто... нет. Теоретически, вы более чем правы - это должно быть довольно легко для компилятора, но не следует путать людей и компиляторы.

Компилятор видит вызов load и говорит: "Эй, мне нужно вызвать этот метод. Круто, я могу? Ну, их 2. Конечно, давайте сопоставим аргумент". Ну, аргумент - это ссылка на перегруженный метод. Таким образом, компилятор действительно запутывается здесь, в основном он говорит: "Если бы я мог сказать, на какую ссылку метода вы указываете, я мог бы вызвать load, но, если бы я мог сказать, какой метод load вы хотите вызвать, я мог бы вывести исправить strangelyAmbiguous " strangelyAmbiguous ", таким образом, он просто идет по кругу, преследуя его сказку. Это принятое решение в "уме" компилятора - самый простой способ объяснить это. Это приносит золотую дурную практику - перегрузка методов и ссылки на методы - плохая идея.

Но, вы могли бы сказать - ARITY! Количество аргументов - это самое первое, что делает компилятор (вероятно), когда решает, является ли это перегрузкой или нет, в точности ваша точка зрения:

Processor p = Overloaded::strangelyAmbiguous;

И в этом простом случае компилятор действительно может вывести правильные методы, я имею в виду, что мы, люди, можем легко понять компилятор. Проблема здесь в том, что это простой случай с двумя способами, а как насчет 100 * 100 вариантов? Дизайнеры должны были либо разрешить что-либо (скажем, до 5 * 5 и разрешить подобное разрешение), либо полностью запретить - я думаю, вы знаете путь, по которому они пошли. Должно быть понятно, почему это сработало бы, если бы вы использовали лямбда-выражения - это прямо, явно.

Что касается сообщения об ошибке, это не будет чем-то новым, если вы поиграете с лямбдами и ссылками на методы, вы начнете ненавидеть сообщение об ошибке: "на статический метод нельзя ссылаться из статического контекста", когда буквально ничего нет делать с этим. IIRC эти сообщения об ошибках улучшились с java-8 и выше, вы никогда не знаете, улучшится ли это сообщение об ошибке также в java-15, скажем так.