Alle Tabs der Lerneinheit (Erklärung · Interaktiv verstehen · Praxis-Übung · Klausur-Quiz) als durchgehender Text. Ideal zum Wiederholen vor der Klausur, und für Suchmaschinen wie Google, Bing und KI-Suche (ChatGPT, Perplexity).
Diese Lerneinheit wurde für typische Bachelor-Klausuren konzipiert. So prüfen wir · Fehler entdeckt? Melde ihn uns oder markiere die fragliche Stelle direkt im Text oben.
Alle Tabs der Lerneinheit (Erklärung · Interaktiv verstehen · Praxis-Übung · Klausur-Quiz) als durchgehender Text. Ideal zum Wiederholen vor der Klausur, und für Suchmaschinen wie Google, Bing und KI-Suche (ChatGPT, Perplexity).
In Programmieren 1 hast du map, filter und reduce als Lambdas kennengelernt. Die Stream-API ist das volle Werkzeug dahinter: eine Pipeline, die Daten durch mehrere Operationen schickt, lazy ausgewertet, abgeschlossen mit einer terminalen Operation und mächtigen Collectors zum Gruppieren und Aggregieren. In Programmieren 2 (z.B. TU Berlin, Mannheim) ist genau diese Tiefe Klausurstoff.
Was du können musst:
toList, groupingBy, joining, countingreduce, flatMap und primitive Streams (IntStream) anwendenVoraussetzung: Lambdas, funktionale Interfaces und Method-References (siehe Topic Lambdas & Higher-Order-Funktionen). Hier geht es um die Stream-API darauf aufbauend, nicht um die Lambda-Syntax selbst.
Klausur-Tipp: Bei "Was gibt diese Pipeline zurück?" gehst du Stufe für Stufe vor und notierst die Zwischen-Liste nach jeder Operation. Achte auf den Rückgabetyp der terminalen Op (List, String, Map, long, Optional), das ist oft die eigentliche Frage.
Anmelden, um den Fortschritt zu speichern.
Nächster Schritt
Aktives Abrufen festigt Wissen schneller als nochmal lesen.
In Programmieren 1 hast du map, filter und reduce als Lambdas kennengelernt. Die Stream-API ist das volle Werkzeug dahinter: eine Pipeline, die Daten durch mehrere Operationen schickt, lazy ausgewertet, abgeschlossen mit einer terminalen Operation und mächtigen Collectors zum Gruppieren und Aggregieren. In Programmieren 2 (z.B. TU Berlin, Mannheim) ist genau diese Tiefe Klausurstoff.
Was du können musst:
toList, groupingBy, joining, countingreduce, flatMap und primitive Streams (IntStream) anwendenVoraussetzung: Lambdas, funktionale Interfaces und Method-References (siehe Topic Lambdas & Higher-Order-Funktionen). Hier geht es um die Stream-API darauf aufbauend, nicht um die Lambda-Syntax selbst.
Ein Stream ist keine Datenstruktur, sondern eine Pipeline: eine Quelle, beliebig viele intermediate Operationen (lazy, geben wieder einen Stream zurück) und genau eine terminale Operation (startet die Verarbeitung und liefert ein Ergebnis).
List<String> namen = List.of("Anna", "Bob", "Cara");
String ergebnis = namen.stream() // Quelle
.filter(s -> s.length() == 4) // intermediate (lazy)
.map(String::toUpperCase) // intermediate (lazy)
.collect(Collectors.joining(", ")); // terminal (startet alles)
// ergebnis = "ANNA, CARA"
| Teil | Beispiele | Eigenschaft |
|---|---|---|
| Quelle | collection.stream(), Stream.of(...), IntStream.range(0, n), Arrays.stream(arr) | erzeugt den Stream |
| intermediate | filter, map, sorted, distinct, limit, flatMap, peek | lazy, geben einen Stream zurück, verkettbar |
| terminal | collect, reduce, forEach, count, findFirst, anyMatch, toList | eager, lösen die Verarbeitung aus, liefern Ergebnis oder void |
Faustregel zum Erkennen: gibt die Methode einen
Streamzurück, ist sie intermediate. Gibt sie etwas anderes zurück (Liste, Zahl,Optional,void), ist sie terminal und beendet die Pipeline.
Die intermediate Operationen bauen nur die Pipeline auf, sie rechnen noch nicht. Erst die terminale Operation zieht die Elemente durch, und zwar Element für Element, nicht Stufe für Stufe. Das erlaubt Short-Circuit: Operationen wie findFirst, anyMatch oder limit stoppen, sobald das Ergebnis feststeht.
Im Stepper siehst du, dass bei findFirst nur das erste passende Element komplett durch die Pipeline läuft, der Rest wird nie angefasst.
Interaktive Visualisierung
Interaktive Komponente: probiere sie im Topic-Player oben aus.
Die terminale Operation collect(...) ist die mächtigste. Sie nimmt einen Collector entgegen, ein Rezept, wie die Elemente zusammengefasst werden:
| Collector | Ergebnis | Wofür |
|---|---|---|
toList() / toSet() | List / Set | Elemente einsammeln |
toMap(k, v) | Map | Schlüssel-Wert-Paare (Duplikat-Key wirft, ohne merge-Funktion) |
groupingBy(classifier) | Map<K, List<V>> | nach einem Kriterium gruppieren |
partitioningBy(predicate) | Map<Boolean, List<V>> | in zwei Gruppen (true/false) teilen |
joining(", ") | String | Strings zusammenfügen |
counting() | Long | zählen |
summingInt / averagingInt | Integer / Double | summieren / mitteln |
groupingBy ist der Klausur-Klassiker. Im Stepper siehst du, wie eine Wörter-Liste nach Länge in eine Map gruppiert wird.
Interaktive Visualisierung
Interaktive Komponente: probiere sie im Topic-Player oben aus.
reduce kombiniert alle Elemente zu einem Wert. Mit Startwert (identity) bekommst du den Typ direkt, ohne Startwert ein Optional (der Stream könnte leer sein):
int summe = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum); // 10
Optional<Integer> max = Stream.of(1, 2, 3).reduce(Integer::max); // Optional[3]
flatMap glättet verschachtelte Strukturen (Stream von Listen wird ein Stream von Elementen):
List<List<Integer>> verschachtelt = List.of(List.of(1, 2), List.of(3, 4));
List<Integer> flach = verschachtelt.stream()
.flatMap(List::stream) // aus Stream<List<Integer>> wird Stream<Integer>
.toList(); // [1, 2, 3, 4]
Primitive Streams (IntStream, LongStream, DoubleStream) vermeiden Autoboxing und bieten sum, average, range:
int summe = IntStream.rangeClosed(1, 100).sum(); // 5050
Parallele Streams (stream().parallel()) verteilen die Arbeit auf mehrere Threads, lohnen sich aber nur bei großen, unabhängigen, zustandslosen Operationen, und die Reihenfolge ist dann nicht garantiert.
1. Pipeline = Quelle + intermediate (lazy) + genau eine terminale Op. Ohne terminale Operation passiert gar nichts, der Stream wird nie ausgewertet.
2. Intermediate erkennt man am Rückgabetyp Stream. filter/map/sorted geben einen Stream zurück (verkettbar), collect/count/findFirst liefern ein Ergebnis und beenden die Pipeline.
3. Ein Stream ist einmal nutzbar. Nach der terminalen Operation ist er verbraucht. Erneutes Verwenden wirft IllegalStateException.
4. Reihenfolge der Operationen zählt. filter vor map spart Arbeit (weniger Elemente zu transformieren), sorted ist teuer (braucht den ganzen Stream und ist ein Pipeline-Bremser).
5. groupingBy für Gruppen, toMap für eindeutige Schlüssel. groupingBy ergibt Map<K, List<V>>. toMap wirft bei doppelten Schlüsseln eine Exception, wenn keine merge-Funktion angegeben ist.
6. reduce ohne Startwert gibt Optional. Mit identity (z.B. reduce(0, ...)) bekommst du den Typ direkt; ohne, weil der Stream leer sein könnte, ein Optional.
1. Stream zweimal nutzen. var s = list.stream(); s.count(); s.forEach(...); wirft IllegalStateException: stream has already been operated upon. Pro Stream genau eine terminale Operation.
2. Intermediate ohne Terminal. list.stream().map(x -> teuer(x)); ohne terminale Op tut nichts, weil lazy. Kein Output, kein Seiteneffekt, der map-Code läuft nie.
3. forEach mit collect verwechseln. forEach liefert void (nur Seiteneffekt). Wer ein Ergebnis sammeln will, braucht collect oder reduce, nicht forEach mit manuellem Befüllen einer Liste.
4. toMap mit doppelten Schlüsseln. Ohne merge-Funktion knallt es bei Duplikaten (IllegalStateException: Duplicate key). Lösung: dritter Parameter (a, b) -> a oder groupingBy statt toMap.
5. reduce-Rückgabe falsch erwartet. Ohne identity ist das Ergebnis ein Optional, nicht der Wert direkt. Wer int summe = stream.reduce(Integer::sum) schreibt, bekommt einen Typfehler (es ist Optional<Integer>).
6. Seiteneffekte in parallelen Streams. parallelStream().forEach(e -> liste.add(e)) auf eine normale ArrayList ist eine Race Condition (nicht thread-safe). collect(Collectors.toList()) ist der sichere Weg, parallel-tauglich.
Eine Liste von Studierenden mit Noten läuft durch eine typische Klausur-Pipeline: bestandene herausfiltern, nach Note sortieren, die Namen extrahieren und zu einem String zusammenfügen. Beobachte, wie sich die Zwischen-Liste nach jeder Stufe verändert, und dass die Reihenfolge der Operationen das Ergebnis bestimmt.
Interaktive Visualisierung
Interaktive Komponente: probiere sie im Topic-Player oben aus.
Klausur-Tipp: Bei "Was gibt diese Pipeline zurück?" gehst du Stufe für Stufe vor und notierst die Zwischen-Liste nach jeder Operation. Achte auf den Rückgabetyp der terminalen Op (List, String, Map, long, Optional), das ist oft die eigentliche Frage.
Klausurfragen mit Lösungen (6)
Antwort: collect
Erklärung: collect ist terminal: es loest die Verarbeitung aus und liefert ein Ergebnis. filter, map und sorted sind intermediate (lazy) und geben wieder einen Stream zurueck. Erkennungsregel: gibt die Methode einen Stream zurueck, ist sie intermediate.
Antwort: Falsch
Erklärung: Falsch. Intermediate Operationen sind lazy, sie bauen nur die Pipeline auf. Ohne terminale Operation laeuft gar nichts, der map/filter-Code wird nie ausgefuehrt.
Typ: Wahr/Falsch
Stream.of("aa", "b", "ccc", "dd")
.filter(s -> s.length() >= 2)
.count();Antwort: 3
Erklärung: filter length >= 2 behaelt aa, ccc, dd (b faellt raus). count() zaehlt sie: 3. count ist eine terminale Operation, die einen long zurueckgibt.
Antwort: Collectors.groupingBy(String::length)
Erklärung: groupingBy(String::length) ergibt Map<Integer, List<String>>, alle Woerter gleicher Laenge landen in einer Liste. toMap wuerde bei gleicher Laenge (Duplikat-Key) eine Exception werfen; partitioningBy teilt nur in true/false (braucht ein Predicate).
Zuordnungen:
Erklärung: filter und map geben einen Stream zurueck (intermediate, lazy). collect und findFirst liefern ein Ergebnis und beenden die Pipeline (terminal).
Typ: Zuordnung
Stream.of(1, 2, 3, 4)
.filter(n -> n % 2 == 1)
.map(n -> n * 10)
.findFirst();Antwort: Wegen Lazy Evaluation und Short-Circuit: findFirst stoppt nach dem ersten Treffer
Erklärung: Lazy Evaluation zieht die Elemente einzeln durch die Pipeline. 1 passt durch filter (ungerade), wird zu 10 gemappt, findFirst nimmt es und schliesst kurz (Short-Circuit). 2, 3, 4 werden nie verarbeitet. Ergebnis: Optional[10].
Klausurfragen mit Lösungen (6)
Stream<Integer> s = Stream.of(1, 2, 3);
long n = s.count();
s.forEach(System.out::println);Antwort: IllegalStateException: stream has already been operated upon
Erklärung: Ein Stream ist einmal nutzbar. Nach der ersten terminalen Operation (count) ist er verbraucht, die zweite (forEach) wirft zur Laufzeit IllegalStateException. Loesung: einen neuen Stream aus der Quelle erzeugen.
Antwort: Intermediate geben einen Stream zurueck, terminale ein Ergebnis oder void
Erklärung: Am Rueckgabetyp: intermediate Operationen geben wieder einen Stream zurueck (verkettbar, lazy). Terminale Operationen geben etwas anderes zurueck (List, long, Optional, void) und loesen die Verarbeitung aus.
List.of("a", "bb", "cc", "ddd").stream()
.filter(s -> s.length() == 2)
.map(String::toUpperCase)
.collect(Collectors.joining("-"));Antwort: "BB-CC"
Erklärung: filter length == 2 behaelt bb und cc. map toUpperCase ergibt BB und CC. joining mit Trenner - fuegt sie zu BB-CC zusammen.
Lösungen pro Lücke:
Erklärung: Quelle + intermediate (lazy, geben Stream zurueck) + eine terminale Op (loest aus). groupingBy gruppiert in eine Map<K, List<V>>.
Typ: Lückentext
Antwort: Falsch
Erklärung: Falsch. Ohne identity gibt reduce ein Optional zurueck, weil der Stream leer sein koennte und es dann keinen Wert gaebe. Mit Startwert (z.B. reduce(0, Integer::sum)) bekommt man den Typ direkt.
Typ: Wahr/Falsch
Richtige Reihenfolge:
Erklärung: Quelle zuerst. filter vor map (weniger Elemente zu transformieren). sorted nach map (sortiert die Namen alphabetisch). collect als terminale Op am Ende. filter vor sorted ist effizient, weil weniger Elemente sortiert werden muessen.
Typ: Reihenfolge