Java hat sich in den letzten Jahren erheblich weiterentwickelt. Seit Java 16 (Records), Java 17 (Sealed Classes) und Java 21 (Pattern Matching für switch als Standard-Feature) ist modernes Java deutlich kompakter und ausdrucksstärker. Dieser Artikel zeigt die praktische Anwendung dieser Features — mit Vergleichen zu PHP 8.x für PHP-Entwickler.
Java Records (ab Java 16 stabil)
Vor Records musste man für eine einfache Datenklasse viel Boilerplate schreiben:
// Alt: Boilerplate-Hölle
public class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int x() { return x; }
public int y() { return y; }
@Override
public boolean equals(Object obj) { /* ... */ }
@Override
public int hashCode() { /* ... */ }
@Override
public String toString() { return "Point[x=" + x + ", y=" + y + "]"; }
}
Mit Records:
// Neu: alles in einer Zeile
public record Point(int x, int y) {}
Der Compiler generiert automatisch: Konstruktor, Getter (ohne get-Präfix), equals(), hashCode() und toString().
Records in Spring Boot
// DTO als Record — ideal für API-Requests/-Responses
public record ProductDto(
String name,
BigDecimal price,
String category
) {}
// Controller
@PostMapping("/products")
public ResponseEntity<Product> create(@RequestBody @Valid ProductDto dto) {
Product product = productService.create(dto.name(), dto.price(), dto.category());
return ResponseEntity.status(201).body(product);
}
Compact Constructors für Validierung
public record Email(String value) {
// Compact Constructor — Code wird vor der Feldzuweisung ausgeführt
public Email {
if (value == null || !value.contains("@")) {
throw new IllegalArgumentException("Ungültige E-Mail-Adresse: " + value);
}
value = value.toLowerCase().strip(); // Normalisierung
}
}
// Vergleich: PHP 8.x Readonly Properties
// class Email {
// public function __construct(
// public readonly string $value
// ) {
// if (!str_contains($value, '@')) {
// throw new \InvalidArgumentException('Ungültige E-Mail-Adresse');
// }
// }
// }
Sealed Classes und Interfaces (ab Java 17)
Sealed Classes erlauben es, die Hierarchie einer Klasse zu kontrollieren — nur explizit erlaubte Subklassen sind möglich.
// Sealed Interface — nur diese drei Implementierungen sind erlaubt
public sealed interface Shape
permits Circle, Rectangle, Triangle {}
public record Circle(double radius) implements Shape {}
public record Rectangle(double width, double height) implements Shape {}
public record Triangle(double base, double height) implements Shape {}
Das ist ähnlich zu PHP 8.1 Enums, aber flexibler, da jede Implementierung eigene Felder haben kann.
Vergleich mit PHP Enum
// PHP 8.1 Enum
enum Shape {
case Circle;
case Rectangle;
case Triangle;
}
// Java Sealed Interface — mehr Flexibilität
sealed interface Shape permits Circle, Rectangle, Triangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
Pattern Matching für switch (Java 21, Standard-Feature)
Pattern Matching für switch ist das Feature, das Sealed Classes erst wirklich nützlich macht. Statt langer instanceof-Kaskaden:
// Alt: instanceof-Kaskade
double area(Shape shape) {
if (shape instanceof Circle c) {
return Math.PI * c.radius() * c.radius();
} else if (shape instanceof Rectangle r) {
return r.width() * r.height();
} else if (shape instanceof Triangle t) {
return 0.5 * t.base() * t.height();
}
throw new IllegalStateException("Unbekannte Shape: " + shape);
}
// Neu: Pattern Matching switch (Java 21)
double area(Shape shape) {
return switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.width() * r.height();
case Triangle t -> 0.5 * t.base() * t.height();
// Kein default nötig — Compiler weiß, dass alle Fälle abgedeckt sind!
};
}
Der Compiler prüft Exhaustiveness: Da Shape sealed ist und alle Subklassen abgedeckt sind, muss kein default-Fall angegeben werden. Wenn eine neue Implementierung von Shape hinzukommt, gibt es einen Compile-Fehler — genau wie PHP Enums in match-Ausdrücken.
Guards im Pattern Matching
String classify(Object obj) {
return switch (obj) {
case Integer i when i < 0 -> "Negative Zahl";
case Integer i when i == 0 -> "Null";
case Integer i -> "Positive Zahl: " + i;
case String s when s.isEmpty() -> "Leerer String";
case String s -> "String: " + s;
case null -> "null";
default -> "Sonstiges: " + obj.getClass().getSimpleName();
};
}
Das when-Guard entspricht PHP's match mit true-Bedingungen.
Text Blocks (Java 15) — für mehrzeilige Strings
// Alt
String json = "{\n" +
" \"name\": \"Produkt\",\n" +
" \"price\": 19.99\n" +
"}";
// Neu: Text Block
String json = """
{
"name": "Produkt",
"price": 19.99
}
""";
Analog zu PHP Heredoc:
$json = <<<JSON
{
"name": "Produkt",
"price": 19.99
}
JSON;
Praktisches Beispiel: Event-System mit Sealed Interfaces
// Events als Sealed Interface + Records
public sealed interface OrderEvent
permits OrderPlaced, OrderShipped, OrderCancelled {}
public record OrderPlaced(String orderId, BigDecimal total) implements OrderEvent {}
public record OrderShipped(String orderId, String trackingNumber) implements OrderEvent {}
public record OrderCancelled(String orderId, String reason) implements OrderEvent {}
// Handler
public class OrderEventHandler {
public void handle(OrderEvent event) {
switch (event) {
case OrderPlaced placed ->
sendConfirmationEmail(placed.orderId(), placed.total());
case OrderShipped shipped ->
sendTrackingEmail(shipped.orderId(), shipped.trackingNumber());
case OrderCancelled cancelled ->
processRefund(cancelled.orderId(), cancelled.reason());
}
// Kein default — der Compiler stellt sicher, dass alle Events behandelt werden
}
}
Fazit
Records, Sealed Classes und Pattern Matching machen Java ausdrucksstärker und sicherer. Records eliminieren Boilerplate für Datenklassen. Sealed Classes machen Typen-Hierarchien kontrollierbar. Pattern Matching für switch ermöglicht erschöpfende, typsichere Fallunterscheidung ohne Laufzeit-Fehler.
Für PHP-Entwickler sind diese Features nicht fremd: Records ähneln PHP Readonly-Klassen, Sealed Classes haben Überschneidungen mit PHP Enums, und switch-Pattern-Matching ähnelt PHP's match-Ausdruck. Der Hauptunterschied: Java prüft Exhaustiveness zur Compile-Zeit, was eine Fehlerklasse komplett eliminiert.
Kommentare
Kommentare werden von Remark42 bereitgestellt. Beim Laden werden Daten an unseren Kommentar-Server übertragen.