Wie kann ich einen Stream in einen Iterable verwandeln? [Duplikat]

8

Streamerbt eine iterator () -Methode, um eine zu erzeugen Iterator.

Aber ich brauche Iterableeher eine als eine Iterator.

Beispiel: Diese Zeichenfolge lautet:

String input = "this\n" +
        "that\n" +
        "the_other";

… Ich muss diese Teile der Zeichenfolge als Iterablean eine bestimmte Bibliothek übergeben. Aufruf input.lines()ergibt a Stream. Ich wäre also bereit, wenn ich das Streamzu einem Iterableseiner Elemente machen könnte.

Basil Bourque
quelle
@Naman Related, aber das scheint das Gegenteil von dem zu sein, was Basil in diesem Q & A tut.
Slaw
1
Eine andere Möglichkeit, ein Iterable zu erhalten, besteht darin, es in einer Liste zu sammeln.
Matt

Antworten:

12

Wie unter Warum implementiert Stream <T> Iterable <T> nicht? , a Iterableträgt die Erwartung, Iteratormehr als einmal liefern zu können , was ein Streamnicht erfüllen kann. Während Sie also ein IterableOut Streamfür Ad-hoc-Zwecke erstellen können , müssen Sie vorsichtig sein, ob Versuche bestehen, es mehrmals zu wiederholen.

Da Sie gesagt haben: " Ich muss diese Teile der Zeichenfolge als Iterablean eine bestimmte Bibliothek übergeben ", gibt es keine allgemeine Lösung, da der Code, der Iterabledas verwendet, außerhalb Ihrer Kontrolle liegt.

Wenn Sie jedoch derjenige sind, der den Stream erstellt, können Sie einen gültigen IterableStream erstellen, der die Stream-Konstruktion jedes Mal wiederholt, wenn eine Iteratorangefordert wird:

Iterable<String> lines = () -> "this\nthat\nthe_other".lines().iterator();

Dies erfüllt die Erwartung, eine beliebige Anzahl von Iterationen zu unterstützen, während nicht mehr Ressourcen als ein einzelner Stream verbraucht werden, wenn nur einmal durchlaufen wird.

for(var s: lines) System.out.println(s);
lines.forEach(System.out::println);
System.out.println(String.join("\n", lines));
Holger
quelle
Hervorragende Erklärung. Es könnte sich lohnen, hinzuzufügen (als etwas fortgeschritteneres Addendum), dass Iterable<String> lines = "this\nthat\nthe_other".lines()::iterator;dies zwar einer der seltenen Fälle ist, in denen eine Methodenreferenz nicht mit einem Lambda identisch ist und tatsächlich unerwünschte Auswirkungen hätte.
Klitos Kyriacou
3
@ KlitosKyriacou-Methodenreferenzen des Formulars expression::namesind niemals mit Lambda-Ausdrücken identisch, auch nicht für System.out::println. Aber hier haben wir einen der Fälle, in denen es darauf ankommt
Holger
In diesem Fall werden jedes Mal Zeilen aufgerufen, aber ich sehe keinen Unterschied zwischen einer Methode und einem Lambda, wenn Stream<String> lines = str.lines();Sie dann das Iterable<String> iter = lines::iteratorVersus erstellen ()->lines.iterator().
Matt
2
@matt niemand sagte, dass es einen Unterschied zwischen stream::iteratorund gab () -> stream.iterator(). In meiner Antwort heißt es genau: Es gibt keine allgemeine Lösung für die Konvertierung eines vorhandenen Streams in eine gültige Iterable, die mehrere Iterationen zulässt. Das Wiederholen der Stream-Konstruktion jedes Mal, wenn ein Iterator angefordert wird, dh hier bedeutet dies, dass lines()jedes Mal aufgerufen wird, macht den Unterschied aus.
Holger
3
@matt gut, formal ist es nicht das gleiche. expression::namebedeutet " einmal auswerten expressionund das Ergebnis erfassen ", während () -> expression.name(…)" jedes Mal auswerten, expressionwenn der Funktionskörper ausgewertet wird " bedeutet. Die Unterschiede sind winzig, wenn " expression" nur eine lokale Variable ist, aber selbst dann ist es anders, dh es stream::iteratorwird sich anders verhalten als () -> stream.iterator()wenn es streamist null. Meine Aussage gilt expression::namealso immer noch, unterscheidet sich immer von einem Lambda-Ausdruck, die Frage ist, wie wichtig es für meinen speziellen Anwendungsfall ist.
Holger
3

tl; dr

Einfach besetzen , keine Konvertierung nötig.

Besetzung Stream < String >zu Iterable < String >.

Einzelheiten

VORSICHT Siehe Antwort von Holger, in der die Gefahren der Verwendung eines Stream-Backed erläutert werden Iterable.

Ja, du kannst Iterableaus einem machen Stream.

Die Lösung ist einfach, aber nicht offensichtlich. Siehe diesen Beitrag in den Lambda-FAQ von Maurice Naftalin .

Die iterator()Methode zur Rückgabe von a BaseStream(Oberklasse von Stream) Iteratorstimmt zufällig mit demselben Namen der iterator()Methode überein, die a zurückgibt, Iteratorwie von der IterableSchnittstelle gefordert . Die Methodensignaturen stimmen überein. Also haben wir eigentlich die werfen können , Streamum ein Iterable, benötigt keine Konvertierung.

Machen Sie Ihre Eingabe.

String input = "this\n" +
        "that\n" +
        "the_other";
Stream < String > stream = input.lines() ;

Wirf das Stream<String>auf Iterable<String>.

Iterable< String > iterable = ( Iterable < String > ) stream ;  // Cast `Stream < String >` to `Iterable < String >`. 

Testen Sie die Ergebnisse.

for ( String s : iterable ) 
{
    System.out.println( "s = " + s );
}

Sehen Sie diesen Code live auf IdeOne.com .

s = das

s = das

s = the_other

CAVEAT Achten Sie auf das Risiko von Stream-Backed Iterable. Erklärt in der richtigen Antwort von Holger .

Basil Bourque
quelle
7
Technisch gesehen bist du kein Casting, Stream<String>weil stream::iteratores nicht vom Typ ist Stream<String>.
Kaya3
3
Ich bin mir nicht sicher, ob Casting die richtige Terminologie ist. Sie erstellen eine Instanz Iterableüber eine Methodenreferenz. Das (Iterable<String>)teilt dem Compiler nur mit, welche funktionale Schnittstelle das Ziel ist.
Slaw
@Slaw & @ kaya3 Diese Feinheiten sind meinem Verständnis entgangen. Ich sehe nicht, wie (Iterable<String>)etwas anderes als eine Besetzung ist, aber ich verstehe die Situation sicherlich nicht, da Ihre Kommentare mich dazu veranlassten, eine zusätzliche Gruppe von Parens zu verwenden, ( (Iterable<String>) stream )die dazu führen, dass der Code fehlschlägt - was beweist, dass mein Verständnis fehlerhaft ist. Bitte bearbeiten Sie meine Antwort, um dies zu klären, oder veröffentlichen Sie Ihre eigene Antwort mit einer besseren Erklärung.
Basil Bourque
4
@Slaw das Problem ist, es ist richtig, dass es eine Besetzung gibt, aber es ist nicht richtig zu sagen, dass die Besetzung die Lösung ist. Die Lösung besteht aus einer Methodenreferenz und einer Besetzung. Die Umwandlung könnte durch ein anderes Konstrukt ersetzt werden, das den beabsichtigten Zieltyp bereitstellt, z. B. die Methodenreferenz an eine Methode übergeben, die eine Iterable erwartet, oder sie einer Variablen vom Typ iterable zuweisen. Die Lösung erfordert jedoch weiterhin eine Methodenreferenz oder einen Lambda-Ausdruck für die Konvertierung. Es ist also Unsinn zu sagen, dass es nur eine Umwandlung statt eine Konvertierung gibt , wenn es noch eine Konvertierung (Adaptercode) und eine Besetzung gibt.
Holger
2
Iterable ist effektiv eine funktionale Schnittstelle. Sie erstellen eine Iterable mit der von stream.iterator überschriebenen Iteratormethode. Es wäre effektiv (Iterable<String>)()->stream.iterator()oder noch expliziter new Iterable<String>(){ public Iterator<String> iterator(){ return stream.iterator();}. Sie übertragen also keinen Stream auf Iterable, was fehlschlagen würde.
Matt