diff --git a/docs/documentation/comparators.md b/docs/documentation/comparators.md index 0a1faa4b79..012170ddec 100644 --- a/docs/documentation/comparators.md +++ b/docs/documentation/comparators.md @@ -62,3 +62,76 @@ public class MainClass { } ``` + +## Komparatoren als Lambda-Ausdrücke + +Da `Comparator` ein funktionales Interface ist, lässt sich ein Komparator +statt als eigene Klasse auch als Lambda-Ausdruck schreiben. Das spart +Boilerplate und macht den Sortierauftrag direkt sichtbar. + +```java title="MainClass.java (Auszug)" showLineNumbers +// Aufsteigend nach Prozessorleistung – als Lambda statt eigener Klasse +Collections.sort(notebooks, (o1, o2) -> Double.compare( + o1.cpu().powerInGhz(), o2.cpu().powerInGhz())); +``` + +## Komparatoren mit Comparator.comparing() + +Die statische Fabrikmethode +`Comparator comparing(keyExtractor: Function)` erstellt einen +Komparator anhand eines Schlüsselextraktors. Das Ergebnis ist kompakter als ein +manuell implementiertes Lambda und lässt sich direkt mit `List.sort()` +verwenden. + +```java title="MainClass.java (Auszug)" showLineNumbers +// Aufsteigend nach Beschreibung +notebooks.sort(Comparator.comparing(Notebook::description)); + +// Aufsteigend nach Arbeitsspeicher +notebooks.sort(Comparator.comparingInt(Notebook::memoryInGb)); +``` + +## Umkehren der Sortierung mit reversed() + +Die Instanzmethode `Comparator reversed()` kehrt die Sortierreihenfolge eines +bestehenden Komparators um, ohne dass ein neues Lambda geschrieben werden muss. + +```java title="MainClass.java (Auszug)" showLineNumbers +// Absteigend nach Arbeitsspeicher +notebooks.sort(Comparator.comparingInt(Notebook::memoryInGb).reversed()); +``` + +## Mehrstufige Sortierung mit thenComparing() + +Mit `Comparator thenComparing(other: Comparator)` lassen sich mehrere +Sortierkriterien verknüpfen. Das zweite Kriterium greift nur dann, wenn das +erste einen Gleichstand liefert. + +```java title="MainClass.java" showLineNumbers +public class MainClass { + + public static void main(String[] args) { + List notebooks = new ArrayList<>(); + notebooks.add(new Notebook("Mein Office Laptop", new Cpu(3.5, 8), 16, 14)); + notebooks.add(new Notebook("Mein Gaming Laptop", new Cpu(4.7, 8), 32, 16)); + notebooks.add(new Notebook("Mein zweites Office Laptop", new Cpu(3.5, 4), 16, 13)); + + // Erst aufsteigend nach Arbeitsspeicher, bei Gleichstand nach Prozessorleistung absteigend + Comparator comparator = Comparator + .comparingInt(Notebook::memoryInGb) + .thenComparingDouble((Notebook n) -> n.cpu().powerInGhz()) + .reversed(); + + notebooks.sort(comparator); + } + +} +``` + +:::tip + +Bevorzuge `Comparator.comparing()` und `thenComparing()` gegenüber manuellen +Lambda-Ausdrücken — der Code bleibt lesbar und die Sortierabsicht ist auf einen +Blick erkennbar. + +::: diff --git a/docs/documentation/enumerations.md b/docs/documentation/enumerations.md index 49bf9d83ab..6cc44bcece 100644 --- a/docs/documentation/enumerations.md +++ b/docs/documentation/enumerations.md @@ -49,6 +49,8 @@ public enum Weekday { Aufzählungen stellen eine Reihe hilfreicher Methoden bereit: +- `String name()` — gibt den Namen der Konstante exakt so zurück, wie er im + Quellcode deklariert wurde (z.B. `"MONDAY"`) - `T[] values()` — statisch; gibt alle Aufzählungskonstanten als Feld zurück - `T valueOf(name: String)` — statisch; gibt die Aufzählungskonstante zur angegebenen Zeichenkette zurück @@ -59,10 +61,94 @@ public class MainClass { public static void main(String[] args) { Weekday monday = Weekday.valueOf("MONDAY"); + System.out.println(monday.name()); // MONDAY + System.out.println(monday.ordinal()); // 0 + for (Weekday w : Weekday.values()) { - System.out.println(w.ordinal()); + System.out.println(w.ordinal() + ": " + w.description()); } } } ``` + +:::info + +`name()` und `toString()` liefern bei einfachen Aufzählungen dasselbe Ergebnis. +`name()` ist jedoch fest spezifiziert und kann nicht überschrieben werden; +`toString()` kann dagegen in der Aufzählung überschrieben werden, um eine +angepasste Darstellung zu liefern. + +::: + +## Abstrakte Methoden in Aufzählungen + +Aufzählungen können abstrakte Methoden deklarieren, wenn jede Konstante ein +individuelles Verhalten bereitstellen soll. Jede Aufzählungskonstante muss die +abstrakte Methode dann mit einer eigenen Implementierung überschreiben. + +```java title="Operation.java" showLineNumbers +public enum Operation { + + ADD { + @Override + public double apply(double a, double b) { + return a + b; + } + }, + SUBTRACT { + @Override + public double apply(double a, double b) { + return a - b; + } + }, + MULTIPLY { + @Override + public double apply(double a, double b) { + return a * b; + } + }, + DIVIDE { + @Override + public double apply(double a, double b) { + if (b == 0) throw new ArithmeticException("Division durch null"); + return a / b; + } + }; + + public abstract double apply(double a, double b); + +} +``` + +In der Startklasse können alle Operationen einheitlich über den gemeinsamen +Methodenaufruf verwendet werden, ohne dass eine `switch`-Fallunterscheidung +nötig ist. + +```java title="MainClass.java" showLineNumbers +public class MainClass { + + public static void main(String[] args) { + double a = 10; + double b = 3; + + for (Operation op : Operation.values()) { + System.out.printf("%s: %.2f%n", op.name(), op.apply(a, b)); + } + // ADD: 13,00 + // SUBTRACT: 7,00 + // MULTIPLY: 30,00 + // DIVIDE: 3,33 + } + +} +``` + +:::tip + +Abstrakte Methoden in Aufzählungen eignen sich besonders dann, wenn sich +Verhalten je nach Konstante unterscheidet und ein `switch`-Ausdruck über die +Aufzählung vermieden werden soll. Das Muster ist eine kompakte Variante des +_Strategy_-Entwurfsmusters. + +::: diff --git a/docs/documentation/generics.md b/docs/documentation/generics.md index 5a6fbab8e4..d4f523e8f4 100644 --- a/docs/documentation/generics.md +++ b/docs/documentation/generics.md @@ -196,19 +196,19 @@ public class MainClass { covariantBox = new Box(); // Kompilierungsfehler covariantBox = new Box(); covariantBox = new Box(); - covariantBox = new Box(); // Kompilierungsfehler + covariantBox = new Box(); // Kompilierungsfehler Box contravariantBox; contravariantBox = new Box(); contravariantBox = new Box(); contravariantBox = new Box(); // Kompilierungsfehler - covariantBox = new Box(); // Kompilierungsfehler + contravariantBox = new Box(); // Kompilierungsfehler Box invariantBox; invariantBox = new Box(); // Kompilierungsfehler invariantBox = new Box(); invariantBox = new Box(); // Kompilierungsfehler - covariantBox = new Box(); // Kompilierungsfehler + invariantBox = new Box(); // Kompilierungsfehler } } diff --git a/docs/documentation/java-stream-api.md b/docs/documentation/java-stream-api.md index eb6dd3ce49..c038993aa3 100644 --- a/docs/documentation/java-stream-api.md +++ b/docs/documentation/java-stream-api.md @@ -68,13 +68,6 @@ public class MainClass { } ``` -:::note - -Die Zahlenfolge 4-8-15-16-23-42 spielt eine große Rolle in der Fernsehserie -_Lost_. - -::: - Im Gegensatz zu `Stream` bieten die spezialisierten Klassen `IntStream`, `DoubleStream` und `LongStream` zusätzliche Methoden zur Verarbeitung primitiver Werte, wie etwa `sum()` oder `average()`. diff --git a/docs/documentation/optionals.md b/docs/documentation/optionals.md index 0fc42c520f..1a31947314 100644 --- a/docs/documentation/optionals.md +++ b/docs/documentation/optionals.md @@ -67,3 +67,89 @@ public class MainClass { } ``` + +## Transformieren mit map() und flatMap() + +Neben dem direkten Zugriff erlaubt `Optional` das Transformieren des enthaltenen +Werts, ohne den `null`-Fall explizit behandeln zu müssen. Die Methode +`Optional map(mapper: Function)` wendet eine Funktion auf den Wert an +und verpackt das Ergebnis wieder in ein `Optional`. Ist der Ausgangswert nicht +vorhanden, liefert `map()` ein leeres `Optional` zurück. + +```java title="MainClass.java" showLineNumbers +public class MainClass { + + private static List names; + + public static void main(String[] args) { + names = List.of("Hans", "Anna", "Klaus"); + + // Liefert die Länge des Namens oder nichts, falls kein Name gefunden + Optional nameLength = getNameByInitial('A') + .map(String::length); + nameLength.ifPresent(System.out::println); // 4 + + // Liefert den Namen in Großbuchstaben oder einen Standardwert + String upper = getNameByInitial('Z') + .map(String::toUpperCase) + .orElse("unbekannt"); + System.out.println(upper); // unbekannt + } + + public static Optional getNameByInitial(char initial) { + return names.stream() + .filter(n -> n.charAt(0) == initial) + .findFirst(); + } + +} +``` + +Die Methode `Optional flatMap(mapper: Function>)` wird +eingesetzt, wenn die Transformationsfunktion selbst ein `Optional` zurückgibt. +Sie verhindert dabei das Entstehen verschachtelter +`Optional>`-Werte. + +```java title="MainClass.java (Auszug)" showLineNumbers +// map() würde Optional> liefern – flatMap() vermeidet das +Optional result = Optional.of(" hallo ") + .flatMap(s -> s.isBlank() ? Optional.empty() : Optional.of(s.trim())); +System.out.println(result); // Optional[hallo] +``` + +## Filtern mit filter() + +Mit `Optional filter(predicate: Predicate)` lässt sich der enthaltene Wert +anhand einer Bedingung prüfen. Erfüllt der Wert das Prädikat, bleibt das +`Optional` unverändert; anderenfalls wird ein leeres `Optional` zurückgegeben. + +```java title="MainClass.java (Auszug)" showLineNumbers +Optional longName = getNameByInitial('H') + .filter(n -> n.length() > 3); +longName.ifPresent(System.out::println); // Hans (Länge 4 > 3) + +Optional shortName = getNameByInitial('H') + .filter(n -> n.length() > 10); +System.out.println(shortName.isPresent()); // false +``` + +## Fehlerbehandlung mit orElseThrow() + +Soll das Fehlen eines Werts als Fehlerfall gewertet werden, bietet +`T orElseThrow(exceptionSupplier: Supplier)` eine präzise Alternative zu +`orElse()`. Ist kein Wert vorhanden, wird die angegebene Ausnahme ausgelöst. + +```java title="MainClass.java (Auszug)" showLineNumbers +// Wirft eine IllegalArgumentException, wenn kein Name mit 'X' existiert +String name = getNameByInitial('X') + .orElseThrow(() -> new IllegalArgumentException("Kein Name gefunden")); +``` + +:::warning + +`Optional.get()` sollte nur aufgerufen werden, wenn vorher `isPresent()` geprüft +wurde. Ohne diese Prüfung kann `get()` eine `NoSuchElementException` auslösen — +genau wie der ursprüngliche `null`-Zugriff. Bevorzuge stattdessen `orElse()`, +`orElseThrow()` oder `ifPresent()`. + +::: diff --git a/docs/documentation/polymorphism.mdx b/docs/documentation/polymorphism.mdx index 500fd2a7f6..7f1b896a1a 100644 --- a/docs/documentation/polymorphism.mdx +++ b/docs/documentation/polymorphism.mdx @@ -42,7 +42,7 @@ public class Computer { public class Notebook extends Computer { ... public Notebook(String description, Cpu cpu, int memoryInGb, double screenSizeInInches) { - super(description, cpu, mainMemoryInGb); + super(description, cpu, memoryInGb); this.screenSizeInInches = screenSizeInInches; } diff --git a/docs/documentation/tests.md b/docs/documentation/tests.md index 59f95eceaa..b709139457 100644 --- a/docs/documentation/tests.md +++ b/docs/documentation/tests.md @@ -36,3 +36,7 @@ stattfindet, wird die testgetriebene Entwicklung zu den Designstrategien gezählt. ::: + +Die praktische Umsetzung von Komponententests mit JUnit 5 — Annotationen, +Assertions und Beispiele — ist auf der Seite +[Komponententests (Unit Tests)](unit-tests) beschrieben.