Почему Stream <T> не реализует Iterable <T>?

В Java 8 мы имеем класс Stream <T> , который любопытно имеет метод

Iterator<T> iterator()

Итак, вы ожидаете, что он реализует интерфейс Iterable <T> , который требует именно этого метода, но это не так.

Когда я хочу перебрать поток через цикл foreach, я должен сделать что-то вроде

public static Iterable<T> getIterable(Stream<T> s) {
    return new Iterable<T> {
        @Override
        public Iterator<T> iterator() {
            return s.iterator();
        }
    };
}

for (T element : getIterable(s)) { ... }

Я что-то пропустил?

Ответ 1

Уже есть люди, которые задали тот же в списке рассылки ☺. Основная причина заключается в том, что Iterable также имеет повторную итеративную семантику, а Stream - нет.

Я думаю, главная причина в том, что Iterable подразумевает повторное использование, тогда как Stream - это то, что можно использовать только один раз - больше похоже на Iterator.

Если Stream extended Iterable, тогда существующий код может быть удивлен, когда он получит Iterable, который выдает Exceptionвторой раз они делают for (element : iterable).

Ответ 2

Чтобы преобразовать a Stream в Iterable, вы можете сделать

Stream<X> stream = null;
Iterable<X> iterable = stream::iterator

Чтобы передать Stream методу, который ожидает Iterable,

void foo(Iterable<X> iterable)

просто

foo(stream::iterator) 

однако это, вероятно, выглядит забавным; было бы лучше быть немного более явным

foo( (Iterable<X>)stream::iterator );

Ответ 3

Я хотел бы указать, что StreamEx реализует IterableStream), а также множество другие невероятно функциональные функции отсутствуют в Stream.

Ответ 4

kennytm описал, почему небезопасно относиться к Stream как Iterable, а Чжун Юй предложил обходное решение, который позволяет использовать Stream как в Iterable, хотя и небезопасно. Можно получить лучшее из обоих миров: многоразовый Iterable от Stream, который отвечает всем гарантиям, указанным в спецификации Iterable.

Примечание: SomeType здесь не является параметром типа - вам нужно заменить его на соответствующий тип (например, String) или прибегнуть к отражению

Stream<SomeType> stream = ...;
Iterable<SomeType> iterable = stream.collect(toList()):

Существует один главный недостаток:

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

Большое преимущество, конечно, состоит в том, что вы можете повторно использовать Iterable, тогда как (Iterable<SomeType>) stream::iterator допускает только одно использование. Если код приема будет повторяться через коллекцию несколько раз, это не только необходимо, но и полезно для производительности.

Ответ 5

Вы можете использовать Stream в цикле for следующим образом:

Stream<T> stream = ...;

for (T x : (Iterable<T>) stream::iterator) {
    ...
}

(Запустите этот фрагмент здесь)

(для этого используется функциональный интерфейс Java 8).

(Это описано в некоторых комментариях выше (например, Александр Дубинский), но я хотел вытащить его в ответ, чтобы сделать его более заметным.)

Ответ 6

Если вы не возражаете против использования сторонних библиотек cyclops-react определяет поток, который реализует как Stream, так и Iterable и также воспроизводится ( решение проблемы kennytm описано).

 Stream<String> stream = ReactiveSeq.of("hello","world")
                                    .map(s->"prefix-"+s);

или: -

 Iterable<String> stream = ReactiveSeq.of("hello","world")
                                      .map(s->"prefix-"+s);

 stream.forEach(System.out::println);
 stream.forEach(System.out::println);

[Раскрытие информации Я ведущий разработчик реакции циклопов]

Ответ 7

Stream не реализует Iterable. Общее понимание Iterable - это все, что можно повторять, часто снова и снова. Stream может не воспроизводиться.

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

    Supplier<Stream<Integer>> streamSupplier = () -> Stream.of(10);
    Iterable<Integer> iterable = () -> streamSupplier.get().iterator();
    for(int i : iterable) {
        System.out.println(i);
    }
    // Can iterate again
    for(int i : iterable) {
        System.out.println(i);
    }

Ответ 8

Не идеально, но будет работать:

iterable = stream.collect(Collectors.toList());

Не идеально, потому что он будет извлекать все элементы из потока и помещать их в это List, что не совсем соответствует Iterable и Stream. Предполагается, что они lazy.

Ответ 9

Вы можете перебирать все файлы в папке, используя Stream<Path> следующим образом:

Path path = Paths.get("...");
Stream<Path> files = Files.list(path);

for (Iterator<Path> it = files.iterator(); it.hasNext(); )
{
    Object file = it.next();

    // ...
}