Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Record patterns

A record pattern not only checks that an object is a record of the right type but also takes it apart into its components right away. Instead of calling point.x() and point.y() you get ready-made x and y variables. Record patterns arrived in Java 21 and work in instanceof and switch.

Taking a record apart

After the record name you list patterns for its components in parentheses — in the same order as in the record declaration:

record Point(int x, int y) {}
 
void main() {
    Object value = new Point(3, 4);
 
    if (value instanceof Point(int x, int y)) {
        IO.println("x = " + x + ", y = " + y);
    }
}

Output:

x = 3, y = 4

The variable names in the pattern can differ from the component names: only the order and the types matter.

In switch

Record patterns are especially expressive in a switch. Each branch gets the data of its shape right away:

sealed interface Shape permits Circle, Rectangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
 
String describe(Shape shape) {
    return switch (shape) {
        case Circle(double r) -> "a circle with radius " + r;
        case Rectangle(double w, double h) when w == h -> "a square with side " + w;
        case Rectangle(double w, double h) -> "a rectangle " + w + " by " + h;
    };
}
 
void main() {
    IO.println(describe(new Circle(2)));
    IO.println(describe(new Rectangle(3, 3)));
    IO.println(describe(new Rectangle(2, 5)));
}

Output:

a circle with radius 2.0
a square with side 3.0
a rectangle 2.0 by 5.0

The when guard works here too, and it can use the variables obtained from taking the record apart.

This combination — a sealed interface, records and a switch with patterns — is called data-oriented programming. The data is described by simple immutable records, and the operations on it are gathered in one switch. If you add a new kind of shape, the compiler itself points to every switch that needs a new branch.

var in a pattern

To avoid repeating the component types, you can write var instead — the compiler infers the type itself:

record Order(String customer, double amount) {}
 
void main() {
    Object value = new Order("Anna", 49.90);
 
    if (value instanceof Order(var customer, var amount)) {
        IO.println(customer + " paid €" + amount);
    }
}

Output:

Anna paid €49.9

Nested patterns

If a component of a record is itself a record, patterns can be nested to take the object apart to any depth:

record Point(int x, int y) {}
record Line(Point start, Point end) {}
 
void main() {
    Object value = new Line(new Point(0, 0), new Point(3, 4));
 
    if (value instanceof Line(Point(var x1, var y1), Point(var x2, var y2))) {
        double length = Math.hypot(x2 - x1, y2 - y1);
        IO.println("Line length: " + length);
    }
}

Output:

Line length: 5.0

Without patterns you'd have to write line.end().x() - line.start().x() and so on.

The unnamed variable

If you don't need some component, write _ in its place — the unnamed variable (Java 22). It shows that the value is skipped on purpose:

record Order(String customer, double amount, String city) {}
 
void main() {
    List<Order> orders = List.of(
            new Order("Anna", 20.0, "Rome"),
            new Order("Oleg", 35.5, "Oslo"));
 
    for (Order order : orders) {
        if (order instanceof Order(var name, _, var city)) {
            IO.println(name + " from " + city);
        }
    }
}

Output:

Anna from Rome
Oleg from Oslo

Common mistake

A pattern must list all components of the record. Leave out even one and the program won't compile:

record Point(int x, int y) {}
 
void main() {
    Object value = new Point(3, 4);
 
    if (value instanceof Point(int x)) {
        IO.println(x);
    }
}

Output:

Main.java:6: error: incorrect number of nested patterns
    if (value instanceof Point(int x)) {
                         ^
  required: int,int
  found: int
1 error
error: compilation failed

Unneeded components aren't left out but replaced with _: Point(int x, _).

Thanks for reading!

Donate to the site — we work hard for you.