Почему компилятор Java не может определить Iterable <String>из ограничений Iterableи () -> (Итератор <String>) - PullRequest
6 голосов
/ 24 октября 2019

Справочная информация: я недавно написал ответ , где я предложил написать следующий код:

Files.write(Paths.get("PostgradStudent.csv"),
        Arrays.stream(PGstudentArray).map(Object::toString).collect(Collectors.toList()),
        StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);

После некоторых мыслей я сказал: «Мне не нужен список здесь, Мне просто нужно Iterable<? extends CharSequence> ".
Поскольку Stream<T> имеет метод Iterator<T> iterator(), я тогда подумал, ну, это просто:

Iterable<? extends CharSequence> iterable = () -> Arrays.stream(arr).map(Object::toString).iterator();

(я извлек его в локальную переменнуюдля этого вопроса я хотел бы сделать это встроенным в конце.)
К сожалению, это не компилируется без дополнительных подсказок типов:

error: incompatible types: bad return type in lambda expression
Iterable<? extends CharSequence> iterable = () -> Arrays.stream(arr).map(Object::toString).iterator();
                                                                                                   ^
    Iterator<String> cannot be converted to Iterator<CharSequence>

Конечно, добавление некоторых подсказок типов сделает эту работу:

Iterable<? extends CharSequence> iterable2 = (Iterable<String>) () -> Arrays.stream(arr).map(Object::toString).iterator();
Iterable<? extends CharSequence> iterable3 = () -> Arrays.stream(arr).<CharSequence>map(Object::toString).iterator();

В моем понимании, компилятор Java делает следующие вещи:

  1. Он просматривает целевой тип выражения, который является Iterable<? extends CharSequence>.
  2. Затем он проверяет лямбду и проверяет ее совместимость.
    В моем случае лямбда имееттип () -> Iterator<String>.
    , который совместим с типом функции, определенной вшаг 2.

Интересно, что если я изменю цель лямбды на Supplier:

Supplier<Iterator<? extends CharSequence>> supplier = () -> Arrays.stream(arr)
    .map(Object::toString)
    .iterator();

, то она будет хорошо скомпилирована.

Вопрос сейчасявляется: почему javac не может определить правильный тип для этой лямбды?

Ответы [ 2 ]

3 голосов
/ 24 октября 2019

Вы можете найти некоторое объяснение здесь :

Параметрированные подстановочными знаками типы функциональных интерфейсов должны быть превращены в тип функции (сигнатуру метода) перед проверкой на совместимость ... Этоработает следующим образом:

Iterable<? extends CharSequence> becomes () -> Iterator<CharSequence>

Итак, если лямбда-выражение неявно типизировано, LHS становится Iterator<CharSequence>, а RHS - Iterator<String>. Следовательно, ошибка:

Iterator<String> cannot be converted to Iterator<CharSequence>

Это поведение также объясняется в JLS §18.5.3 .

0 голосов
/ 24 октября 2019

После прочтения другого ответа (который является абсолютно правильным) и небольшого количества кофе, кажется, что объяснение в ошибке довольно логично.

Здесь есть два случая: явный лямбда-тип и неявный лямбда-тип. Явный тип:

Iterable<String> one = () -> Arrays.stream(arr).map(Object::toString).iterator();
Iterable<? extends CharSequence> iterable = one;

или как в примере с OP:

Iterable<? extends CharSequence> iterable2 = (Iterable<String>) () -> Arrays.stream(arr).map(Object::toString).iterator();

Мы прямо сообщаем компилятору, какой тип лямбда-выражения имеет вид: Iterable<String>.

В этом случае компилятору нужно сделать только одно: посмотреть, можно ли назначить цель этому типу;довольно легко узнать и не очень связано с лямбдами как таковыми.

Другой тип - это неявный тип, когда компилятор должен вывести тип и все становится немного сложнее. «Хитрая» часть проистекает из того факта, что цель использует подстановочные знаки, поэтому может соответствовать более чем одному варианту. Там может быть бесконечное количество способов (конечно, конечно, но только чтобы доказать свою точку зрения), что лямбда может быть выведен как.

Может начаться что-то вроде этого, например:

Iterator<? extends Serializable> iter = Arrays.stream(arr).map(Object::toString).iterator();

Независимо от того, что будет сделано дальше, это не получится: CharSequence не расширяется Serializable, но Stringделает;мы не сможем присвоить Iterable<? extends CharSequence> iterable "независимо от типа-с-типом-с-сериализуемым-есть".

Или он может начинаться с:

Iterator<? extends Comparable<? extends CharSequence>> iter = Arrays.stream(arr).map(Object::toString).iterator();

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

Другой способ намного проще: «порезать» цель и таким образом отбросить возможности вывода только до одного. Как только цель преобразуется в:

Iterable<CharSequence> iterable...

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

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

...