Подтвердить что ты не робот

В Java почему я не могу использовать лямбда в качестве расширенного выражения цикла?

Предположим, что a Iterator<Integer> iterator. Поскольку Iterable является функциональным интерфейсом, мы можем написать:

Iterable<Integer> iterable = () -> iterator;

Мы можем, конечно, использовать Iterable как расширенный для цикла Expression:

for (Integer i : iterable) foo(i);

Итак, почему

for (Integer i : () -> iterator) foo(i);

не разрешено? (Это приводит к следующей ошибке компилятора:

error: lambda expression not expected here
    for (Integer i : () -> iterator) foo(i);
                     ^

Сделать тип цели явным, как

for (Integer i : (Iterable<Integer>) () -> iterator) foo(i);

очевидно работает, но почему компилятор не может вывести целевой тип λ-выражения, если он опущен? Из того факта, что выражение выражено в λ-нотации, должно ли не ясно, компилятору, что целевой тип не может быть Array и, следовательно, должен быть Iterable?

Это просто недосмотр дизайнеров языка, или есть что-то еще, что я здесь отсутствует?

4b9b3361

Ответ 1

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

Одно можно сказать наверняка, что это не надзор; дело было рассмотрено и отклонено.

Чтобы процитировать раннюю спецификацию:

http://cr.openjdk.java.net/~dlsmith/jsr335-0.9.3/D.html

Определение того, какие контексты разрешены для поддержки поли-выражений, во многом обусловлено практической потребностью в таких функциях:

Выражение в цикле расширенного цикла не находится в поликонтексте, потому что, поскольку конструкция в настоящее время определена, это как если бы выражение было получателем: exp.iterator() (или в случае массива exp[i])), Правдоподобно, что Iterator можно было обернуть как Iterable в цикле for через выражение лямбда (for (String s : () -> stringIterator)), но это не очень хорошо связано с семантикой Iterable.

Я считаю, что каждый вызов Iterable.iterator() должен возвращать новый независимый итератор, расположенный в начале. Тем не менее, выражение лямбда в примере (и в вашем примере) возвращает тот же самый итератор. Это не соответствует семантике Iterable.


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

    iterator.forEachRemaining( element->{ ... } )

Или, если вы предпочитаете старую школу

    while(iterator.hasNext()) {
        Foo elment = iterator.next();

Ничего страшного; это не стоит усложнять языковой спецификации еще больше. (Если мы хотим, чтобы каждый из них обеспечивал целевую типизацию, помните, что он должен работать и для других поли-выражений, например, ?:, а затем для каждого в некоторых случаях становится слишком сложно понять. И вообще, есть два возможных типа цели, Iterable<? extends X> | X[], что очень сложно для системы вывода типов.)


Конструкцию for-each можно рассматривать как синтаксический сахар, потому что лямбда недоступна. Если язык уже имеет лямбда-выражение, нет необходимости иметь специальную конструкцию языка для поддержки каждого; это может быть сделано с помощью API-интерфейсов библиотеки.

Ответ 2

В документации Lambda Expressions перечислены сценарии, в которых можно определить тип цели, в частности:

Чтобы определить тип выражения лямбда, компилятор Java использует целевой тип контекста или ситуацию, в которой было найдено выражение лямбда. Из этого следует, что вы можете использовать только лямбда-выражения в ситуациях, когда компилятор Java может определить целевой тип:

  • Переменные объявления

  • Назначение

  • Операторы возврата

  • Инициализаторы массива

  • Аргументы метода или конструктора

  • Тело выражения лямбда

  • Условные выражения:?:

  • Эксклюзивные выражения

Этот сценарий не является ничем из этого, поэтому целевой тип не может быть выведен. Я согласен с вами в том, что целевой тип никогда не может быть массивом (поскольку массив не является функциональным интерфейсом), но в документации ясно, что это не один из этих сценариев.

В частности, в JLS 15.27.3 говорится:

Лямбда-выражение совместимо в контексте назначения, контексте вызова или контексте каста с целевым типом T, если T - тип функционального интерфейса (§9.8), и выражение сравнимо с типом функции основного целевого типа, полученного от T.

  • контекст присваивания - контексты присваивания позволяют присваивать значение выражения (§15.26) переменной.
  • контекст вызова - контексты вызова позволяют значение аргумента в вызове метода или конструктора
  • Контекст кастинга - контексты каста позволяют преобразовать операнд оператора трансляции (§15.16) в тип, явно названный оператором литья.

Ясно, что это ничто из этого. Единственное, что даже возможно, является контекстом Invocation, но расширенная конструкция цикла for не является вызовом метода.


Насколько "почему" этот сценарий не разрешен авторами Java, я понятия не имею. Говоря с умом писателей Java, как правило, выходит за рамки Stack Overflow; Я попытался объяснить, почему код не работает, но я не могу догадаться, почему они решили написать его таким образом.

Добавление, объяснение/обсуждение в ответе @bayou.io по-прежнему присутствует в окончательной версии JSR-335:

Лямбда-выражения и ссылки на методы могут появляться только в определенных контекстах, и их тип и правильность определяются этим контекстом. Другие виды выражений на существующем языке уже ввели зависимости от контекста, и это тенденция, которая, вероятно, будет продолжаться. Вместо того, чтобы рассматривать каждую новую функцию специальным образом, введение поли выражения и явное признание того, что типы-мишени могут влиять на типы выражений, позволяет нам унифицировать обработку контекстно-зависимых выражений под одним зонтиком.

... snip

Выражение в цикле с расширенным циклом не находится в поликонтексте, потому что, поскольку конструкция в настоящее время определена, это как если бы выражение было получателем: exp.iterator() (или в случае массива exp[i])), Правдоподобно, что Iterator можно было обернуть как Iterable в цикле for через лямбда-выражение (for (String s : () -> stringIterator)), но это не очень хорошо связано с семантикой Iterable.

Ответ 3

Как durron597 объясняет, вы можете использовать только лямбда-выражение в ситуациях, когда компилятор Java может определить целевой тип.

В каждом цикле:

для выражения FormalParameter: Expression

JLS говорит, что:

Тип выражения должен быть Iterable или тип массива (§10.1), или возникает ошибка времени компиляции.

Это означает, что допустимы два разных типа, а не один, как требуется для выражения лямбда, поэтому не было никакого способа разработать язык таким образом, чтобы лямбда-выражения работали для каждого цикла. Не с использованием понятия лямбда-выражения.

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