Deine Auswertung
Deine Landkarte
Vier Versionssprünge und wie weit jeder davon in deinem Code angekommen ist. Klapp ein Feature auf, dann siehst du es vorher und nachher.
- Streams (Java 8)
Streams jenseits von
filterundmap:groupingBy,flatMap, eigene CollectorsGruppieren: Bestellungen je Kundin
Bestellungen nach Kundin gruppieren: Vorher baut eine Schleife die Map von Hand auf und legt für jede neue Kundin eine Liste an. Nachher erledigt
Collectors.groupingBy()das in einem Ausdruck.Vorher
Map<String, List<Order>> ordersByCustomer = new HashMap<>(); for (Order order : orders) { String customer = order.getCustomer(); List<Order> list = ordersByCustomer.get(customer); if (list == null) { list = new ArrayList<>(); ordersByCustomer.put(customer, list); } list.add(order); }Nachher
Map<String, List<Order>> ordersByCustomer = orders.stream() .collect(Collectors.groupingBy(Order::getCustomer));
- Java 8 → 11
varfür lokale VariablenGenerische Typen, dreifach wiederholt
Vorher steht der verschachtelte generische Typ in jeder Deklaration – bei der Map, in der Schleife und bei jeder Hilfsvariablen. Nachher steht er nur noch einmal, beim
new; überall sonst leitetvarihn ab.Vorher
Map<String, List<Map.Entry<LocalDate, BigDecimal>>> priceHistory = new HashMap<>(); loadPrices(priceHistory); for (Map.Entry<String, List<Map.Entry<LocalDate, BigDecimal>>> entry : priceHistory.entrySet()) { List<Map.Entry<LocalDate, BigDecimal>> history = entry.getValue(); Map.Entry<LocalDate, BigDecimal> latest = history.get(history.size() - 1); System.out.println(entry.getKey() + ": " + latest.getValue()); }Nachher
var priceHistory = new HashMap<String, List<Map.Entry<LocalDate, BigDecimal>>>(); loadPrices(priceHistory); for (var entry : priceHistory.entrySet()) { var history = entry.getValue(); var latest = history.get(history.size() - 1); System.out.println(entry.getKey() + ": " + latest.getValue()); }List.of(),Map.of(),Set.of()Eine Map als Konstante
Eine unveränderliche Map als Konstante: Vorher braucht sie einen
static-Block, eine Hilfs-Map undCollections.unmodifiableMap(). Nachher genügtMap.of().Vorher
private static final Map<String, Integer> SHIPPING_DAYS; static { Map<String, Integer> days = new HashMap<>(); days.put("DE", 1); days.put("AT", 2); days.put("CH", 3); SHIPPING_DAYS = Collections.unmodifiableMap(days); }Nachher
private static final Map<String, Integer> SHIPPING_DAYS = Map.of( "DE", 1, "AT", 2, "CH", 3);Die neuen String- und Files-Methoden:
isBlank(),strip(),lines(),Files.readString()Eine Datei lesen
Eine Textdatei in einen String lesen: Vorher Zeile für Zeile über einen
BufferedReader, nachher mit einem einzigen Aufruf vonFiles.readString().Vorher
String readTemplate(Path path) throws IOException { StringBuilder content = new StringBuilder(); try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { String line; while ((line = reader.readLine()) != null) { content.append(line).append('\n'); } } return content.toString(); }Nachher
String readTemplate(Path path) throws IOException { return Files.readString(path); }
- Java 11 → 17
Records
Der Klassiker: Wertklasse mit allem Drum und Dran
Eine Wertklasse für Geldbeträge: Vorher schreibst du Konstruktor, Getter,
equals(),hashCode()undtoString()von Hand. Nachher erzeugt der Record all das aus einer Zeile – die Zugriffsmethoden heißen dannamount()undcurrency().Vorher
public final class Money { private final BigDecimal amount; private final String currency; public Money(BigDecimal amount, String currency) { this.amount = amount; this.currency = currency; } public BigDecimal getAmount() { return amount; } public String getCurrency() { return currency; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Money)) return false; Money other = (Money) o; return amount.equals(other.amount) && currency.equals(other.currency); } @Override public int hashCode() { return Objects.hash(amount, currency); } @Override public String toString() { return amount + " " + currency; } }Nachher
public record Money(BigDecimal amount, String currency) {}Switch Expressions (
switchmit->und Rückgabewert)Ergebnisvariable und
breakEin
switch, der einen Wert berechnet: Vorher braucht er eine Ergebnisvariable und nach jedem Fall einbreak, das man nicht vergessen darf. Nachher liefert derswitchden Wert direkt, und mit->fällt kein Fall mehr in den nächsten durch.Vorher
int shippingDays(Country country) { int days; switch (country) { case GERMANY: days = 1; break; case AUSTRIA: case SWITZERLAND: days = 2; break; default: days = 5; } return days; }Nachher
int shippingDays(Country country) { return switch (country) { case GERMANY -> 1; case AUSTRIA, SWITZERLAND -> 2; default -> 5; }; }Text Blocks (
""")JSON im Test, mit Platzhaltern
Erwartetes JSON in einem Test: Vorher mit maskierten Anführungszeichen,
\nund+zusammengesetzt, nachher als Text Block, in dem das JSON aussieht wie JSON. Die Werte setztformatted()ein.Vorher
String json = "{\n" + " \"customer\": \"" + customer + "\",\n" + " \"lines\": [\n" + " {\"product\": \"" + product + "\", \"quantity\": " + quantity + "}\n" + " ]\n" + "}";Nachher
String json = """ { "customer": "%s", "lines": [ {"product": "%s", "quantity": %d} ] } """.formatted(customer, product, quantity);
- Java 17 → 21
Virtual Threads
Reaktiv gegen blockierend
Eine Rechnung aus vier Aufrufen an andere Dienste: Vorher ist das die Kette, die man schreibt, um Threads nicht zu blockieren. Nachher ist derselbe Ablauf blockierend geschrieben – auf einem virtuellen Thread kostet das Blockieren nichts mehr.
Vorher
CompletableFuture<Invoice> createInvoice(long orderId) { return orderClient.findOrder(orderId) .thenCompose(order -> customerClient.findCustomer(order.customerId()) .thenCombine(taxClient.rateFor(order.country()), (customer, taxRate) -> new Invoice(order, customer, taxRate))) .thenCompose(invoice -> pdfService.render(invoice) .thenApply(invoice::withPdf)) .exceptionally(e -> { log.error("Rechnung fehlgeschlagen", e); throw new InvoiceException(e); }); }Nachher
Invoice createInvoice(long orderId) { try { Order order = orderClient.findOrder(orderId); Customer customer = customerClient.findCustomer(order.customerId()); BigDecimal taxRate = taxClient.rateFor(order.country()); Invoice invoice = new Invoice(order, customer, taxRate); return invoice.withPdf(pdfService.render(invoice)); } catch (Exception e) { log.error("Rechnung fehlgeschlagen", e); throw new InvoiceException(e); } } // Aufruf: Thread.startVirtualThread(() -> createInvoice(orderId));Pattern Matching for
switch– mit Record Patterns und Sealed Typesinstanceof-Kaskade mit Cast,elsewirftJe nach Art der Sendung ein anderer Text: Vorher prüft eine
if-Kaskade den Typ, castet und wirft imelse, falls ein unbekannter Typ ankommt. Nachher zerlegt einswitchdie Records direkt, und weilShipmentsealed ist, prüft der Compiler, dass kein Fall fehlt – eindefaultbraucht es nicht.Vorher
String describe(Shipment shipment) { if (shipment instanceof Parcel) { Parcel parcel = (Parcel) shipment; return "Paket, " + parcel.getWeightKg() + " kg"; } else if (shipment instanceof Letter) { Letter letter = (Letter) shipment; return "Brief an " + letter.getRecipient(); } else if (shipment instanceof Pickup) { Pickup pickup = (Pickup) shipment; return "Abholung in " + pickup.getStore().getCity(); } else { throw new IllegalArgumentException("Unbekannte Sendung: " + shipment); } }Nachher
sealed interface Shipment permits Parcel, Letter, Pickup {} record Parcel(double weightKg) implements Shipment {} record Letter(String recipient) implements Shipment {} record Pickup(Store store) implements Shipment {} record Store(String city) {} String describe(Shipment shipment) { return switch (shipment) { case Parcel(double weightKg) -> "Paket, " + weightKg + " kg"; case Letter(String recipient) -> "Brief an " + recipient; case Pickup(Store(String city)) -> "Abholung in " + city; }; }Sequenced Collections (
getFirst(),getLast(),reversed())Erstes und letztes Element
Das erste und das letzte Element einer Liste, das erste eines
LinkedHashSet, der erste Eintrag einerLinkedHashMap: Vorher über Index oder Iterator, jedes Mal anders. Nachher mitgetFirst(),getLast()undfirstEntry().Vorher
Order firstOrder = orders.get(0); Order latestOrder = orders.get(orders.size() - 1); String firstTag = tags.iterator().next(); // LinkedHashSet Map.Entry<String, Order> firstEntry = ordersById.entrySet().iterator().next(); // LinkedHashMapNachher
Order firstOrder = orders.getFirst(); Order latestOrder = orders.getLast(); String firstTag = tags.getFirst(); Map.Entry<String, Order> firstEntry = ordersById.firstEntry();
- Java 21 → 25
Stream Gatherers (
windowSliding,fold,mapConcurrent)windowSliding: gleitender DurchschnittEin gleitender Durchschnitt über je drei Tage: Vorher mit zwei verschachtelten Index-Schleifen. Nachher bildet
Gatherers.windowSliding(3)die Fenster, und der Stream rechnet für jedes den Durchschnitt aus.Vorher
List<Double> movingAverage(List<Integer> dailySales) { List<Double> averages = new ArrayList<>(); for (int i = 0; i + 3 <= dailySales.size(); i++) { int sum = 0; for (int j = i; j < i + 3; j++) { sum += dailySales.get(j); } averages.add(sum / 3.0); } return averages; }Nachher
List<Double> movingAverage(List<Integer> dailySales) { return dailySales.stream() .gather(Gatherers.windowSliding(3)) .map(SalesReport::average) .toList(); } private static double average(List<Integer> window) { return window.stream().mapToInt(Integer::intValue).average().orElseThrow(); }Unnamed Variables:
_incatch, Lambdas und PatternsRecord Patterns und
catch: nur, was zähltVorher benennt das Pattern jede Komponente, auch die, die niemand braucht, und die Exception bekommt einen Namen, obwohl niemand sie ansieht. Nachher steht dort
_– und der Code sagt selbst, was ungenutzt bleibt.Vorher
double area(Shape shape) { return switch (shape) { case Circle(Point center, double radius) -> Math.PI * radius * radius; case Rectangle(Point topLeft, double width, double height) -> width * height; }; } double areaOrZero(String serialized) { try { return area(Shape.parse(serialized)); } catch (IllegalArgumentException e) { return 0; } }Nachher
double area(Shape shape) { return switch (shape) { case Circle(_, double radius) -> Math.PI * radius * radius; case Rectangle(_, double width, double height) -> width * height; }; } double areaOrZero(String serialized) { try { return area(Shape.parse(serialized)); } catch (IllegalArgumentException _) { return 0; } }Scoped Values statt
ThreadLocalSetzen, benutzen, aufräumen
Den angemeldeten User tief in der Aufrufkette lesen, ohne ihn durch jede Methode zu reichen: Vorher über ein
ThreadLocal, das du imfinallywieder aufräumen musst. Nachher gilt der Wert genau für die Dauer vonrun()und ist danach von selbst weg.Vorher
private static final ThreadLocal<User> CURRENT_USER = new ThreadLocal<>(); void handle(Request request) { CURRENT_USER.set(authenticate(request)); try { orderService.process(request.orderId()); } finally { CURRENT_USER.remove(); // wer das vergisst, leakt den User in den nächsten Request } } // irgendwo tief im OrderService: User user = CURRENT_USER.get();Nachher
private static final ScopedValue<User> CURRENT_USER = ScopedValue.newInstance(); void handle(Request request) { ScopedValue.where(CURRENT_USER, authenticate(request)) .run(() -> orderService.process(request.orderId())); } // irgendwo tief im OrderService: User user = CURRENT_USER.get();
Bis Java 25 hast du keine Lücke mehr. Was du noch nicht regelmäßig einsetzt, findest du in der Landkarte – mit Code.
Weiterlesen, kostenlos
- Java Streams (mit Beispielen)
- Java 10 Features (mit Beispielen)
- Java 9 Features (mit Beispielen)
- Java 11 Features (mit Beispielen)
- Java Records: Der vollständige Guide (mit Beispielen)
- Java Switch Expressions (JEP 361): Pfeil, yield, kein break
- Java Text Blocks: mehrzeilige Strings und Zeilenumbrüche ohne \n
- Virtual Threads in Java: Funktionsweise, Beispiele und Grenzen
- Sealed Classes und Interfaces in Java
- Java 21 Features (mit Beispielen)
- Java Stream Gatherers – Schreibe deine eigenen Stream-Operationen!
- Unbenannte Variablen und Patterns in Java (JEP 456)
- Scoped Values in Java (JEP 506) erklärt – statt ThreadLocal
- Java-Versionen-Cheat-Sheet – alle Features von Java 10 bis 27 auf 26 Seiten
Deine Auswertung per Mail
Das hat nicht geklappt. Trag deine Adresse bitte unten ein.