Разрешение перегрузки со ссылками на методы и специализациями интерфейса функций для примитивных типов

Допустим, у нас есть класс и перегруженная функция:

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.


person Jens    schedule 25.08.2017    source источник


Ответы (1)


Для примитивных типов пункт «R1 ‹: R2» определен в 4.10.1 в соответствии с размером типов. Между double и int нет никакой связи, поэтому этот случай не определяет, какой тип является более конкретным.

Это не тот случай; double на самом деле является супертипом int.

Параграф 4.10:

Супертипы типа получаются рефлексивным и транзитивным замыканием над прямым отношением супертипа, обозначаемым S >₁ T, которое определяется правилами, приведенными далее в этом разделе. Мы пишем S :> T, чтобы указать, что отношение супертипа сохраняется между S и T.

Параграф 4.10.1:

Следующие правила определяют прямое отношение супертипа между примитивными типами:

double >₁ float

float >₁ long

long >₁ int

Отношение supertype, являющееся рефлексивным и транзитивным замыканием отношения direct supertype, означает, что из (double >₁ float) ∧ (float >₁ long) ∧ (long >₁ int) следует double :> int.

person Anton    schedule 26.08.2017
comment
Хорошо поймал. И очень просто... Таким образом, можно сделать вывод, что ToIntFunction лучше подходит, чем ToDoubleFunction, и код соответствует требованиям. В этом случае это ошибка ecj. - person Jens; 26.08.2017