Tutorium 24.06¶
Lambda¶
Ein Lambda ist in Java eine kurze Schreibweise für die Implementierung genau einer abstrakten Methode eines funktionalen Interfaces.
1@FunctionalInterface
2public interface Funktion2p {
3 double rechne(double x, double y);
4}
5
6Funktion2p f = (a, b) -> a + b;
Interface |
Bedeutung |
Methode |
Beispiel |
|---|---|---|---|
|
Eingabe |
|
|
|
Eingabe |
|
|
|
Eingabe |
|
|
|
keine Eingabe, Ausgabe |
|
|
|
zwei Eingaben, eine Ausgabe |
|
|
|
Eingabe und Ausgabe gleicher Typ |
|
|
|
zwei Eingaben gleicher Typ, Ausgabe gleicher Typ |
|
|
Frage 1¶
Was ist ein Lambda-Ausdruck in Java in einem Satz?
Lösung anzeigen
Ein Lambda-Ausdruck ist eine kurze Schreibweise für die Implementierung genau einer abstrakten Methode eines funktionalen Interfaces.
1Funktion1p quadrat = x -> x * x;
Das Lambda beschreibt hier, was die Methode rechne(double x) tun soll.
Frage 2¶
Warum kann Java mit diesem Ausdruck allein noch nichts anfangen?
(a, b) -> a + b
Lösung anzeigen
Der Ausdruck hat allein keinen eindeutigen Typ. Java weiß ohne Kontext nicht:
Welche Datentypen haben
aundb?Welcher Rückgabetyp ist gemeint?
Welche Methode soll dadurch implementiert werden?
Ist das eine
BiFunction, einDoubleBinaryOperator, ein eigenes Interface oder etwas anderes?
Deshalb braucht ein Lambda immer einen Zieltyp.
Frage 3¶
Warum funktioniert diese Zuweisung nicht?
var f = (a, b) -> a + b;
Lösung anzeigen
var muss den Typ aus der rechten Seite ableiten. Bei einem Lambda geht das
ohne Zieltyp nicht, weil das Lambda selbst Java nicht genug Informationen liefert.
Korrekt wäre zum Beispiel:
1BinaryOperator<Integer> f = (a, b) -> a + b;
oder mit eigenem Interface:
1Funktion2p f = (a, b) -> a + b;
Frage 4¶
Warum funktioniert diese Zuweisung?
Funktion2p f = (a, b) -> a + b;
Gegeben sei:
1@FunctionalInterface
2public interface Funktion2p {
3 double rechne(double x, double y);
4}
Lösung anzeigen
Funktion2p hat genau eine abstrakte Methode:
double rechne(double x, double y);
Der Compiler kann deshalb ableiten:
aundbsinddouble,das Ergebnis muss ein
doublesein,das Lambda implementiert die Methode
rechne.
Das Lambda ist also die Implementierung dieser Methode.
Frage 5¶
Was bedeutet @FunctionalInterface?
1@FunctionalInterface
2public interface Funktion1p {
3 double rechne(double x);
4}
Lösung anzeigen
@FunctionalInterface bedeutet: Dieses Interface soll genau eine abstrakte
Methode besitzen. Der Compiler prüft das.
Wenn man eine zweite abstrakte Methode hinzufügen würde, meldet der Compiler einen Fehler. Die Annotation ist nicht zwingend notwendig, macht die Absicht aber klar.
Frage 6¶
Ist folgendes Interface für ein Lambda geeignet? Begründe kurz.
1public interface Rechner {
2 double addiere(double a, double b);
3 double multipliziere(double a, double b);
4}
Lösung anzeigen
Nein. Das Interface hat zwei abstrakte Methoden. Ein Lambda kann aber nur
genau eine abstrakte Methode implementieren. Java wüsste nicht, ob das Lambda
addiere oder multipliziere implementieren soll.
Frage 7¶
Ist folgendes Interface für ein Lambda geeignet? Begründe kurz.
1public interface Aktion {
2 void ausfuehren();
3
4 default void beschreibungAusgeben() {
5 System.out.println("Eine Aktion wird ausgefuehrt.");
6 }
7}
Lösung anzeigen
Ja. Es gibt nur eine abstrakte Methode: void ausfuehren(). Die Methode
beschreibungAusgeben() ist eine default-Methode und besitzt bereits eine
Implementierung. Sie zählt daher nicht als abstrakte Methode.
Beispiel:
1Aktion a = () -> System.out.println("Hallo");
2a.ausfuehren();
Frage 8¶
Was steht bei einem Lambda links vom Pfeil und was rechts vom Pfeil?
x -> x * x
Lösung anzeigen
Links vom Pfeil steht der Parameter: x
Rechts vom Pfeil steht die Implementierung, also die Berechnung: x * x
Man kann es lesen als:
Nimm
xund lieferex * xzurück.
Frage 9¶
Sind diese beiden Lambdas gleichwertig?
x -> x * x
(x) -> x * x
Lösung anzeigen
Ja. Bei genau einem Parameter darf man die Klammern weglassen.
Bei mehreren Parametern braucht man Klammern:
(a, b) -> a + b
Bei keinem Parameter braucht man ebenfalls Klammern:
() -> Math.random()
Frage 10¶
Warum braucht dieses Lambda kein return?
x -> x * x
Warum braucht dieses Lambda ein return?
1x -> {
2 System.out.println(x);
3 return x * x;
4}
Lösung anzeigen
Das erste Lambda braucht kein return, weil rechts vom Pfeil nur ein
einzelner Ausdruck steht. Das Ergebnis dieses Ausdrucks wird automatisch
zurückgegeben.
Das zweite Lambda braucht return, weil der Lambda-Körper aus einem Block
mit geschweiften Klammern besteht. Bei einem Block muss man explizit angeben,
welcher Wert zurückgegeben wird.
Frage 11¶
Welches funktionale Interface aus Java passt zu dieser Idee?
text -> text.length()
Also: Ein String wird in einen Integer umgewandelt.
Lösung anzeigen
Passend ist Function<String, Integer>.
1Function<String, Integer> laenge = text -> text.length();
2System.out.println(laenge.apply("Hallo"));
Die Methode heißt bei Function: apply
Frage 12¶
Welches funktionale Interface aus Java passt zu dieser Idee?
text -> text.length() > 10
Also: Ein String wird auf true oder false geprüft.
Lösung anzeigen
Passend ist Predicate<String>.
1Predicate<String> istLang = text -> text.length() > 10;
2System.out.println(istLang.test("Programmieren"));
Die Methode heißt bei Predicate: test
Ein Predicate ist immer eine Prüfung auf true oder false.
Frage 13¶
Welches funktionale Interface aus Java passt zu dieser Idee?
text -> System.out.println(text)
Also: Ein String wird verarbeitet, aber es wird kein Wert zurückgegeben.
Lösung anzeigen
Passend ist Consumer<String>.
1Consumer<String> ausgabe = text -> System.out.println(text);
2ausgabe.accept("Hallo");
Die Methode heißt bei Consumer: accept
Ein Consumer verbraucht einen Wert, gibt aber keinen Wert zurück.
Frage 14¶
Welches funktionale Interface aus Java passt zu dieser Idee?
() -> Math.random()
Also: Es gibt keine Eingabe, aber es wird ein Wert erzeugt.
Lösung anzeigen
Passend ist Supplier<Double>.
1Supplier<Double> zufall = () -> Math.random();
2System.out.println(zufall.get());
Die Methode heißt bei Supplier: get
Ein Supplier liefert einen Wert, bekommt aber keine Eingabe.
Frage 15¶
Welches funktionale Interface aus Java passt zu dieser Idee?
(a, b) -> a + b
Also: Zwei Werte werden zu einem Ergebnis verarbeitet.
Lösung anzeigen
Passend ist zum Beispiel BiFunction<Integer, Integer, Integer>.
1BiFunction<Integer, Integer, Integer> addiere = (a, b) -> a + b;
2System.out.println(addiere.apply(3, 4));
Wenn man mit double arbeitet, ist oft diese Variante besser:
1DoubleBinaryOperator addiere = (a, b) -> a + b;
2System.out.println(addiere.applyAsDouble(3.0, 4.0));
Frage 16¶
Was gibt dieser Code aus?
1Predicate<String> istLang = text -> text.length() > 5;
2
3System.out.println(istLang.test("Hallo"));
4System.out.println(istLang.test("Programmieren"));
Lösung anzeigen
false
true
Erklärung:
"Hallo"hat 5 Zeichen.5 > 5istfalse."Programmieren"hat mehr als 5 Zeichen. Das Ergebnis isttrue.
Frage 17¶
Was gibt dieser Code aus?
1Function<String, Integer> laenge = text -> text.length();
2
3int ergebnis = laenge.apply("Java");
4System.out.println(ergebnis);
Lösung anzeigen
4
Das Lambda berechnet die Länge des Strings. "Java" hat 4 Zeichen.
Frage 18¶
Ersetze die anonyme Klasse durch ein Lambda.
1Runnable r = new Runnable() {
2 @Override
3 public void run() {
4 System.out.println("Programm laeuft");
5 }
6};
7
8r.run();
Lösung anzeigen
1Runnable r = () -> System.out.println("Programm laeuft");
2
3r.run();
Das funktioniert, weil Runnable genau eine abstrakte Methode hat:
void run()
Frage 19¶
Ersetze die anonyme Klasse durch ein Lambda.
1Comparator<String> nachLaenge = new Comparator<String>() {
2 @Override
3 public int compare(String a, String b) {
4 return a.length() - b.length();
5 }
6};
Lösung anzeigen
1Comparator<String> nachLaenge = (a, b) -> a.length() - b.length();
Noch etwas sauberer:
1Comparator<String> nachLaenge =
2 (a, b) -> Integer.compare(a.length(), b.length());
Oder mit Methodenreferenz:
1Comparator<String> nachLaenge = Comparator.comparingInt(String::length);
Für den Anfang ist aber die Lambda-Variante am besten verständlich.
Frage 20¶
Wann ist ein Lambda sinnvoll und wann eher nicht? Nenne jeweils ein Beispiel.
Lösung anzeigen
Ein Lambda ist sinnvoll, wenn eine kleine Funktion einmalig oder direkt an Ort und Stelle gebraucht wird.
1List<String> namen = List.of("Anna", "Max", "Christopher");
2namen.sort((a, b) -> a.length() - b.length());
Ein Lambda ist eher nicht sinnvoll, wenn die Logik lang, kompliziert oder fachlich wichtig ist. Dann ist eine benannte Methode meistens besser.
Lambdas sind gut für kurze, lokale Logik. Bei langer oder wichtiger Fachlogik ist eine benannte Methode meistens besser.
Collections¶
Collections sind Datenstrukturen, mit denen mehrere Werte gespeichert und verarbeitet werden können.
Typ |
Grundidee |
Typische Frage |
|---|---|---|
|
geordnete Sammlung, Duplikate erlaubt |
In welcher Reihenfolge stehen die Elemente? |
|
Sammlung ohne Duplikate |
Ist dieses Element enthalten? |
|
Zuordnung von Schlüssel zu Wert |
Welcher Wert gehört zu diesem Schlüssel? |
|
Warteschlange |
Wer ist als Nächstes dran? |
|
beidseitige Warteschlange |
Vorne oder hinten einfügen/entfernen? |
Interface |
Implementierung |
Eigenschaft |
|---|---|---|
|
|
Standardliste, schneller Zugriff per Index |
|
|
keine Duplikate, keine garantierte Reihenfolge |
|
|
keine Duplikate, Einfügereihenfolge bleibt erhalten |
|
|
keine Duplikate, sortierte Reihenfolge |
|
|
Standard-Map, schnelle Zuordnung |
|
|
Map mit Einfügereihenfolge |
|
|
Map mit sortierten Schlüsseln |
|
|
sehr effiziente Map, wenn der Schlüssel ein |
Frage 1¶
Du willst eine Liste von Namen speichern. Die Reihenfolge soll erhalten bleiben und Namen dürfen mehrfach vorkommen. Welche Collection passt?
Lösung anzeigen
Passend ist eine List<String>, meistens eine ArrayList<String>.
1List<String> namen = new ArrayList<>();
Grund:
Reihenfolge bleibt erhalten.
Duplikate sind erlaubt.
Man kann per Index zugreifen.
Frage 2¶
Du willst speichern, welche Studierenden bei einer Veranstaltung anwesend waren. Jede Person soll nur einmal vorkommen. Welche Collection passt?
Lösung anzeigen
Passend ist ein Set<String>, meistens ein HashSet<String>.
1Set<String> anwesend = new HashSet<>();
Grund:
Jede Person soll nur einmal vorkommen.
Ein
Setverhindert Duplikate automatisch.
Frage 3¶
Du willst zu einer Matrikelnummer den Namen eines Studierenden speichern.
12345 -> "Anna"
67890 -> "Max"
Welche Collection passt?
Lösung anzeigen
Passend ist eine Map<Integer, String>.
1Map<Integer, String> studenten = new HashMap<>();
2studenten.put(12345, "Anna");
3studenten.put(67890, "Max");
Eine Map speichert eine Zuordnung von Schlüssel zu Wert.
Frage 4¶
Was ist der wichtigste Unterschied zwischen List und Set?
Lösung anzeigen
Eine
Listerlaubt Duplikate und hat eine feste Reihenfolge.Ein
Seterlaubt keine Duplikate.
1List<String> liste = new ArrayList<>();
2liste.add("Anna");
3liste.add("Anna");
4
5Set<String> set = new HashSet<>();
6set.add("Anna");
7set.add("Anna");
8
9System.out.println(liste); // [Anna, Anna]
10System.out.println(set); // [Anna]
Frage 5¶
Was ist der wichtigste Unterschied zwischen Set und Map?
Lösung anzeigen
Ein
Setspeichert einzelne Werte.Eine
Mapspeichert Paare aus Schlüssel und Wert.
1Set<String> namen = new HashSet<>();
2namen.add("Anna");
3
4Map<String, Integer> alter = new HashMap<>();
5alter.put("Anna", 22);
Bei der Map fragt man: Welcher Wert gehört zu diesem Schlüssel?
Frage 6¶
Was gibt dieser Code aus?
1List<String> namen = new ArrayList<>();
2
3namen.add("Anna");
4namen.add("Max");
5namen.add("Anna");
6
7System.out.println(namen);
Lösung anzeigen
[Anna, Max, Anna]
Eine List erlaubt Duplikate und behält die Reihenfolge bei.
Frage 7¶
Was gibt dieser Code ungefähr aus? Warum steht "Anna" nicht zweimal darin?
1Set<String> namen = new HashSet<>();
2
3namen.add("Anna");
4namen.add("Max");
5namen.add("Anna");
6
7System.out.println(namen);
Lösung anzeigen
Mögliche Ausgabe:
[Max, Anna]
"Anna" steht nur einmal darin. Ein Set speichert jedes Element nur
einmal. Das zweite add("Anna") fügt keinen neuen Eintrag hinzu.
Wichtig: Die Reihenfolge bei einem HashSet ist nicht garantiert.
Frage 8¶
Warum sollte man sich bei einem HashSet nicht auf die Reihenfolge der Ausgabe
verlassen?
Lösung anzeigen
Bei einem HashSet ist die Reihenfolge intern nicht definiert. Sie hängt
vom Hashwert der Elemente ab und kann sich ändern.
Wenn die Einfügereihenfolge wichtig ist:
Set<String> namen = new LinkedHashSet<>();
Wenn eine sortierte Reihenfolge wichtig ist:
Set<String> namen = new TreeSet<>();
Frage 9¶
Welche Collection passt, wenn jedes Element nur einmal vorkommen soll, aber die Einfügereihenfolge erhalten bleiben soll?
Lösung anzeigen
Passend ist LinkedHashSet.
1Set<String> namen = new LinkedHashSet<>();
2
3namen.add("Anna");
4namen.add("Max");
5namen.add("Anna");
6namen.add("Lena");
7
8System.out.println(namen);
[Anna, Max, Lena]
Frage 10¶
Welche Collection passt, wenn jedes Element nur einmal vorkommen soll und automatisch sortiert werden soll?
Lösung anzeigen
Passend ist TreeSet.
1Set<String> namen = new TreeSet<>();
2
3namen.add("Max");
4namen.add("Anna");
5namen.add("Lena");
6namen.add("Anna");
7
8System.out.println(namen);
[Anna, Lena, Max]
TreeSet entfernt Duplikate und sortiert die Elemente automatisch.
Frage 11¶
Was gibt dieser Code aus? Warum?
1Map<String, Integer> alter = new HashMap<>();
2
3alter.put("Anna", 22);
4alter.put("Max", 25);
5alter.put("Anna", 23);
6
7System.out.println(alter.get("Anna"));
Lösung anzeigen
23
In einer Map kann jeder Schlüssel nur einmal vorkommen. Der zweite
put-Aufruf mit dem Schlüssel "Anna" überschreibt den ersten Wert.
Frage 12¶
Was ist der Unterschied zwischen diesen beiden Operationen?
namen.add("Anna");
alter.put("Anna", 22);
Lösung anzeigen
add fügt ein einzelnes Element in eine Collection ein.
put speichert eine Zuordnung in einer Map:
Schlüssel: "Anna"
Wert: 22
Bei add geht es um einzelne Elemente.
Bei put geht es um Schlüssel-Wert-Paare.
Frage 13¶
Was passiert, wenn man bei einer Map einen Wert mit einem bereits vorhandenen
Schlüssel einfügt?
1Map<String, String> emails = new HashMap<>();
2
3emails.put("anna", "anna@example.com");
4emails.put("anna", "anna.neu@example.com");
5
6System.out.println(emails);
Lösung anzeigen
{anna=anna.neu@example.com}
Der zweite put-Aufruf überschreibt den alten Wert zum gleichen Schlüssel.
Frage 14¶
Welche Collection würdest du verwenden, um zu zählen, wie oft jeder Name vorkommt? Begründe kurz.
1List<String> namen = List.of(
2 "Anna", "Max", "Anna", "Lena", "Max", "Anna"
3);
Erwartetes Ergebnis:
Anna -> 3
Max -> 2
Lena -> 1
Lösung anzeigen
Man verwendet eine Map<String, Integer>.
1Map<String, Integer> counts = new HashMap<>();
Grund: Man will zu jedem Namen eine Anzahl speichern.
Name -> Anzahl
Das ist genau der typische Einsatzzweck einer Map.
Frage 15¶
Ergänze den Code, sodass die Namen gezählt werden.
1List<String> namen = List.of(
2 "Anna", "Max", "Anna", "Lena", "Max", "Anna"
3);
4
5Map<String, Integer> counts = new HashMap<>();
6
7for (String name : namen) {
8 // Was fehlt hier?
9}
10
11System.out.println(counts);
Lösung anzeigen
Eine verständliche Lösung mit containsKey:
1for (String name : namen) {
2 if (counts.containsKey(name)) {
3 counts.put(name, counts.get(name) + 1);
4 } else {
5 counts.put(name, 1);
6 }
7}
Kompakter mit getOrDefault:
1for (String name : namen) {
2 counts.put(name, counts.getOrDefault(name, 0) + 1);
3}
Noch kompakter mit merge:
1for (String name : namen) {
2 counts.merge(name, 1, Integer::sum);
3}
Für Anfänger ist die containsKey-Variante oft am besten, weil man den
Ablauf klar sieht.
Frage 16¶
Was macht getOrDefault in diesem Beispiel?
counts.put(name, counts.getOrDefault(name, 0) + 1);
Lösung anzeigen
Diese Zeile bedeutet:
Wenn
nameschon in der Map steht, nimm den bisherigen Wert.Wenn
namenoch nicht in der Map steht, nimm stattdessen0.Addiere
1.Speichere den neuen Wert wieder in der Map.
Beispiel:
counts.getOrDefault("Anna", 0)
liefert entweder den bisherigen Wert von "Anna" oder 0, falls
"Anna" noch nicht vorhanden ist.
Frage 17¶
Was gibt dieser Code aus?
1List<String> namen = List.of("Anna", "Max", "Lena");
2
3System.out.println(namen.contains("Max"));
4System.out.println(namen.contains("Tom"));
Lösung anzeigen
true
false
contains prüft, ob ein Element enthalten ist.
Frage 18¶
Warum ist ein Set oft besser geeignet als eine List, wenn man sehr oft
prüfen möchte, ob ein Element enthalten ist?
Lösung anzeigen
Bei einer List muss Java im schlechtesten Fall alle Elemente nacheinander
durchsuchen.
Ein HashSet ist für Mitgliedschaftsprüfungen typischerweise deutlich
besser geeignet:
1Set<String> verboteneNamen = new HashSet<>();
2
3verboteneNamen.add("admin");
4verboteneNamen.add("root");
5verboteneNamen.add("system");
6
7if (verboteneNamen.contains(name)) {
8 System.out.println("Name ist nicht erlaubt.");
9}
Frage 19¶
Entferne Duplikate aus einer Liste, aber behalte die ursprüngliche Reihenfolge.
1List<String> namen = List.of(
2 "Anna", "Max", "Anna", "Lena", "Max"
3);
Erwartetes Ergebnis:
[Anna, Max, Lena]
Welche Collection hilft?
Lösung anzeigen
Passend ist ein LinkedHashSet.
1Set<String> eindeutig = new LinkedHashSet<>(namen);
2System.out.println(eindeutig);
[Anna, Max, Lena]
Wenn wieder eine List gebraucht wird:
1List<String> ohneDuplikate = new ArrayList<>(new LinkedHashSet<>(namen));
Frage 20¶
Gruppiere Studierende nach Kurs. Gesucht ist eine Struktur wie:
Programmieren -> [Anna, Max]
Datenbanken -> [Lena, Max]
Welche Collection-Kombination passt?
Lösung anzeigen
Passend ist Map<String, List<String>>.
1Map<String, List<String>> kurse = new HashMap<>();
2
3kurse.computeIfAbsent("Programmieren", k -> new ArrayList<>()).add("Anna");
4kurse.computeIfAbsent("Programmieren", k -> new ArrayList<>()).add("Max");
5kurse.computeIfAbsent("Datenbanken", k -> new ArrayList<>()).add("Lena");
6kurse.computeIfAbsent("Datenbanken", k -> new ArrayList<>()).add("Max");
7
8System.out.println(kurse);
Der Typ Map<String, List<String>> bedeutet:
Kursname -> Liste von Studierenden
Jeder Schlüssel führt also nicht zu einem einzelnen Wert, sondern zu einer Liste von Werten.
Frage 21¶
Welche Collection passt für eine Warteschlange, zum Beispiel bei Druckaufträgen?
Lösung anzeigen
Passend ist eine Queue, zum Beispiel mit ArrayDeque.
1Queue<String> druckauftraege = new ArrayDeque<>();
2
3druckauftraege.add("Dokument1.pdf");
4druckauftraege.add("Dokument2.pdf");
5
6System.out.println(druckauftraege.poll());
7System.out.println(druckauftraege.poll());
Dokument1.pdf
Dokument2.pdf
Eine Queue arbeitet nach dem Prinzip: First In, First Out. Der erste Auftrag wird zuerst verarbeitet.
Frage 22¶
Welche Collection passt, wenn man Elemente vorne und hinten einfügen oder entfernen will?
Lösung anzeigen
Passend ist eine Deque, zum Beispiel mit ArrayDeque.
1Deque<String> namen = new ArrayDeque<>();
2
3namen.addFirst("Anna");
4namen.addLast("Max");
5
6System.out.println(namen.removeFirst());
7System.out.println(namen.removeLast());
Eine Deque kann vorne und hinten arbeiten.
Frage 23¶
Was ist an diesem Code problematisch?
1Map<String, Integer> lager = new HashMap<>();
2
3lager.put("Apfel", 30);
4lager.put("Banane", 12);
5
6System.out.println(lager.get("Milch") + 1);
Lösung anzeigen
lager.get("Milch") liefert null, weil "Milch" nicht in der Map
steht. Dann versucht Java sinngemäß:
null + 1
Das führt zu einer NullPointerException.
Frage 24¶
Wie kann man das Problem aus Frage 23 mit getOrDefault lösen?
Lösung anzeigen
1Map<String, Integer> lager = new HashMap<>();
2
3lager.put("Apfel", 30);
4lager.put("Banane", 12);
5
6System.out.println(lager.getOrDefault("Milch", 0) + 1);
1
Wenn "Milch" nicht vorhanden ist, wird 0 verwendet.
Frage 25¶
Was ist der Unterschied zwischen keySet, values und entrySet bei
einer Map?
Lösung anzeigen
1Map<String, Integer> alter = new HashMap<>();
2
3alter.put("Anna", 22);
4alter.put("Max", 25);
5
6System.out.println(alter.keySet()); // alle Schlüssel
7System.out.println(alter.values()); // alle Werte
8System.out.println(alter.entrySet()); // alle Schlüssel-Wert-Paare
Mögliche Ausgabe:
[Anna, Max]
[22, 25]
[Anna=22, Max=25]
Die genaue Reihenfolge ist bei HashMap nicht garantiert.
Frage 26¶
Welche Schleife ist meistens besser, wenn man sowohl Schlüssel als auch Wert
einer Map braucht?
Variante A:
1for (String key : map.keySet()) {
2 System.out.println(key + ": " + map.get(key));
3}
Variante B:
1for (Map.Entry<String, Integer> entry : map.entrySet()) {
2 System.out.println(entry.getKey() + ": " + entry.getValue());
3}
Lösung anzeigen
Meistens ist Variante B besser. Man bekommt Schlüssel und Wert direkt zusammen, ohne für jeden Schlüssel den Wert erneut aus der Map zu holen.
Frage 27¶
Sortiere Namen alphabetisch und entferne gleichzeitig Duplikate.
1List<String> namen = List.of(
2 "Max", "Anna", "Lena", "Anna", "Tom"
3);
Erwartetes Ergebnis:
[Anna, Lena, Max, Tom]
Welche Collection passt?
Lösung anzeigen
Passend ist ein TreeSet.
1Set<String> sortiert = new TreeSet<>(namen);
2System.out.println(sortiert);
[Anna, Lena, Max, Tom]
TreeSet entfernt Duplikate und sortiert die Elemente. Wenn wieder eine
Liste gebraucht wird:
1List<String> ergebnis = new ArrayList<>(new TreeSet<>(namen));
Frage 28¶
Warum sollte man meistens gegen das Interface programmieren?
List<String> namen = new ArrayList<>();
statt:
ArrayList<String> namen = new ArrayList<>();
Lösung anzeigen
Der Rest des Codes muss nur wissen: Das ist eine List. Ob intern eine
ArrayList, LinkedList oder eine andere Implementierung verwendet wird,
ist oft egal.
Man kann später leichter austauschen:
List<String> namen = new LinkedList<>();
ohne den restlichen Code stark zu verändern.
Verwende links meistens das Interface, rechts die konkrete Implementierung.
Mini-Aufgabe¶
Ein kleines Programm soll Bestellungen auswerten.
1List<String> bestellungen = List.of(
2 "Apfel", "Banane", "Apfel", "Milch", "Banane", "Apfel"
3);
Bearbeite folgende Teilaufgaben:
Speichere alle bestellten Produkte in der ursprünglichen Reihenfolge.
Ermittle alle unterschiedlichen Produkte (Reihenfolge egal).
Ermittle alle unterschiedlichen Produkte in der Reihenfolge ihres ersten Auftretens.
Ermittle, wie oft jedes Produkt bestellt wurde.
Gib alle Produkte mit ihrer Anzahl aus.
Lösung anzeigen
1import java.util.*;
2
3public class BestellAuswertung {
4 public static void main(String[] args) {
5 List<String> bestellungen = List.of(
6 "Apfel", "Banane", "Apfel", "Milch", "Banane", "Apfel"
7 );
8
9 // 1. Ursprüngliche Reihenfolge speichern
10 List<String> alleBestellungen = new ArrayList<>(bestellungen);
11
12 // 2. Unterschiedliche Produkte, Reihenfolge egal
13 Set<String> unterschiedlicheProdukte = new HashSet<>(bestellungen);
14
15 // 3. Unterschiedliche Produkte, Reihenfolge des ersten Auftretens
16 Set<String> produkteInReihenfolge = new LinkedHashSet<>(bestellungen);
17
18 // 4. Anzahl pro Produkt zählen
19 Map<String, Integer> counts = new HashMap<>();
20 for (String produkt : bestellungen) {
21 counts.put(produkt, counts.getOrDefault(produkt, 0) + 1);
22 }
23
24 // 5. Ausgabe
25 System.out.println("Alle Bestellungen:");
26 System.out.println(alleBestellungen);
27
28 System.out.println("Unterschiedliche Produkte:");
29 System.out.println(unterschiedlicheProdukte);
30
31 System.out.println("Unterschiedliche Produkte in Reihenfolge:");
32 System.out.println(produkteInReihenfolge);
33
34 System.out.println("Anzahl pro Produkt:");
35 for (Map.Entry<String, Integer> entry : counts.entrySet()) {
36 System.out.println(entry.getKey() + ": " + entry.getValue());
37 }
38 }
39}
Mögliche Ausgabe:
Alle Bestellungen:
[Apfel, Banane, Apfel, Milch, Banane, Apfel]
Unterschiedliche Produkte:
[Apfel, Milch, Banane]
Unterschiedliche Produkte in Reihenfolge:
[Apfel, Banane, Milch]
Anzahl pro Produkt:
Apfel: 3
Milch: 1
Banane: 2
Hinweis: Die Reihenfolge bei HashSet und HashMap ist nicht garantiert.
Die Ausgabe dort kann anders sortiert erscheinen.
Streams¶
Ein Stream in Java ermöglicht es, eine Folge von Elementen deklarativ zu verarbeiten. Statt einer Schleife beschreibt man, was mit den Elementen passieren soll, nicht wie.
1List<String> namen = List.of("Anna", "Max", "Lena", "Tom");
2
3List<String> ergebnis = namen.stream()
4 .filter(n -> n.length() > 3)
5 .sorted()
6 .collect(Collectors.toList());
7
8System.out.println(ergebnis); // [Anna, Lena]
Operation |
Beschreibung |
Beispiel |
|---|---|---|
|
Nur Elemente durchlassen, die eine Bedingung erfüllen |
|
|
Jedes Element in etwas anderes umwandeln |
|
|
Elemente sortieren (natürlich oder mit Comparator) |
|
|
Ergebnis zusammenfassen, z. B. in eine Liste |
|
|
Jedes Element verarbeiten (kein Rückgabewert) |
|
|
Anzahl der verbleibenden Elemente zählen |
|
|
Duplikate entfernen |
|
|
Nur die ersten N Elemente weitergeben |
|
|
|
|
Frage 1¶
Was gibt dieses Programm aus?
1import java.util.*;
2
3public class Pipeline1 {
4
5 public static void main(String[] args) {
6 List<String> strings = Arrays.asList("bb", "ZZ", "aa", "PP", "zz");
7
8 strings
9 .stream()
10 .sorted()
11 .map(String::toUpperCase)
12 .forEach(n -> System.out.print(n + " "));
13 }
14}
Lösung anzeigen
PP ZZ AA BB ZZ
sorted() verwendet die natürliche Reihenfolge von Strings. Großbuchstaben
haben niedrigere Unicode-Werte als Kleinbuchstaben (z. B. 'Z' = 90,
'a' = 97). Deshalb kommen "PP" und "ZZ" vor "aa", "bb"
und "zz".
Sortiert: ["PP", "ZZ", "aa", "bb", "zz"]
Erst danach wandelt map(String::toUpperCase) alles in Großbuchstaben um.
Die Reihenfolge ändert sich dabei nicht mehr.
Original |
nach |
nach |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Frage 2¶
Was gibt dieses Programm aus? Erkläre jeden Schritt kurz.
1import java.util.*;
2
3public class Pipeline2 {
4
5 public static void main(String[] args) {
6 List<String> strings = Arrays.asList(
7 "bbb", "ZZZ", "aaa", "PPP",
8 "zzz", "fff", "vvv"
9 );
10
11 boolean i = strings
12 .stream()
13 .distinct()
14 .sorted((a, b) -> a.compareToIgnoreCase(b))
15 .limit(4)
16 .map(a -> a.substring(0, 2))
17 .noneMatch(a -> a.equals("aa"));
18
19 System.out.println(i);
20 }
21}
Lösung anzeigen
false
Schrittweise Erklärung:
.distinct(): Alle Elemente sind bereits eindeutig — keine Änderung..sorted((a, b) -> a.compareToIgnoreCase(b)): Sortiert ohne Unterschied zwischen Groß- und Kleinschreibung:["aaa", "bbb", "fff", "PPP", "vvv", "ZZZ", "zzz"].limit(4): Nur die ersten 4 Elemente behalten:["aaa", "bbb", "fff", "PPP"].map(a -> a.substring(0, 2)): Jedes Element auf die ersten 2 Zeichen kürzen:["aa", "bb", "ff", "PP"].noneMatch(a -> a.equals("aa")): Prüft, ob kein Element gleich"aa"ist. Da"aa"vorhanden ist, liefertnoneMatchfalse.
Frage 3¶
Ergänze die zwei fehlenden Stream-Operationen, sodass nur die geraden Zahlen aus der Liste aufsteigend sortiert ausgegeben werden.
Erwartete Ausgabe:
2
4
6
1import java.util.*;
2
3public class Pipeline3 {
4
5 public static void main(String[] args) {
6
7 List<Integer> intArray = Arrays.asList(7, 6, 5, 4, 3, 2, 1);
8
9 intArray
10 .stream()
11 ._____________________________
12 ._____________________________
13 .forEach(System.out::println);
14 }
15}
Lösung anzeigen
1intArray
2 .stream()
3 .filter(n -> n % 2 == 0)
4 .sorted()
5 .forEach(System.out::println);
filter(n -> n % 2 == 0)behält nur gerade Zahlen:[6, 4, 2]sorted()sortiert sie aufsteigend:[2, 4, 6]forEachgibt jede Zahl in einer eigenen Zeile aus.
Wichtig: erst filtern, dann sortieren — so werden nur 3 statt 7 Elemente sortiert.
Aufgaben¶
Aufgabe 1: Benachrichtigungssystem¶
Eine Anwendung verwaltet verschiedene Arten von Benachrichtigungen. Jede Benachrichtigung hat einen Empfänger und einen Text. Es gibt unterschiedliche Kanäle, zum Beispiel E-Mail und SMS.
Gegeben ist folgendes Grundgerüst:
1public abstract class Nachricht {
2
3 private final String empfaenger;
4 private final String text;
5
6 public Nachricht(String empfaenger, String text) {
7 this.empfaenger = empfaenger;
8 this.text = text;
9 }
10
11 public String getEmpfaenger() {
12 return empfaenger;
13 }
14
15 public String getText() {
16 return text;
17 }
18
19 public abstract String kanal();
20
21 public abstract int prioritaet();
22}
1public class EmailNachricht extends Nachricht {
2 public EmailNachricht(String empfaenger, String text) {
3 super(empfaenger, text);
4 }
5
6 @Override
7 public String kanal() { return "E-Mail"; }
8
9 @Override
10 public int prioritaet() { return 1; }
11}
12
13public class SmsNachricht extends Nachricht {
14 public SmsNachricht(String empfaenger, String text) {
15 super(empfaenger, text);
16 }
17
18 @Override
19 public String kanal() { return "SMS"; }
20
21 @Override
22 public int prioritaet() { return 2; }
23}
24
25@FunctionalInterface
26public interface NachrichtenFormat {
27 String formatiere(Nachricht n);
28}
In der main-Methode existiert folgende Liste:
1List<Nachricht> nachrichten = new ArrayList<>();
2
3nachrichten.add(new EmailNachricht("anna@example.com", "Termin morgen"));
4nachrichten.add(new SmsNachricht("Max", "Bitte zurueckrufen"));
5nachrichten.add(new EmailNachricht("lena@example.com", "Unterlagen erhalten"));
6nachrichten.add(new SmsNachricht("Anna", "Raum wurde geaendert"));
Aufgaben:
Erkläre, warum folgender Code bei SMS-Nachrichten
"SMS"und bei E-Mail-Nachrichten"E-Mail"ausgibt, obwohl die Liste den TypList<Nachricht>hat.1for (Nachricht n : nachrichten) { 2 System.out.println(n.kanal()); 3}
Erstelle ein Lambda vom Typ
NachrichtenFormat, das eine Nachricht in folgender Form formatiert:KANAL an EMPFAENGER: TEXTBeispiel:
SMS an Max: Bitte zurueckrufenWende das Lambda auf alle Nachrichten an und gib das Ergebnis aus.
Sortiere die Liste so, dass Nachrichten mit höherer Priorität zuerst kommen (
SmsNachricht.prioritaet() == 2,EmailNachricht.prioritaet() == 1).Erstelle eine Collection, die alle Empfänger enthält, aber jeden Empfänger nur einmal. Die Reihenfolge des ersten Auftretens soll erhalten bleiben.
Erstelle eine
Map, die zählt, wie viele Nachrichten pro Kanal vorhanden sind. Erwartete Idee:{SMS=2, E-Mail=2}Beantworte kurz:
Warum wird für
nachrichteneineListverwendet?Warum ist für eindeutige Empfänger ein
Setsinnvoll?Warum braucht das Lambda aus Teilaufgabe 2 ein Interface?
Warum wäre
var format = n -> n.kanal();nicht erlaubt?
Lösung anzeigen
Lösung 1 – Polymorphie
Obwohl die Liste den Typ List<Nachricht> hat, befinden sich darin konkrete
Objekte vom Typ EmailNachricht und SmsNachricht. Beim Methodenaufruf
n.kanal() entscheidet Java zur Laufzeit, welche konkrete Implementierung
verwendet wird. Das nennt man Polymorphie.
Lösung 2 – Lambda erstellen
1NachrichtenFormat format =
2 n -> n.kanal() + " an " + n.getEmpfaenger() + ": " + n.getText();
Das Lambda implementiert die Methode String formatiere(Nachricht n) des
funktionalen Interfaces NachrichtenFormat.
Lösung 3 – Lambda anwenden
1for (Nachricht n : nachrichten) {
2 System.out.println(format.formatiere(n));
3}
E-Mail an anna@example.com: Termin morgen
SMS an Max: Bitte zurueckrufen
E-Mail an lena@example.com: Unterlagen erhalten
SMS an Anna: Raum wurde geaendert
Lösung 4 – Sortieren nach Priorität
1nachrichten.sort((a, b) -> Integer.compare(b.prioritaet(), a.prioritaet()));
b wird mit a verglichen, damit höhere Priorität zuerst steht. Die
Variante mit Integer.compare ist robuster als eine einfache Subtraktion.
Lösung 5 – Eindeutige Empfänger
Da die Reihenfolge des ersten Auftretens erhalten bleiben soll, passt ein
LinkedHashSet.
1Set<String> empfaenger = new LinkedHashSet<>();
2
3for (Nachricht n : nachrichten) {
4 empfaenger.add(n.getEmpfaenger());
5}
6
7System.out.println(empfaenger);
[anna@example.com, Max, lena@example.com, Anna]
Ein HashSet würde ebenfalls Duplikate entfernen, aber keine Reihenfolge
garantieren.
Lösung 6 – Nachrichten pro Kanal zählen
1Map<String, Integer> counts = new HashMap<>();
2
3for (Nachricht n : nachrichten) {
4 String kanal = n.kanal();
5 counts.put(kanal, counts.getOrDefault(kanal, 0) + 1);
6}
7
8System.out.println(counts);
{SMS=2, E-Mail=2}
Lösung 7 – Verständnisfragen
Eine
Listwird verwendet, weil die Reihenfolge erhalten bleiben soll und mehrere Nachrichten vom gleichen Typ erlaubt sind.Ein
Setist sinnvoll, weil jeder Empfänger nur einmal vorkommen soll.Ein Lambda braucht ein funktionales Interface, damit Java weiß, welche abstrakte Methode das Lambda implementiert.
var format = n -> n.kanal();ist nicht erlaubt, weil Java den Typ vonnund das gemeinte Interface ohne Zieltyp nicht ableiten kann.
Aufgabe 2: Shop-Auswertung¶
Ein kleiner Shop verwaltet Produkte. Produkte haben einen Namen, eine Kategorie und einen Preis. Es gibt verschiedene Produktarten.
Gegeben ist folgendes Grundgerüst:
1public enum Kategorie {
2 BUCH,
3 LEBENSMITTEL,
4 TECHNIK
5}
6
7public abstract class Produkt {
8
9 private final String name;
10 private final Kategorie kategorie;
11
12 public Produkt(String name, Kategorie kategorie) {
13 this.name = name;
14 this.kategorie = kategorie;
15 }
16
17 public String getName() { return name; }
18 public Kategorie getKategorie() { return kategorie; }
19
20 public abstract double preis();
21}
22
23public class Buch extends Produkt {
24 private final double preis;
25
26 public Buch(String name, double preis) {
27 super(name, Kategorie.BUCH);
28 this.preis = preis;
29 }
30
31 @Override
32 public double preis() { return preis; }
33}
34
35public class Lebensmittel extends Produkt {
36 private final double preis;
37
38 public Lebensmittel(String name, double preis) {
39 super(name, Kategorie.LEBENSMITTEL);
40 this.preis = preis;
41 }
42
43 @Override
44 public double preis() { return preis; }
45}
46
47@FunctionalInterface
48public interface RabattRegel {
49 double berechne(Produkt p);
50}
Der folgende Code soll Produkte auswerten. Er enthält aber mehrere Fehler:
1List<Produkt> warenkorb = List.of(
2 new Buch("Java Basics", 30.0),
3 new Lebensmittel("Apfel", 1.0),
4 new Buch("Java Basics", 30.0)
5);
6
7RabattRegel regel = p -> {
8 if (p.preis() > 20) {
9 p.preis() * 0.9;
10 }
11
12 p.preis();
13};
14
15Map<Kategorie, Double> summen = new EnumMap<>();
16
17for (Produkt p : warenkorb) {
18 summen.put(
19 p.getKategorie(),
20 summen.get(p.getKategorie()) + regel.berechne(p)
21 );
22}
23
24Set<Produkt> eindeutigeProdukte = new HashSet<>(warenkorb);
25
26System.out.println(eindeutigeProdukte.size());
Aufgaben:
Finde mindestens vier Probleme im Code und erkläre jeweils kurz, warum sie problematisch sind.
Korrigiere die Rabattregel. Produkte über 20 Euro erhalten 10 % Rabatt, alle anderen behalten ihren normalen Preis.
Korrigiere die Erstellung der
EnumMap.Berechne die Summe der (rabattierten) Preise pro Kategorie korrekt. Erwartete Idee:
{BUCH=54.0, LEBENSMITTEL=1.0}Erkläre, warum die beiden Bücher mit dem Namen
"Java Basics"durchnew HashSet<>(warenkorb)nicht automatisch als Duplikate erkannt werden. Nenne eine einfache Alternative, wenn nur eindeutige Produktnamen gesucht sind.Beantworte kurz:
Warum ist
Produkteine abstrakte Klasse?Warum ist
RabattRegelein Interface und keine Klasse?Warum passt
EnumMapgut zuKategorie?Warum ist für einen Warenkorb eine
Listsinnvoller als einSet?
Lösung anzeigen
Lösung 1 – Fehler finden
Problem 1: Lambda-Block ohne ``return``
Der Lambda-Körper verwendet geschweifte Klammern, aber es fehlen die
return-Anweisungen. Die Zeilen p.preis() * 0.9; und p.preis();
sind nur Ausdrücke, deren Ergebnis verworfen wird.
Problem 2: ``EnumMap`` braucht die Enum-Klasse
new EnumMap<>() funktioniert nicht. Eine EnumMap braucht beim
Erzeugen die Klasse des Enums: new EnumMap<>(Kategorie.class).
Problem 3: ``summen.get(…)`` kann ``null`` liefern
Wenn für eine Kategorie noch kein Wert gespeichert wurde, liefert get
den Wert null. Dann entsteht sinngemäß null + 27.0, was zu einer
NullPointerException führt.
Problem 4: ``HashSet<Produkt>`` erkennt fachliche Duplikate nicht automatisch
Ohne überschriebene equals- und hashCode-Methoden sind zwei
new Buch("Java Basics", 30.0)-Objekte für Java verschiedene Objekte,
auch wenn Name und Preis identisch sind.
Lösung 2 – Lambda korrigieren
1RabattRegel regel = p -> {
2 if (p.preis() > 20) {
3 return p.preis() * 0.9;
4 }
5
6 return p.preis();
7};
Kürzer mit dem ternären Operator:
1RabattRegel regel = p -> p.preis() > 20 ? p.preis() * 0.9 : p.preis();
Lösung 3 – ``EnumMap`` korrekt erstellen
1Map<Kategorie, Double> summen = new EnumMap<>(Kategorie.class);
Die Angabe Kategorie.class sagt der EnumMap, mit welchem Enum sie
arbeitet.
Lösung 4 – Summen pro Kategorie berechnen
1Map<Kategorie, Double> summen = new EnumMap<>(Kategorie.class);
2
3for (Produkt p : warenkorb) {
4 Kategorie kategorie = p.getKategorie();
5 double bisher = summen.getOrDefault(kategorie, 0.0);
6 summen.put(kategorie, bisher + regel.berechne(p));
7}
8
9System.out.println(summen);
{BUCH=54.0, LEBENSMITTEL=1.0}
Zwei Bücher à 30 Euro mit je 10 % Rabatt ergeben 2 × 27 = 54 Euro.
Lösung 5 – Eindeutige Produkte
Ohne passende equals- und hashCode-Methoden vergleicht Java Objekte
nach Objektidentität. Zwei getrennt erzeugte Buch-Objekte sind daher
verschiedene Objekte.
Wenn nur eindeutige Produktnamen gesucht sind, kann man ein Set<String>
verwenden:
1Set<String> produktNamen = new LinkedHashSet<>();
2
3for (Produkt p : warenkorb) {
4 produktNamen.add(p.getName());
5}
6
7System.out.println(produktNamen);
[Java Basics, Apfel]
Lösung 6 – Verständnisfragen
Produktist abstrakt, weil ein allgemeines Produkt nicht direkt instanziiert werden soll. Die konkrete Preisberechnung übernehmen die Unterklassen.RabattRegelist ein Interface, damit die Regel flexibel als Lambda übergeben werden kann.EnumMappasst gut, weilKategorieeinenumist und die Menge der möglichen Schlüssel fest definiert ist.Eine
Listist für den Warenkorb sinnvoll, weil gleiche Produkte mehrfach vorkommen dürfen.Ein Lambda kann nicht ohne Zieltyp verwendet werden, weil Java wissen muss, welches funktionale Interface und welche Methode implementiert wird.
Aufgabe 3: Stream-Verarbeitung von Städten¶
Gegeben ist folgende Liste:
1List<String> staedte = List.of(
2 "Berlin", "München", "Hamburg", "Köln", "Frankfurt",
3 "Stuttgart", "Düsseldorf", "Leipzig", "Bremen", "Hannover"
4);
Bearbeite folgende Teilaufgaben mit Java Streams.
Erstelle eine Liste aller Städte, deren Name mehr als 7 Zeichen hat. Sortiere das Ergebnis alphabetisch.
Erstelle eine Liste aller Städtenamen in Großbuchstaben, aber nur für Städte, die mit
"H"beginnen.Zähle, wie viele Städte einen Namen mit genau 6 Zeichen haben.
Erstelle eine Liste der Längen aller Städtenamen, sortiert aufsteigend. (Nicht die Namen, sondern die Längen als
Integer-Liste.)Erkläre, warum Variante A schneller ist als Variante B, obwohl beide dasselbe Ergebnis liefern:
1// Variante A – erst filtern, dann sortieren 2staedte.stream() 3 .filter(s -> s.length() > 7) 4 .sorted() 5 .collect(Collectors.toList()); 6 7// Variante B – erst sortieren, dann filtern 8staedte.stream() 9 .sorted() 10 .filter(s -> s.length() > 7) 11 .collect(Collectors.toList());
Lösung anzeigen
Lösung 1 – Mehr als 7 Zeichen, alphabetisch sortiert
1List<String> ergebnis = staedte.stream()
2 .filter(s -> s.length() > 7)
3 .sorted()
4 .collect(Collectors.toList());
5
6System.out.println(ergebnis);
[Düsseldorf, Frankfurt, Hannover, Stuttgart]
Lösung 2 – Großbuchstaben, nur Städte mit „H“
1List<String> ergebnis = staedte.stream()
2 .filter(s -> s.startsWith("H"))
3 .map(String::toUpperCase)
4 .collect(Collectors.toList());
5
6System.out.println(ergebnis);
[HAMBURG, HANNOVER]
Lösung 3 – Anzahl der Städte mit genau 6 Zeichen
1long anzahl = staedte.stream()
2 .filter(s -> s.length() == 6)
3 .count();
4
5System.out.println(anzahl);
2
Städte mit genau 6 Zeichen: "Berlin" und "Bremen".
Lösung 4 – Längen aller Namen, aufsteigend
1List<Integer> laengen = staedte.stream()
2 .map(String::length)
3 .sorted()
4 .collect(Collectors.toList());
5
6System.out.println(laengen);
[4, 6, 6, 7, 7, 7, 8, 9, 9, 10]
Lösung 5 – Filter vor Sort
Beide Varianten liefern dasselbe Ergebnis. Der Unterschied liegt in der Effizienz:
Variante A filtert zuerst. Aus 10 Städten bleiben 4 übrig. Diese 4 werden sortiert.
Variante B sortiert zuerst alle 10 Städte. Danach werden 6 davon verworfen.
Sortieren ist eine vergleichsweise aufwändige Operation. Je weniger Elemente sortiert werden müssen, desto schneller geht es. Deshalb ist Variante A effizienter.
Faustregel: Filter so früh wie möglich in die Stream-Kette stellen.
Aufgabe 4: Produkt-Stream mit Fehlersuche¶
Verwendet wird dieselbe Klassenhierarchie wie in Aufgabe 2. Gegeben sind diese Produkte:
1List<Produkt> produkte = new ArrayList<>(List.of(
2 new Buch("Java Basics", 29.99),
3 new Lebensmittel("Apfel", 1.49),
4 new Buch("Clean Code", 34.99),
5 new Lebensmittel("Brot", 2.89),
6 new Buch("Design Patterns", 44.99),
7 new Lebensmittel("Milch", 1.19),
8 new Buch("Refactoring", 39.99)
9));
Aufgaben:
Erstelle mit einem Stream eine Liste aller Produktnamen, deren Preis unter 5,00 Euro liegt. Sortiere die Namen alphabetisch.
Erstelle mit einem Stream eine Liste aller Buchpreise (nur Bücher), sortiert aufsteigend.
Folgender Code soll alle Produkte mit einem Preis ≥ 30 Euro, aufsteigend nach Preis sortiert, in eine Namensliste sammeln. Er kompiliert nicht und ist außerdem langsamer als nötig. Finde beide Probleme und erkläre sie:
1List<String> teuer = produkte.stream() 2 .sorted((a, b) -> a.preis() - b.preis()) 3 .filter(p -> p.preis() >= 30.0) 4 .map(p -> p.getName()) 5 .collect(Collectors.toList());
Erstelle eine
Map, die jedem Kategorienamen (alsString) die Anzahl der Produkte in dieser Kategorie zuordnet. VerwendeCollectors.groupingByundCollectors.counting.Erwartete Ausgabe:
{BUCH=4, LEBENSMITTEL=3}
Lösung anzeigen
Lösung 1 – Günstige Produkte, alphabetisch
1List<String> ergebnis = produkte.stream()
2 .filter(p -> p.preis() < 5.0)
3 .map(Produkt::getName)
4 .sorted()
5 .collect(Collectors.toList());
6
7System.out.println(ergebnis);
[Apfel, Brot, Milch]
Lösung 2 – Buchpreise aufsteigend
1List<Double> buchpreise = produkte.stream()
2 .filter(p -> p.getKategorie() == Kategorie.BUCH)
3 .map(Produkt::preis)
4 .sorted()
5 .collect(Collectors.toList());
6
7System.out.println(buchpreise);
[29.99, 34.99, 39.99, 44.99]
Lösung 3 – Fehler und Performance-Problem
Problem 1 – Kompilierfehler:
Der Ausdruck a.preis() - b.preis() liefert einen double-Wert.
Ein Comparator<Produkt> erwartet aber int als Rückgabe der
compare-Methode. Java kann double nicht automatisch in int
umwandeln — der Code kompiliert daher nicht.
Korrekt:
.sorted((a, b) -> Double.compare(a.preis(), b.preis()))
Problem 2 – Reihenfolge ineffizient:
.sorted(...) steht vor .filter(...). Das bedeutet: alle 7 Produkte
werden zuerst sortiert. Danach werden 4 davon herausgefiltert.
Effizienter: erst filtern (übrig bleiben 3 teure Produkte), dann nur diese sortieren.
Korrigierte Version:
1List<String> teuer = produkte.stream()
2 .filter(p -> p.preis() >= 30.0)
3 .sorted((a, b) -> Double.compare(a.preis(), b.preis()))
4 .map(Produkt::getName)
5 .collect(Collectors.toList());
6
7System.out.println(teuer);
[Clean Code, Refactoring, Design Patterns]
Lösung 4 – groupingBy mit counting
1Map<String, Long> proKategorie = produkte.stream()
2 .collect(Collectors.groupingBy(
3 p -> p.getKategorie().name(),
4 Collectors.counting()
5 ));
6
7System.out.println(proKategorie);
{BUCH=4, LEBENSMITTEL=3}
Collectors.groupingBy gruppiert die Elemente nach dem angegebenen
Kriterium. Collectors.counting zählt die Elemente in jeder Gruppe.