4 Pattern Matching

Pattern matching involves testing whether an object has a particular structure, then extracting data from that object if there's a match. You can already do this with Java. However, pattern matching introduces new language enhancements that enable you to conditionally extract data from objects with code that's more concise and robust.

Pattern Matching for instanceof

The following example calculates the perimeter of the parameter shape only if it's an instance of Rectangle or Circle:
interface Shape { }
record Rectangle(double length, double width) implements Shape { }
record Circle(double radius) implements Shape { }
...
    public static double getPerimeter(Shape shape) throws IllegalArgumentException {
        if (shape instanceof Rectangle r) {
            return 2 * r.length() + 2 * r.width();
        } else if (shape instanceof Circle c) {
            return 2 * c.radius() * Math.PI;
        } else {
            throw new IllegalArgumentException("Unrecognized shape");
        }
    }

A pattern is a combination of a test, which is called a predicate; a target; and a set of local variables, which are called pattern variables:

  • The predicate is a Boolean-valued function with one argument; in this case, it’s the instanceof operator, testing whether the Shape argument is a Rectangle or a Circle.
  • The target is the argument of the predicate, which is the Shape value.
  • The pattern variables are those that store data from the target only if the predicate returns true, which are the variables r and s.

See Pattern Matching for instanceof Operator for more information.

Pattern Matching for switch

Note:

This is a preview feature. A preview feature is a feature whose design, specification, and implementation are complete, but is not permanent. A preview feature may exist in a different form or not at all in future Java SE releases. To compile and run code that contains preview features, you must specify additional command-line options. See Preview Language and VM Features.

For background information about pattern matching for switch expressions and statements, see JEP 427.

The following example also calculates the perimeter only for instances of Rectangle or Circle. However, it uses a switch expression instead of an if-then-else statement:

    public static double getPerimeter(Shape shape) throws IllegalArgumentException {
        return switch (shape) {
            case Rectangle r -> 2 * r.length() + 2 * r.width();
            case Circle c    -> 2 * c.radius() * Math.PI;
            default          -> throw new IllegalArgumentException("Unrecognized shape");
        }
    }

See Pattern Matching for switch Expressions and Statements.

When Clauses

Note:

This feature is part of JEP 427, which is a preview feature.

A when clause enables a pattern to be refined with a Boolean expression. A pattern label that contains a when clause is called a guarded pattern label, and the Boolean expression in the when clause is called a guard. A value matches a guarded pattern label if it matches the pattern and the guard evaluates to true. Consider the following example:

    static void test(Object obj) {
        switch (obj) {
            case String s:
                if (s.length() == 1) {
                    System.out.println("Short: " + s);
                } else {
                    System.out.println(s);
                }
                break;
            default:
                System.out.println("Not a string");
        }
    }

You can move the Boolean expression s.length == 1 into the case label with a when clause:

    static void test(Object obj) {
        switch (obj) {
            case String s when s.length() == 1 -> System.out.println("Short: " + s);
            case String s                      -> System.out.println(s);
            default                            -> System.out.println("Not a string");
        }
    }

The first pattern label (which is a guarded pattern label) matches if obj is both a String and of length 1. The second patten label matches if obj is a String of a different length.

A guarded patten label has the form p when e where p is a pattern and e is a Boolean expression. The scope of any pattern variable declared in p includes e.

Parenthesized Patterns

Note:

This feature is part of JEP 427, which is a preview feature.

A parenthesized pattern is a pattern surrounded by a pair of parentheses. Because guarded patterns combine patterns and expressions, you might introduce parsing ambiguities. You can surround patterns with parentheses to avoid these ambiguities, force the compiler to parse an expression containing a pattern differently, or increase the readability of your code. Consider the following example:

    static Function<Integer, String> testParen(Object obj) {
        boolean b = true;
        return switch (obj) {
            case String s && b -> t -> s;
            default            -> t -> "Default string";
        };
    }    

This example compiles. However, if you want to make it clear that the first arrow token (->) is part of the case label and not part of a lambda expression, you can surround the guarded pattern with parentheses:

    static Function<Integer, String> testParen(Object obj) {
        boolean b = true;
        return switch (obj) {
            case (String s && b) -> t -> s;
            default              -> t -> "Default string";
        };
    }

Record Patterns

Note:

This feature is part of JEP 405, which is a preview feature.

You can use a record pattern to test whether a value is an instance of a record class type (see Record Classes) and, if it is, to recursively perform pattern matching on its component values. In the following example, the pattern tests if obj is an instance of the Point record:

    record Point(double x, double y) {}

    static void printAngleFromXAxis(Object obj) {
        if (obj instanceof Point(double x, double y)) {
            System.out.println(Math.toDegrees(Math.atan2(y, x)));
        }
    }     

In addition, this example extracts the x and y values from obj directly, automatically calling the Point record's accessor methods. The following example is the same as the previous one except it uses a type pattern instead of a record pattern:

    static void printAngleFromXAxisTypePattern(Object obj) {
        if (obj instanceof Point p) {
            System.out.println(Math.toDegrees(Math.atan2(p.y, p.x)));
        }
    }   

You can use var in the record pattern's component list. In the following example, the compiler infers that the pattern variables x and y are of type double:

    static void printAngleFromXAxis(Object obj) {
        if (obj instanceof Point(var x, var y)) {
            System.out.println(Math.toDegrees(Math.atan2(y, x)));
        }
    }     

A record pattern consists of a type, a (possibly empty) record component pattern list, and an optional identifier. A record pattern with an identifier is called a named record pattern, and the variable is the record pattern variable. The following is an example of a named record pattern where r is its record pattern variable:

    record Rectangle(Point upperLeft, Point lowerRight) {
        void printDimensions() {
            System.out.println(
                "Length = " + Math.abs(lowerRight.x - upperLeft.x) +
                ", height = " + Math.abs(lowerRight.y - upperLeft.y));
        }
    }

    static void printRectangleDimensions(Object obj) {
        if (obj instanceof Rectangle(var ul, var lr) r) {
            System.out.printf("Upper-left corner: (%.2f, %.2f)%n", ul.x, ul.y);
            System.out.printf("Lower-right corner: (%.2f, %.2f)%n", lr.x, lr.y);
            r.printDimensions();
        }
    }

Record patterns are currently restricted such that if they name a generic record class, then they must use a generic type in the pattern and not a raw type. The following example compiles:

    record Box<T>(T t) { }
    
    static void printBoxContents(Box<Object> bo) {
        if (bo instanceof Box<Object>(String s)) {
            System.out.println("Box contains: " + s);
        }
    }
    
    static void printBoxContentsAgain(Box<String> bo) {
        if (bo instanceof Box<String>(var s)) {
            System.out.println("Box contains: " + s);
        }
    } 

However, the following example doesn't compile:

    record Box<T>(T t) { }

    static void erroneousPrintBoxContents(Box<Object> bo) {
        // Error: Raw deconstruction patterns are not allowed
        if (bo instanceof Box(var s)) {
            System.out.println("Box contains: " + s);
        }
    }

You can nest a record pattern inside another record pattern:

    enum Color { RED, GREEN, BLUE }
    record ColoredPoint(Point p, Color c) {}
    record ColoredRectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {}   

    static void printXCoordOfUpperLeftPointWithPatterns(ColoredRectangle r) {
        if (r instanceof ColoredRectangle(
            ColoredPoint(Point(var x, var y), var upperLeftColor),
                         var lowerRightCorner)) {
            System.out.println("Upper-left corner: " + x);
        }
    }   

Note:

The null value does not match any record pattern.