W3docs

Java Streams erstellen

Java Streams aus Collections, Arrays, Stream.of, Stream.iterate, Stream.generate und I/O-Quellen erstellen – kompakt erklärt.

Das Einführungskapitel hat die Pipeline-Struktur — Quelle → Zwischenoperationen → Terminaloperation — vorgestellt und die Quelle als gegeben betrachtet. Dieses Kapitel ist der Katalog der Quellen. Jede Stream-Pipeline, die Sie schreiben, beginnt mit einer davon, und jede bringt eine kleine Reihe von Eigenschaften mit, die darüber entscheiden, ob die folgende Pipeline korrekt, lazy, endlich, geordnet oder parallelisierbar ist.

Die Liste ist kürzer als sie aussieht. Fast jeder Stream, den Sie je schreiben werden, beginnt mit coll.stream(), Stream.of(...), Arrays.stream(arr) oder einem IntStream.range. Der Rest dieses Kapitels erklärt, in welchen seltenen Situationen die anderen Quellen die richtige Wahl sind.

Aus einer Collectioncoll.stream()

Die häufigste Quelle. Collection<T> hat eine Standard-Methode stream(), sodass jede List, Set, Queue und Deque sie kostenlos bereitstellt:

List<String> names = List.of("Alice", "Bob", "Carol");
long count = names.stream().filter(n -> n.length() > 3).count();

Der Stream ist sequenziell, größenbekannt (die JVM kennt die Elementanzahl vorab) und geordnet, wenn die Collection es ist. Eine List erzeugt einen geordneten Stream; ein HashSet einen ungeordneten; ein TreeSet einen nach dem Comparator der Menge geordneten.

Es gibt auch coll.parallelStream(), das Arbeit auf dem gemeinsamen ForkJoinPool verteilt. Gleiche Quelle, andere Ausführungsstrategie — behandelt in Java Parallel Streams.

Aus expliziten Elementen — Stream.of(...)

Verwenden Sie Stream.of, wenn Sie eine kurze, bekannte Liste von Elementen haben und keine temporäre List anlegen möchten:

Stream<String> s   = Stream.of("a", "b", "c");
Stream<Integer> n  = Stream.of(1, 2, 3, 4, 5);
Stream<Object> one = Stream.of("just one");

Es ist eine Varargs-Methode, die beliebig viele Argumente akzeptiert (null ist erlaubt und ergibt einen leeren Stream). Bei einem einzelnen T[]-Argument wählt der Compiler Stream.of(T...) statt Stream.of(T) — praktisch, wenn Sie bereits ein Array haben:

String[] arr = {"x", "y", "z"};
Stream<String> fromArr = Stream.of(arr);   // same as Arrays.stream(arr)

Aus einem Array — Arrays.stream(...)

Arrays.stream bietet Überladungen für T[], int[], long[], double[] sowie Bereichsvarianten:

int[] xs = {3, 1, 4, 1, 5, 9, 2, 6};
IntStream ix = Arrays.stream(xs);              // primitive specialisation
IntStream tail = Arrays.stream(xs, 2, xs.length);   // half-open [2, len)

String[] words = {"alpha", "beta", "gamma"};
Stream<String> ws = Arrays.stream(words);

Die primitiven Überladungen geben IntStream, LongStream, DoubleStream zurück — kein Stream<Integer>. Das ist wichtig: Primitive Streams vermeiden Boxing, haben sum, average, min, max direkt (ohne Collector) und lassen sich mit mapToInt/mapToObj zwischen den Welten wechseln.

Primitive Bereiche — IntStream.range / rangeClosed

Der schnellste Weg, per Index zu iterieren, ohne eine for-Schleife:

// 0, 1, 2, ..., 9
IntStream.range(0, 10).forEach(i -> System.out.println(i));

// 1..10 inclusive
int sum = IntStream.rangeClosed(1, 10).sum();   // 55

range(a, b) ist halbgeöffnet [a, b). rangeClosed(a, b) ist [a, b]. Beide sind begrenzt, geordnet, größenbekannt und schneller als Stream.iterate(0, i -> i + 1).limit(n), weil die JVM die Anzahl vorab kennt. Verwenden Sie sie immer, wenn der Schleifenkörper „tue etwas bei Index i" lautet.

Um einen Index mit den Elementen einer List zu verbinden, schreiben Sie:

List<String> names = List.of("Alice", "Bob", "Carol");
IntStream.range(0, names.size())
    .mapToObj(i -> i + ": " + names.get(i))
    .forEach(System.out::println);

Erzeugte unendliche Streams — Stream.iterate und Stream.generate

Zwei Möglichkeiten, einen unbegrenzten Stream zu erzeugen. Sie sehen ähnlich aus, sind es aber nicht.

Stream.iterate(seed, f) — beginnt mit seed, dann f(seed), dann f(f(seed)), …. Geordnet, deterministisch, sequenziell. Fast immer gefolgt von einem Short-Circuit:

Stream.iterate(1, n -> n * 2)
    .limit(10)
    .forEach(System.out::println);   // 1, 2, 4, 8, ..., 512

Es gibt auch eine 3-Argument-Überladung Stream.iterate(seed, hasNext, next) (Java 9+), die die Abbruchbedingung in die Quelle einbettet — kein limit nötig:

Stream.iterate(1, n -> n < 1000, n -> n * 2).forEach(System.out::println);

Stream.generate(supplier) — ruft einen Supplier<T> wiederholt auf. Ungeordnet, kein Zusammenhang zwischen den Elementen:

Stream.generate(Math::random).limit(5).forEach(System.out::println);
Stream.generate(() -> "ping").limit(3).forEach(System.out::println);

Verwenden Sie iterate für Folgen, bei denen jedes Glied vom vorherigen abhängt (n -> n + 1, n -> n * 2, das Fibonacci-Paar arr -> {arr[1], arr[0] + arr[1]}). Verwenden Sie generate für unabhängige Werte aus einer Seitenquelle — Zufallszahlen, feste Konstanten, UUIDs.

Beenden Sie unendliche Streams in jedem Fall mit einer Short-Circuit-Operation: limit(n), dem 3-Argument-iterate oder einem Terminal wie findFirst / anyMatch. Ein einfaches toList() auf einem unendlichen Stream lässt die JVM hängen.

Aus I/O — Files.lines, BufferedReader.lines

Files.lines(path) öffnet eine Datei und gibt einen Stream<String> ihrer Zeilen zurück. Lazy: Zeilen werden gelesen, wenn die Pipeline sie anfordert, nicht vorab:

try (Stream<String> lines = Files.lines(Path.of("words.txt"))) {
    long longWords = lines.filter(w -> w.length() > 8).count();
}

Das try-with-resources ist zwingend erforderlich. Der Stream hält ein offenes Datei-Handle, und die einzige Möglichkeit, es freizugeben, ist der Aufruf von close() — was try-with-resources automatisch erledigt. Ohne es leckt der Descriptor bis zur Garbage Collection des Streams, was unter Last niemals passieren kann.

Gleiche Struktur für Readers über BufferedReader.lines(). Beide sind der kanonische Weg, eine Textdatei zeilenweise zu durchlaufen, ohne sie komplett in den Speicher zu laden.

String.chars() und String.codePoints()

Ein String ist eine Folge von UTF-16-Code-Einheiten; die API bietet beide Sichten:

"hello".chars()                   // IntStream of UTF-16 code units
       .filter(Character::isUpperCase)
       .count();

"héllo".codePoints()              // IntStream of Unicode code points
       .mapToObj(Character::toString)
       .forEach(System.out::println);

Beide geben IntStream zurück. chars() ist für ASCII ausreichend; für Inhalte, die Surrogate-Paare enthalten könnten (die meisten Emojis, viele Schriftsysteme), ist codePoints() die sichere Wahl.

Leere und einelementige Streams

Für Standardfälle und flatMap-Zweige:

Stream<String> none = Stream.empty();          // 0 elements
Stream<String> one  = Stream.of("x");          // exactly 1
Stream<String> opt  = Optional.of("x").stream();   // 1 if present, else empty

Optional.stream() (Java 9+) ist die Brücke zwischen Optional<T> und Stream<T> — praktisch, wenn Sie einen Stream von Optionals per flatMap in einen Stream vorhandener Werte umwandeln, ohne null-Buchführung.

Stream.Builder — Elemente einzeln hinzufügen

Wenn Sie die Quelle nicht als Literal, Array oder Generator ausdrücken können — meist weil Elemente aus verschiedenen Zweigen imperativen Codes stammen — gibt es einen Builder:

Stream.Builder<String> b = Stream.builder();
b.add("first");
if (someCondition) b.add("second");
b.accept("third");
Stream<String> s = b.build();

Nach build() ist der Builder versiegelt; weiteres add wirft eine Exception. Es ist ein seltenes, aber legitimes Werkzeug. Die meisten Code-Stellen, die danach greifen, lassen sich besser mit einer ArrayList<String> gefolgt von list.stream() schreiben, aber der Builder vermeidet diese Zwischenstruktur, wenn die Daten stückweise aufgebaut werden.

Map-Streams — die gibt es nicht

Map<K, V> hat keine stream()-Methode. Stattdessen streamen Sie ihre Sichten:

Map<String, Integer> ages = Map.of("Alice", 30, "Bob", 25);
ages.entrySet().stream().filter(e -> e.getValue() >= 18).map(Map.Entry::getKey).toList();
ages.keySet().stream().sorted().toList();
ages.values().stream().mapToInt(Integer::intValue).sum();

entrySet().stream() ist meistens das, was Sie brauchen — beide Hälften jedes Eintrags sind im Scope, und Map.Entry::getKey / ::getValue funktionieren als Methodenreferenzen.

Die richtige Quelle wählen

SituationVerwenden Sie
Sie haben bereits eine List, Set, Queuecoll.stream()
Sie haben wenige feste ElementeStream.of(a, b, c)
Sie haben ein T[]Arrays.stream(arr)
Sie haben int[], long[], double[]Arrays.stream(arr) → primitiver Stream
Sie wollen per Index iterierenIntStream.range(0, n)
Jedes Glied soll vom vorherigen abhängenStream.iterate
Sie wollen unabhängige StichprobenStream.generate
Sie wollen Zeilen einer TextdateiFiles.lines(path) in try-with-resources
Sie wollen Zeichen eines String"...".chars() oder .codePoints()
Sie brauchen einen leeren Fallback-StreamStream.empty()
Sie bauen stückweise aufStream.builder()
Sie wollen eine Map streamenmap.entrySet().stream()

Diese Tabelle deckt alles im Kapitel ab — und wahrscheinlich 99 % des echten Codes.

Ein durchgängiges Beispiel: zehn Quellen, ein Programm

Das folgende Programm erstellt aus jeder der wichtigsten Quellen einen Stream, führt eine kleine Terminaloperation darauf aus, damit die Ausgabe sichtbar ist, und gibt sowohl das Ergebnis als auch die Art der Quelle aus.

java— editable, runs on the server

Was man der Ausgabe entnehmen kann:

  • Jede Zeile in der Ausgabe stammt von einer anderen Quelle, aber alle fließen in dasselbe Vokabular aus Zwischen- und Terminaloperationen. Die Wahl der Quelle bestimmt, womit die Pipeline beginnt; sie ändert nichts daran, was danach kommt.
  • Arrays.stream(int[]) hat einen IntStream erzeugt — sum() steht direkt auf dem Stream, kein Boxing, kein Collectors.summingInt. Die primitiven Spezialisierungen sind wichtig bei numerischen Pipelines.
  • Die beiden Stream.iterate-Aufrufe zeigen den Unterschied zwischen iterate(seed, f) + limit(n) (Sie bestimmen die Anzahl) und dem 3-Argument-iterate(seed, hasNext, next) (die Quelle bestimmt die Anzahl). Beide sind begrenzt; ein iterate ohne Grenze und ohne eine Short-Circuit-Terminaloperation ist der klassische JVM-hängt-für-immer-Bug.
  • Stream.empty() und Optional.of(...).stream() sind der Weg, wie leere und einelementige Streams in eine Pipeline eintreten — typischerweise innerhalb eines flatMap-Zweigs, in dem manche Eingaben null oder ein Downstream-Element erzeugen.
  • Stream.builder() ist der Notausgang für den (seltenen) Fall, dass die Quelle imperativ über mehrere Zweige aufgebaut wird. Die meisten echten Programme greifen zuerst zu coll.stream().

Wie geht es weiter

Sie können nun jeden Stream aus jeder Quelle erstellen, die Sie tatsächlich haben. Die nächsten beiden Kapitel behandeln die Operationen zwischen Quelle und Ergebnis. Zuerst Java Stream Intermediate Operationsfilter, map, flatMap, distinct, sorted, peek, limit, skip — die lazy Transformationen, die den Stream umformen, ohne ihn auszuführen. Dann die Terminals, die den Wert erzeugen.

Übungen

Übung
Welches ist der *günstigste* Weg, die ganzen Zahlen von `0` bis `99` der Reihe nach als Stream zu iterieren?
Welches ist der *günstigste* Weg, die ganzen Zahlen von `0` bis `99` der Reihe nach als Stream zu iterieren?
Was this page helpful?