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

262

В 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)) { ... }

Я что-то здесь упускаю?

roim
источник
7
не говоря уже о том, что другие 2 метода итерируемого (forEach и spliterator) также находятся в Stream
njzk2
1
это необходимо, чтобы перейти Streamк устаревшим API, которые ожидаютIterable
ZhongYu
12
Хорошая IDE (например, IntelliJ) предложит вам упростить код getIterable()доreturn s::iterator;
ZhongYu
24
Вам не нужен метод вообще. Если у вас есть Stream и вам нужен Iterable, просто передайте stream :: iterator (или, если хотите, () -> stream.iterator ()), и все готово.
Брайан Гетц
7
К сожалению, я не могу писать for (T element : stream::iterator), поэтому я все же предпочел бы, чтобы Stream также реализовывал Iterableили метод toIterable().
Торстен

Ответы:

197

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

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

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

kennytm
источник
22
Любопытно, что в Java 7 уже было несколько итераций с таким поведением, например, DirectoryStream: хотя DirectoryStream расширяет Iterable, он не является Iterable общего назначения, поскольку он поддерживает только один итератор; Вызов метода итератора для получения второго или последующего итератора создает исключение IllegalStateException. ( openjdk.java.net/projects/nio/javadoc/java/nio/file/… )
Рим
32
К сожалению, нет документации Iterableо том, iteratorдолжен ли он быть всегда или не вызываться несколько раз. Это то, что они должны положить туда. Кажется, это больше стандартная практика, чем формальная спецификация.
Лии
7
Если они собираются использовать оправдания, вы можете подумать, что они могли бы по крайней мере добавить метод asIterable () - или перегрузить все те методы, которые принимают только Iterable.
Трейказ
26
Может быть, лучшим решением было бы заставить foreach Java принимать Iterable <T>, а также потенциально Stream <T>?
пожрал элизиум
3
@Lii Стандартные практики довольно сильны.
Бизиклоп
161

Чтобы преобразовать 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 );
Zhongyu
источник
67
Вы также можете использовать это в цикле for(X x : (Iterable<X>)stream::iterator), хотя это выглядит некрасиво. Действительно, вся ситуация просто абсурдна.
Александр Дубинский
24
@HRJIntStream.range(0,N).forEach(System.out::println)
MikeFHay,
11
Я не понимаю синтаксис двойного двоеточия в этом контексте. В чем разница между stream::iteratorи stream.iterator(), что делает бывший приемлемым для , Iterableно не последний?
Даниэль С. Собрал
20
Отвечаю себе: Iterableэто функциональный интерфейс, поэтому достаточно передать функцию, которая его реализует.
Даниэль С. Собрал
5
Я должен согласиться с @AleksandrDubinsky, реальная проблема заключается в том, что существует логический обходной путь для (X x: (Iterable <X>) stream :: iterator), который просто допускает ту же операцию, хотя и с большим шумом кода. Но тогда это Java, и мы долгое время терпели List <String> list = new ArrayList (Arrays.asList (array)) :) Хотя все эти возможности могли бы предложить нам все List <String> list = array. toArrayList (). Но настоящая нелепость заключается в том, что, поощряя всех использовать forEach on Stream, исключения обязательно должны быть проверены [rant end].
Координатор
10

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

Александр Дубинский
источник
8

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

Stream<T> stream = ...;

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

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

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

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

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

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

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

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

Есть один главный недостаток:

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

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

Zenexer
источник
1
Вы пытались скомпилировать код перед ответом? Не работает
Тагир Валеев
1
@TagirValeev Да, я сделал. Вам нужно заменить T на соответствующий тип. Я скопировал пример из рабочего кода.
Zenexer
2
@TagirValeev Я снова проверил это в IntelliJ. Похоже, что IntelliJ иногда путается с этим синтаксисом; Я действительно не нашел образец этому. Однако код компилируется нормально, и IntelliJ удаляет уведомление об ошибке после компиляции. Я думаю, это просто ошибка.
Zenexer
1
Stream.toArray()возвращает массив, а не an Iterable, поэтому этот код по-прежнему не компилируется. но это может быть ошибкой в ​​затмении, так как IntelliJ, кажется, компилирует его
Benez
1
@Zenexer Как вы смогли назначить массив для Iterable?
radiantRazor
3

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);
    }
Ашиш Тяги
источник
2

Если вы не возражаете против использования сторонних библиотек, циклоп-реакция определяет поток, который реализует оба потока и итерируемый, а также воспроизводимый (решение проблемы описанной 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);

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

Джон МакКлин
источник
0

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

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

Не совершенным , потому что он будет получать все элементы из потока и поместить их в том , что List, что это не совсем то , что Iterableи Streamо. Они должны быть ленивыми .

yegor256
источник
-1

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

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

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

    // ...
}
BullyWiiPlaza
источник