Допустим, у нас есть класс и перегруженная функция:
public class Main {
static final class A {
}
public static String g(ToIntFunction<? extends A> f) {
return null;
}
public static String g(ToDoubleFunction<? extends A> f) {
return null;
}
}
и я хочу вызвать g со ссылкой на метод функции типа A -> int:
public class Main {
static final class A {
}
public static String g(ToIntFunction<? extends A> f) {
return null;
}
public static String g(ToDoubleFunction<? extends A> f) {
return null;
}
private static int toInt(A x) {
return 2;
}
public static void main(String[] args) {
ToIntFunction<? extends A> f1 = Main::toInt;
ToDoubleFunction<? extends A> f2 = Main::toInt;
g(Main::toInt);
}
}
Это работает с javac, но не с eclipse ecj. Я отправил отчет об ошибке в ecj, но я не уверен, является ли это ошибкой ecj или javac, и попытался следовать алгоритму разрешения перегрузки, чтобы понять это. Я считаю, что этот код следует принять, потому что интуитивно понятно, что ToIntFunction
лучше соответствует toInt
, чем ToDoubleFunction
. Однако мое прочтение JLS заключается в том, что его следует отвергнуть, потому что нет никаких оснований для того, чтобы он был более конкретным.
Я должен признать, что немного потерялся в спецификации JLS и был бы признателен за помощь. Сначала я хотел вычислить тип Main::double2int
, поэтому я посмотрел 15.13.2. Тип ссылки на метод. Он не определяет тип, но определяет, когда тип совместим в разных контекстах:
Выражение ссылки на метод совместимо в контексте присваивания, контексте вызова или контексте приведения с целевым типом T, если T является типом функционального интерфейса (§9.8) и выражение соответствует типу функции наземного целевого типа, полученного из T .
Типы земли: ToIntFunction<A>
и ToDoubleFunction<A>
. toInt
возвращает целое число, совместимое по присваиванию с double, поэтому я бы сделал вывод, что ссылка на метод совместима с ToIntFunction<? extends A>
и ToDoubleFunction<? extends A>
в контексте вызова. Это можно проверить, присвоив ссылку на метод как ToIntFunction<? extends A>
, так и ToDoubleFunction<? extends A>
, которая принимается в основной функции.
Затем я посмотрел на разрешение перегрузки и нашел 15.12.2.5. Выбор наиболее конкретного метода, который имеет особый случай для ссылок на методы, чтобы решить, какая из двух перегрузок для ToIntFunction
или ToDoubleFunction
более конкретна для параметра Main::toInt
объявления времени компиляции A -> int
.
Тип функционального интерфейса S более специфичен, чем тип функционального интерфейса T для выражения e, если T не является подтипом S и выполняется одно из следующих условий (где U1 ... Uk и R1 — типы параметров и тип возвращаемого значения выражения e). тип функции захвата S, а V1...Vk и R2 — типы параметров и возвращаемый тип функции типа T):
...
Если e — точное выражение ссылки на метод (§15.13.1), то i) для всех i (1 ≤ i ≤ k) Ui совпадает с Vi, и ii) верно одно из следующих утверждений:
R2 недействителен.
R1 <: R2.
R1 — это примитивный тип, R2 — это ссылочный тип, а объявление времени компиляции для ссылки на метод имеет возвращаемый тип, который является примитивным типом.
R1 — это ссылочный тип, R2 — примитивный тип, а объявление времени компиляции для ссылки на метод имеет возвращаемый тип, который является ссылочным типом.
Очевидно, что первое условие не выполняется, поскольку R1 и R2 не пусты.
Два интерфейса ToIntFunction
и ToDoubleFunction
отличаются только типами возвращаемых данных, которые являются примитивными типами double
и int
. Для примитивных типов пункт «R1 ‹: R2» определен в 4.10.1 в соответствии с размером типов. Между double
и int
нет никакой связи, поэтому этот случай не определяет, какой тип является более конкретным.
Последние два пункта тоже не подходят, потому что ни один из двух функциональных интерфейсов не имеет возвращаемого значения ссылочного типа.
Кажется, что нет правила для случая, когда два функциональных интерфейса возвращают примитивы и код следует отбрасывать как неоднозначный. Однако javac принимает код, и я ожидаю, что он это сделает. Поэтому мне интересно, отсутствует ли это в JLS.