Объясните, как эту лямбду можно присвоить итерации

Я наткнулся на какой-то хитрый код, чтобы конвертировать 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 не нарушают критерии, потому что они не абстрактны.) Поэтому присваивание действительно.