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 the instanceof Operator

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 for more information.

Pattern Matching for switch Expressions and Statements

Note:

This is a preview feature, which is a feature whose design, specification, and implementation are complete, but is not permanent, which means that the 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 406.

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

Guarded Patterns and Parenthesized Patterns

Note:

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

A guarded pattern enables a pattern to be refined with a boolean expression. A value matches a guarded pattern if it matches the pattern and the boolean expression 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 guarded pattern:

    static void test(Object obj) {
        switch (obj) {
            case String s && (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 matches if obj is both a String and of length 1. The second patten matches if obj is a String of a different length.

A guarded patten has the form p && e where p is a pattern and e is a boolean expression. The scope of a pattern variable that appears in p includes e. This lets you specify patterns such as String s && (s.length() > 1), which matches a value that can be cast to a String whose length is greater than one.

Parenthesized Patterns

Note:

This feature is part of JEP 406, 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";
        };
    }