Объясните, как эту лямбду можно присвоить итерации
Я наткнулся на какой-то хитрый код, чтобы конвертировать Iterator в Stream из Karol на этот пост. Я должен признать, что я не совсем понимаю, как lambda можно присвоить типу Iterable
в следующем коде...
static <T> Stream<T> iteratorToFiniteStream(final Iterator<T> iterator) {
final Iterable<T> iterable = () -> iterator;
return StreamSupport.stream(iterable.spliterator(), false);
}
Я решил написать свой собственный небольшой тест, чтобы убедиться, что он компилируется и выполняется, и это происходит.
public void printsStream_givenIterator()
{
Iterator<String> iterator = Arrays.asList("a", "b", "c").iterator();
final Iterable<String> iterable = () -> iterator;
StreamSupport.stream(iterable.spliterator(), false).forEach(s -> System.out.println(s));
}
// prints: abc
Я понимаю, что lambda () -> iterator
действует как функция Supplier.
Iterable не FunctionalInterface, как его можно присвоить этой лямбда?
Ответы
Ответ 1
() -> iterator
не действует как функция Supplier
. Лямбда-выражения могут быть назначены любому конгруэнтному функциональному интерфейсу. И нет необходимости в том, чтобы функциональный интерфейс был аннотирован с помощью @FunctionalInterface
. Вы можете создать Iterable
с помощью выражения лямбда, который будет реализовывать метод Iterable.iterator()
, который является единственным методом abstract
этого интерфейса. Однако, реализуя его, возвращая один и тот же экземпляр Iterator
каждый раз, может нарушить ожидание возможности повторного итерации этого объекта несколько раз (что является причиной почему Stream
не реализует Iterable
, несмотря на наличие метода iterator()
).
Это решение будет работать в этом узком контексте, но не умно.
Последовательность
final Iterable<T> iterable = () -> iterator; … iterable.spliterator()
просто вводит дополнительный шаг до Spliterators.spliteratorUnknownSize(iterator, 0)
, который реализует метод default
Iterable.spliterator()
.
Итак,
static <T> Stream<T> iteratorToFiniteStream(final Iterator<T> iterator) {
final Iterable<T> iterable = () -> iterator;
return StreamSupport.stream(iterable.spliterator(), false);
}
является менее эффективным вариантом
static <T> Stream<T> iteratorToFiniteStream(final Iterator<T> iterator) {
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);
}
Если вы сравните это с принятым ответом Q & A youve linked, вы увидите, что это именно так, но этот ответ использует возможность передачи определенного characteristics
вместо 0
.
Ответ 2
Как вы указали, назначение из лямбда-выражения допустимо только в том случае, если целью является функциональный интерфейс. Это описано в разделе 15.27.3 JLS: Тип выражения лямбда.
Лямбда-выражение совместимо в контексте назначения, контексте вызова или контексте каста с целевым типом T, если T - тип функционального интерфейса (§9.8), и выражение сравнимо с типом функции основного целевого типа, полученного от T.
Переход на раздел 9.8: Функциональные интерфейсы, мы можем увидеть определение функционального интерфейса.
Функциональный интерфейс - это интерфейс, который имеет только один абстрактный метод (помимо методов Object) и, таким образом, представляет собой контракт с одной функцией.
Iterable
удовлетворяет критериям функционального интерфейса, потому что он имеет только один абстрактный метод: iterator()
. (2 дополнительных метода default
не нарушают критерии, потому что они не абстрактны.) Поэтому присваивание действительно.