How it works
The explanation, with examples you can run.
Records
record Money(long cents, String currency) { Money { // compact constructor: validates, then assigns if (cents < 0) throw new IllegalArgumentException("negative"); currency = currency.toUpperCase(); } Money plus(Money other) { return new Money(cents + other.cents, currency); }}From the header, the compiler generates a private final field for each component, a canonical constructor, accessors named after the components (cents(), not getCents()), and equals, hashCode and toString based on every component. A record is implicitly final and can't extend another class, but it can implement interfaces and have static fields and extra methods. It is only shallowly immutable: a record holding a List holds a reference to a list that can still change, so copy it with List.copyOf in the compact constructor if that matters.
Sealed types
sealed interface Shape permits Circle, Rect, Square {}record Circle(double r) implements Shape {}record Rect(double w, double h) implements Shape {}record Square(double side) implements Shape {}permits lists every allowed subtype. Each must be final (records already are), sealed itself, or non-sealed to reopen its branch. The permitted types must be in the same module, or the same package if you don't use modules. The JVM enforces the list too: the class file records it, so another library can't add a fourth shape.
Patterns in switch
static double area(Shape s) { return switch (s) { // no default: the compiler knows every case case Circle c -> Math.PI * c.r() * c.r(); case Rect(double w, double h) -> w * h; case Square(double side) when side == 0 -> 0; case Square(double side) -> side * side; };}- A type pattern (
Circle c) tests the type and binds a variable. - A record pattern (
Rect(double w, double h)) also calls the accessors and binds the parts. Record patterns nest:case Line(Point(var x1, var y1), Point p2). - A guard (
when side == 0) adds a condition. Cases are tried top to bottom, and the compiler rejects a case that an earlier one makes unreachable. case nullis allowed. Without it, switching onnullthrowsNullPointerExceptionas before.
Because Shape is sealed, the compiler knows these cases are exhaustive, so no default is needed. That is the real benefit: add Triangle to permits and every switch that doesn't handle it stops compiling. If code compiled against the old hierarchy meets a new subtype at run time anyway, the compiler's hidden default throws MatchException.
The same patterns work in instanceof: if (obj instanceof Rect(var w, var h) && w == h).
Preview features to know about
Java 21 also previewed unnamed patterns and variables (_ for a component you don't need), which became final in Java 22. Preview features need --enable-preview and can change, so keep them out of production code on 21.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.