Warum implementiert Stream <T> Iterable <T> nicht?

261

In Java 8 haben wir die Klasse Stream <T> , die seltsamerweise eine Methode hat

Iterator<T> iterator()

Sie würden also erwarten, dass die Schnittstelle Iterable <T> implementiert wird , was genau diese Methode erfordert, aber das ist nicht der Fall.

Wenn ich mit einer foreach-Schleife über einen Stream iterieren möchte, muss ich so etwas tun

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

Vermisse ich hier etwas?

roim
quelle
7
ganz zu schweigen davon, dass die anderen 2 iterierbaren Methoden (forEach und spliterator) ebenfalls in Stream
njzk2
1
Dies ist erforderlich, um Streaman ältere APIs zu übergeben, die erwartenIterable
ZhongYu
11
Eine gute IDE (zB IntelliJ) werden Sie aufgefordert , Ihren Code zu vereinfachen in getIterable()zureturn s::iterator;
Zhongyu
23
Sie brauchen überhaupt keine Methode. Wenn Sie einen Stream haben und einen Iterable möchten, übergeben Sie einfach stream :: iterator (oder, wenn Sie es vorziehen, () -> stream.iterator ()), und fertig.
Brian Goetz
6
Leider kann ich nicht schreiben for (T element : stream::iterator), daher würde ich es immer noch vorziehen, wenn Stream auch Iterableeine Methode implementieren würde toIterable().
Thorsten

Antworten:

197

Die Leute haben das schon auf der Mailingliste gefragt ☺. Der Hauptgrund dafür ist, dass Iterable auch eine wiederholbare Semantik hat, Stream jedoch nicht.

Ich denke, der Hauptgrund ist, dass Iterabledies die Wiederverwendbarkeit impliziert, während Streamdies nur einmal verwendet werden kann - eher wie ein Iterator.

Wenn Streamverlängert Iterabledann könnten vorhandenen Code überrascht sein , wenn er erhält ein Iterable, der eine wirft Exceptiondas zweite Mal , wenn sie tun for (element : iterable).

kennytm
quelle
22
Seltsamerweise gab es in Java 7 bereits einige Iterables mit diesem Verhalten, z. B. DirectoryStream: Während DirectoryStream Iterable erweitert, ist es kein Allzweck-Iterable, da es nur einen einzigen Iterator unterstützt. Wenn Sie die Iterator-Methode aufrufen, um einen zweiten oder nachfolgenden Iterator zu erhalten, wird IllegalStateException ausgelöst. ( openjdk.java.net/projects/nio/javadoc/java/nio/file/… )
Roim
31
Leider gibt es in der Dokumentation nichts Iterabledarüber, ob iteratorimmer mehrmals aufgerufen werden soll oder nicht. Das sollten sie da reinstecken. Dies scheint eher eine Standardpraxis als eine formale Spezifikation zu sein.
Lii
7
Wenn sie Ausreden verwenden, würden Sie denken, dass sie zumindest eine asIterable () -Methode hinzufügen könnten - oder Überladungen für all jene Methoden, die nur Iterable verwenden.
Trejkaz
25
Vielleicht wäre die beste Lösung gewesen, Javas foreach dazu zu bringen, sowohl ein Iterable <T> als auch möglicherweise einen Stream <T> zu akzeptieren?
verschlungenes Elysium
3
@Lii Standardmäßig ist es jedoch ziemlich stark.
Biziclop
160

Um ein Streamin ein zu konvertieren Iterable, können Sie tun

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

Um a Streaman eine Methode zu übergeben, die erwartet Iterable,

void foo(Iterable<X> iterable)

einfach

foo(stream::iterator) 

aber es sieht wahrscheinlich lustig aus; es könnte besser sein, ein bisschen expliziter zu sein

foo( (Iterable<X>)stream::iterator );
ZhongYu
quelle
66
Sie können dies auch in einer Schleife verwenden for(X x : (Iterable<X>)stream::iterator), obwohl es hässlich aussieht. Wirklich, die ganze Situation ist einfach absurd.
Aleksandr Dubinsky
24
@ HRJIntStream.range(0,N).forEach(System.out::println)
MikeFHay
11
Ich verstehe die Doppelpunkt-Syntax in diesem Zusammenhang nicht. Was ist der Unterschied zwischen stream::iteratorund stream.iterator(), was das erstere für ein Iterableaber nicht das letztere akzeptabel macht ?
Daniel C. Sobral
20
Ich antworte mir: Iterableist eine funktionale Schnittstelle, daher reicht es aus, eine Funktion zu übergeben, die sie implementiert.
Daniel C. Sobral
5
Es ist erwähnenswert, dass dies nicht funktioniert, wenn der empfangende Code versucht, die Iterable (z. B. zwei separate Schleifen für jede Schleife) gemäß der Antwort von kennytm wiederzuverwenden . Dies bricht technisch spez. Sie können einen Stream nur einmal verwenden. Sie können BaseStream :: iterator nicht zweimal aufrufen. Der erste Aufruf beendet den Stream. Laut JavaDoc: "Dies ist eine Terminaloperation." Obwohl diese Problemumgehung zweckmäßig ist, sollten Sie die resultierende Iterable nicht an Code übergeben, der nicht unter Ihrer endgültigen Kontrolle steht.
Zenexer
10

Ich möchte darauf hinweisen, dass StreamExdies Iterable(und Stream) implementiert , sowie eine Vielzahl anderer immens beeindruckender Funktionen, die fehlen Stream.

Aleksandr Dubinsky
quelle
8

Sie können einen Stream in einer forSchleife wie folgt verwenden:

Stream<T> stream = ...;

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

(Führen Sie dieses Snippet hier aus )

(Dies verwendet eine Java 8-Funktionsschnittstelle.)

(Dies wird in einigen der obigen Kommentare behandelt (z. B. Aleksandr Dubinsky ), aber ich wollte es in eine Antwort ziehen, um es sichtbarer zu machen.)

Reich
quelle
Fast jeder muss es zweimal oder dreimal ansehen, bevor er merkt, wie es überhaupt kompiliert wird. Es ist absurd. (Dies wird in der Antwort auf die Kommentare im selben Kommentar-Stream behandelt, aber ich wollte den Kommentar hier hinzufügen, um ihn besser sichtbar zu machen.)
ShioT
7

kennytm beschrieb, warum es unsicher ist, a Streamals zu behandeln Iterable, und Zhong Yu bot eine Problemumgehung an , die die Verwendung eines Streamas in ermöglicht Iterable, wenn auch auf unsichere Weise. Es ist möglich , das Beste aus beiden Welten zu bekommen: ein wiederverwendbaren Iterablevon einem Stream, der durch die aus allen Garantien erfüllt IterableSpezifikation.

Hinweis: SomeTypeist hier kein Typparameter - Sie müssen ihn durch einen geeigneten Typ (z. B. String) ersetzen oder auf Reflexion zurückgreifen

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

Es gibt einen großen Nachteil:

Die Vorteile der verzögerten Iteration gehen verloren. Wenn Sie vorhaben, alle Werte im aktuellen Thread sofort zu durchlaufen, ist der Overhead vernachlässigbar. Wenn Sie jedoch nur teilweise oder in einem anderen Thread iterieren möchten, kann diese sofortige und vollständige Iteration unbeabsichtigte Folgen haben.

Der große Vorteil ist natürlich, dass Sie das wiederverwenden können Iterable, während (Iterable<SomeType>) stream::iteratores nur eine einmalige Verwendung erlauben würde. Wenn der empfangende Code mehrmals über die Sammlung iteriert, ist dies nicht nur erforderlich, sondern wahrscheinlich auch vorteilhaft für die Leistung.

Zenexer
quelle
1
Haben Sie versucht, Ihren Code zu kompilieren, bevor Sie antworten? Es funktioniert nicht.
Tagir Valeev
1
@TagirValeev Ja, ich habe es getan. Sie müssen T durch den entsprechenden Typ ersetzen. Ich habe das Beispiel aus dem Arbeitscode kopiert.
Zenexer
2
@TagirValeev Ich habe es erneut in IntelliJ getestet. Es scheint, dass IntelliJ manchmal durch diese Syntax verwirrt wird; Ich habe kein Muster dafür gefunden. Der Code wird jedoch einwandfrei kompiliert, und IntelliJ entfernt den Fehlerhinweis nach dem Kompilieren. Ich denke, es ist nur ein Fehler.
Zenexer
1
Stream.toArray()Gibt ein Array zurück, kein Iterable, daher wird dieser Code immer noch nicht kompiliert. aber es kann ein Fehler in Eclipse sein, da IntelliJ es zu kompilieren scheint
benez
1
@Zenexer Wie konnten Sie einem Iterable ein Array zuweisen?
RadiantRazor
3

Streamnicht implementiert Iterable. Das allgemeine Verständnis von Iterableist alles, worauf oft wiederholt werden kann. Streamist möglicherweise nicht wiederholbar.

Die einzige Problemumgehung, die ich mir vorstellen kann, wenn ein auf einem Stream basierendes Iterable ebenfalls wiedergegeben werden kann, besteht darin, den Stream neu zu erstellen. Ich verwende Supplierunten, um eine neue Instanz des Streams zu erstellen, jedes Mal, wenn ein neuer Iterator erstellt wird.

    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);
    }
Ashish Tyagi
quelle
2

Wenn es Ihnen nichts ausmacht, Bibliotheken von Drittanbietern zu verwenden, definiert cyclops-react einen Stream, der sowohl Stream als auch Iterable implementiert und auch wiedergegeben werden kann (Lösung des beschriebenen Kennytm- Problems ).

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

oder :-

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

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

[Offenlegung Ich bin der Hauptentwickler von Cyclops-React]

John McClean
quelle
0

Nicht perfekt, wird aber funktionieren:

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

Nicht perfekt, weil es alle Elemente aus dem Stream abruft und in das einfügt List, was nicht genau das ist Iterableund worum Streames geht. Sie sollen faul sein .

yegor256
quelle
-1

Sie können alle Dateien in einem Ordner folgendermaßen durchlaufen 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
quelle