Существует много сложности на пересечении разрешения перегрузки и вывода типа. В спецификации current draft спецификации лямбда есть все детали. Разделы F и G разрешают перегрузку и вывод типа соответственно. Я не притворяюсь, что все это понимаю. Сводные разделы во введении достаточно понятны, и я рекомендую людям читать их, особенно резюме разделов F и G, чтобы понять, что происходит в этой области.
Чтобы кратко рассмотреть проблемы, рассмотрите вызов метода с некоторыми аргументами при наличии перегруженных методов. Разрешение перегрузки должно выбрать правильный метод для вызова. «Форма» метода (арность или количество аргументов) наиболее значительна; очевидно, вызов метода с одним аргументом не может решить метод, который принимает два параметра. Но перегруженные методы часто имеют одинаковое количество параметров разных типов. В этом случае типы начинают иметь значение.
Пусть есть два перегруженных метода:
void foo(int i);
void foo(String s);
и некоторый код имеет метод следующий вызов:
foo("hello");
Очевидно, что это решает для второго метода, основанного на типе аргумента являющегося прошло. Но что, если мы делаем разрешение перегрузки, а аргумент - лямбда? (Особенно тот, чьи типы неявны, который полагается на вывод типа для установления типов.) Напомним, что тип выражения лямбда выведен из целевого типа, то есть типа, ожидаемого в этом контексте. К сожалению, если у нас есть перегруженные методы, у нас нет целевого типа, пока мы не разрешим перегруженный метод, который мы будем называть. Но поскольку у нас еще нет типа для выражения лямбда, мы не можем использовать его тип, чтобы помочь нам во время разрешения перегрузки.
Давайте рассмотрим пример здесь. Рассмотрим интерфейс A
и абстрактный класс B
, как определено в примере.У нас есть класс C
, который содержит две перегрузки, а затем какой-то код вызывает метод apply
и передает его лямбда:
public void apply(A a)
public B apply(B b)
c.apply(x -> System.out.println(x));
apply
Оба перегруженных имеют одинаковое количество параметров. Аргументом является лямбда, которая должна соответствовать функциональному интерфейсу. A
и B
являются фактическими типами, поэтому очевидно, что A
является функциональным интерфейсом, тогда как B
нет, поэтому результатом разрешения перегрузки является apply(A)
. На данный момент мы теперь имеем целевой тип A
для лямбда, и вывод типа для x
продолжается.
Теперь изменение:
public void apply(A a)
public <T extends B> T apply(T t)
c.apply(x -> System.out.println(x));
Вместо фактического типа, вторая перегрузка apply
является общим типом переменной T
. Мы не сделали вывод типа, поэтому мы не учитываем T
, по крайней мере, до тех пор, пока не будет достигнуто разрешение перегрузки. Таким образом, обе перегрузки по-прежнему применимы, и не являются наиболее конкретными, а компилятор испускает ошибку, вызывающую неоднозначность.
Вы можете возразить, что, так как мы знаем что T
имеет тип, связанный из B
, который является классом, а не функциональный интерфейс, лямбда не может применить к этой перегрузки, при этом она должна быть исключена во время разрешения перегрузки, устраняя двусмысленность. Я не из тех, у кого есть этот аргумент. :-) Это может быть ошибка в компиляторе или, возможно, даже в спецификации.
Я знаю, что эта область прошла через кучу изменений во время разработки Java 8. Ранее варианты пытались принести больше информации о проверке типов и выводах в фазу разрешения перегрузки, но их было сложнее реализовать, указать, и понять. (Да, еще труднее понять, чем сейчас). К сожалению, проблемы продолжали возникать. Было решено упростить ситуацию, уменьшив диапазон вещей, которые могут быть перегружены.
Тип вывода и перегрузка всегда находятся в оппозиции; многие языки с типом вывода с первого дня запрещают перегрузку (за исключением, может быть, arity.) Поэтому для конструкций, таких как неявные лямбды, которые требуют вывода, кажется разумным отказаться от чего-то в перегрузке, чтобы увеличить диапазон случаев, когда могут использоваться неявные лямбды ,
- Brian Goetz, Lambda Expert Group, 9 Aug 2013
(Это было довольно спорное решение Обратите внимание, что было 116 сообщений в этой теме, и есть несколько других потоков, которые обсуждают этот вопрос.).
Один из Последствия этого решения заключались в том, что некоторые API были изменены, чтобы избежать перегрузки, например, the Comparator API. Ранее метод Comparator.comparing
имел четыре перегруженных:
comparing(Function)
comparing(ToDoubleFunction)
comparing(ToIntFunction)
comparing(ToLongFunction)
Проблема заключалась в том, что эти перегруженные различаются только типом возвращаемого значения лямбда, и мы на самом деле никогда не получил логического вывода типа работать здесь с неявно типизированных лямбды. Чтобы использовать их, всегда нужно было бы указать или предоставить аргумент явного типа для лямбда.Эти API впоследствии были изменены на:
comparing(Function)
comparingDouble(ToDoubleFunction)
comparingInt(ToIntFunction)
comparingLong(ToLongFunction)
, который несколько неуклюжий, но он совершенно недвусмыслен. Аналогичная ситуация возникает с Stream.map
, mapToDouble
, mapToInt
и mapToLong
, а также в нескольких других местах вокруг API.
Суть в том, что получение разрешения перегрузки прямо при наличии вывода типа очень сложно в целом и что разработчики языка и компилятора отменили питание от разрешения перегрузки, чтобы сделать вывод типа более эффективным. По этой причине API Java 8 избегают перегруженных методов, в которых предполагается использовать неявно типизированные лямбда.
Вы можете явно вызвать первый метод с 'с. применяются (x -> System.out.println (x)); '. Но похоже, что он должен работать без него ... – assylias
Я бы предположил, что ответ таков, что подтип T of B может реализовать A. –
@ user2580516 Да, это может быть проблемой, я не думал об этой возможности. – schenka7