Почему Stream<T> не реализует Iterable<T>?
В Java 8 у нас есть класс Stream
Iterator<T> iterator()
Поэтому вы ожидаете, что он реализует интерфейс Iterable
Когда я хочу перебрать поток через цикл 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)) { ... }
Я что-то здесь упускаю?
9 ответов
В списке рассылки уже есть люди, которые спрашивают об этом ☺. Основная причина в том, что Iterable также имеет повторяемую семантику, а Stream - нет.
Я думаю, что главная причина в том, что
Iterable
подразумевает возможность многократного использования, тогда какStream
это то, что может быть использовано только один раз - больше похоже наIterator
,Если
Stream
расширенныйIterable
тогда существующий код может быть удивлен, когда он получаетIterable
это бросаетException
во второй раз они делаютfor (element : iterable)
,
Чтобы преобразовать 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 );
Вы можете использовать поток в for
цикл следующим образом:
Stream<T> stream = ...;
for (T x : (Iterable<T>) stream::iterator) {
...
}
(Запустите этот фрагмент здесь)
(При этом используется приведение функционального интерфейса Java 8.)
(Это описано в некоторых комментариях выше (например, Александр Дубинский), но я хотел вытащить его в ответ, чтобы сделать его более заметным.)
Я хотел бы отметить, что StreamEx
действительно реализует Iterable
(а также Stream
), а также множество других потрясающих функций, отсутствующих в Stream
,
Кеннитм описал, почему небезопасно лечить Stream
как Iterable
и Чжун Юй предложил обходной путь, который позволяет использовать Stream
как в Iterable
Хотя и небезопасно. Можно получить лучшее из обоих миров: многоразовое Iterable
из Stream
который отвечает всем гарантиям, сделанным Iterable
Спецификация.
Замечания: SomeType
здесь не параметр типа - вам нужно заменить его на соответствующий тип (например, String
) или прибегнуть к размышлению
Stream<SomeType> stream = ...;
Iterable<SomeType> iterable = stream.collect(toList()):
Есть один главный недостаток:
Преимущества ленивой итерации будут потеряны. Если вы планировали немедленно выполнить итерацию по всем значениям в текущем потоке, любые издержки будут незначительными. Однако, если вы планируете выполнять итерацию только частично или в другом потоке, эта немедленная и полная итерация может иметь непредвиденные последствия.
Большим преимуществом, конечно же, является то, что вы можете использовать Iterable
, в то время как (Iterable<SomeType>) stream::iterator
позволит только одно использование. Если принимающий код будет перебирать коллекцию несколько раз, это не только необходимо, но, скорее всего, выгодно для производительности.
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);
}
Если вы не возражаете против использования сторонних библиотек, циклоп-реактив определяет Stream, который реализует как 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);
[Раскрытие Я ведущий разработчик циклоп-реакции]
Вы можете перебирать все файлы в папке, используя Stream<Path>
как это:
Path path = Paths.get("...");
Stream<Path> files = Files.list(path);
for (Iterator<Path> it = files.iterator(); it.hasNext(); )
{
Object file = it.next();
// ...
}
Не идеально, но будет работать:
iterable = stream.collect(Collectors.toList());
Не идеально, потому что он будет получать все элементы из потока и помещать их в List
что не совсем то Iterable
а также Stream
о. Они должны быть ленивыми.