Чому Stream <T> не реалізує Iterable <T>?


261

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

Я щось тут пропускаю?


7
не кажучи вже про те, що інші два способи ітерабельності (forEach і spliterator) також є у Stream
njzk2

1
це потрібно, щоб перейти Streamдо застарілих API, які очікуютьIterable
ZhongYu

11
Хороший IDE (наприклад, IntelliJ) запропонує вам спростити свій код getIterable()доreturn s::iterator;
ZhongYu

23
Вам метод взагалі не потрібен. Там, де у вас є Потік, і ви хочете Iterable, просто передайте stream :: iterator (або, якщо вам більше зручно, () -> stream.iterator ()), і ви закінчите.
Брайан Гец

6
На жаль, я не можу писати for (T element : stream::iterator), тому я все-таки вважаю за краще, якщо Stream також реалізує Iterableабо метод toIterable().
Торстен

Відповіді:


197

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

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

Якщо Streamрозширено, Iterableто існуючий код може бути здивований, коли він отримає Iterableте, що кидає Exceptionвдруге for (element : iterable).


22
Цікаво, що в Java 7 вже були деякі ітерабелі з такою поведінкою, наприклад, DirectoryStream: Хоча DirectoryStream розширює Iterable, він не є інтерабельним загальним призначенням, оскільки він підтримує лише одного ітератора; виклик методу ітератора для отримання другого або наступного ітератора викидає IllegalStateException. ( openjdk.java.net/projects/nio/javadoc/java/nio/file/… )
roim

31
На жаль, в документації Iterableпро те, чи iteratorслід завжди чи не слід дзвонити кілька разів, не існує нічого . Це те, що вони повинні туди вкласти. Це здається більше стандартною практикою, ніж формальною специфікацією.
Лій

7
Якщо вони збираються використовувати виправдання, ви можете подумати, що вони могли б принаймні додати метод asIterable () або перевантажувати всі ті методи, які приймають лише Iterable.
Трежказ

25
Можливо, найкращим рішенням було б змусити передбачення Java прийняти Iterable <T>, а також потенційно Stream <T>?
пожирав елізіум

3
@Lii Як звичайні практики, хоча це досить сильно.
biziclop

160

Щоб перетворити a Streamв an 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 );

66
Ви також можете використовувати це в циклі 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
Варто відзначити, що це порушиться, якщо код прийому намагатиметься повторно використовувати Iterable (наприклад, дві окремі петлі для кожного), відповідно до відповіді kennytm . Це технічно порушує специфікацію. Потік можна використовувати лише один раз; ви не можете зателефонувати BaseStream :: iterator двічі. Перший виклик припиняє потік. За JavaDoc: "Це термінальна операція." Хоча це рішення, якщо це зручно, ви не повинні передавати отриманий Iterable коду, який не знаходиться під вашим остаточним контролем.
Зенексер

10

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


8

Ви можете використовувати Потік у forциклі таким чином:

Stream<T> stream = ...;

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

(Запустіть цей фрагмент тут )

(Для цього використовується функціональний інтерфейс Java 8.)

(Це висвітлено в деяких коментарях вище (наприклад, Олександр Дубінський ), але я хотів витягнути це у відповідь, щоб зробити його більш помітним.)


Практично кожному потрібно подивитися на це двічі чи тричі, перш ніж зрозуміти, як це навіть складається. Це абсурд. (Це висвітлено у відповіді на коментарі в тому ж потоці коментарів, але я хотів додати коментар сюди, щоб зробити його більш помітним.)
ShioT

7

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

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

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

Є один головний недолік:

Переваги лінивої ітерації будуть втрачені. Якщо ви планували негайно повторити всі значення в поточному потоці, будь-які накладні витрати будуть незначними. Однак якщо ви планували повторити лише частково або іншою темою, ця негайна та повна ітерація може мати непередбачувані наслідки.

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


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

Якщо ви не заперечуєте проти використання сторонніх бібліотек, 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);

[Розкриття інформації Я є провідним розробником циклоп-реакції]


0

Не ідеально, але буде працювати:

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

Не ідеально, тому що він витягне всі елементи з потоку і помістить їх у те List, що не є саме тим Iterableі Streamпро що йдеться . Вони, мабуть, ліниві .


-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();

    // ...
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.