Pattern matching (Java 16+)

Pattern matching — это механизм, который сочетает проверку типа и извлечение данных в одном выражении. В Java сопоставление по образцу развивается поэтапно:

  • Java 16 (JEP 394) — pattern matching for instanceof (стабильно);

  • Java 21 (JEP 441) — pattern matching for switch (стабильно);

  • Java 21 (JEP 440) — record patterns (деконструкция records, стабильно).

Идея простая: вместо «проверить тип → привести к типу → достать поле» написать одно выражение, в котором все три действия слиты.

Минимальная версия для базовой формы: Java 16. Для record patterns и switch patterns: Java 21.

Синтаксис

// 1) Pattern matching for instanceof (Java 16+)
if (obj instanceof String s) {
    // s уже типа String, каст не нужен
}

// 2) Pattern matching for switch (Java 21)
String r = switch (obj) {
    case Integer i -> "int " + i;
    case String s  -> "str " + s;
    default        -> "?";
};

// 3) Record patterns (Java 21)
if (point instanceof Point(int x, int y)) {
    // x и y уже извлечены из record
}

Зачем нужно

  • Убирает ручной каст после instanceof.

  • Делает switch строго типизированным и exhaustive.

  • Деконструкция records читается как формула.

  • В связке с sealed-классами компилятор гарантирует обработку всех случаев.

Пример 1. instanceof без каста (Java 16+)

public class PatternInstanceof {
    public static String describe(Object obj) {
        if (obj instanceof String s) {
            return "Строка длиной " + s.length();
        } else if (obj instanceof Integer i) {
            return "Целое число " + i;
        } else if (obj instanceof int[] arr) {
            return "Массив int длиной " + arr.length;
        }
        return "Неизвестно";
    }

    public static void main(String[] args) {
        System.out.println(describe("AlashEd"));
        System.out.println(describe(42));
        System.out.println(describe(new int[] {1, 2, 3, 4}));
        System.out.println(describe(3.14));
    }
}

Output:

Строка длиной 7
Целое число 42
Массив int длиной 4
Неизвестно

Пример 2. Pattern с условием (guard, Java 21)

public class PatternGuard {
    public static String classify(Object obj) {
        return switch (obj) {
            case Integer i when i < 0   -> "Отрицательное: " + i;
            case Integer i when i == 0  -> "Ноль";
            case Integer i              -> "Положительное: " + i;
            case String s when s.isEmpty() -> "Пустая строка";
            case String s               -> "Строка: " + s;
            case null                   -> "null";
            default                     -> "Другое";
        };
    }

    public static void main(String[] args) {
        System.out.println(classify(-5));
        System.out.println(classify(0));
        System.out.println(classify(42));
        System.out.println(classify(""));
        System.out.println(classify("hello"));
        System.out.println(classify(null));
    }
}

Output:

Отрицательное: -5
Ноль
Положительное: 42
Пустая строка
Строка: hello
null

Пример 3. Record pattern — деконструкция (Java 21)

public class PatternRecord {
    public record Point(int x, int y) { }

    public static String quadrant(Point p) {
        return switch (p) {
            case Point(int x, int y) when x > 0 && y > 0 -> "I";
            case Point(int x, int y) when x < 0 && y > 0 -> "II";
            case Point(int x, int y) when x < 0 && y < 0 -> "III";
            case Point(int x, int y) when x > 0 && y < 0 -> "IV";
            case Point(int x, int y)                     -> "на оси";
        };
    }

    public static void main(String[] args) {
        System.out.println(quadrant(new Point(3, 4)));
        System.out.println(quadrant(new Point(-1, 2)));
        System.out.println(quadrant(new Point(-3, -3)));
        System.out.println(quadrant(new Point(5, -1)));
        System.out.println(quadrant(new Point(0, 0)));
    }
}

Output:

I
II
III
IV
на оси

Пример 4. Вложенные record patterns (Java 21)

public class PatternNested {
    public record Point(int x, int y) { }
    public record Line(Point from, Point to) { }

    public static double length(Line line) {
        if (line instanceof Line(Point(int x1, int y1), Point(int x2, int y2))) {
            int dx = x2 - x1;
            int dy = y2 - y1;
            return Math.sqrt(dx * dx + dy * dy);
        }
        return 0;
    }

    public static void main(String[] args) {
        var l = new Line(new Point(0, 0), new Point(3, 4));
        System.out.println("Длина: " + length(l));
    }
}

Output:

Длина: 5.0

Пример 5. С sealed-классами (exhaustive)

public class PatternSealed {
    public sealed interface Event permits ButtonPress, SensorRead { }
    public record ButtonPress(String name) implements Event { }
    public record SensorRead(String sensor, double value) implements Event { }

    public static String handle(Event e) {
        // default не нужен — Event запечатан
        return switch (e) {
            case ButtonPress(String name)       -> "Нажата кнопка: " + name;
            case SensorRead(String s, double v) -> s + " = " + v;
        };
    }

    public static void main(String[] args) {
        System.out.println(handle(new ButtonPress("START")));
        System.out.println(handle(new SensorRead("ultrasonic", 12.5)));
    }
}

Output:

Нажата кнопка: START
ultrasonic = 12.5

Пример 6. instanceof в логическом выражении

public class PatternLogical {
    public static boolean isShortString(Object obj) {
        return obj instanceof String s && s.length() < 5;
    }

    public static void main(String[] args) {
        System.out.println(isShortString("hi"));        // true
        System.out.println(isShortString("AlashEd"));   // false
        System.out.println(isShortString(42));          // false
        System.out.println(isShortString(null));        // false
    }
}

Output:

true
false
false
false

Подводные камни

Предупреждение

  • Имя переменной в pattern — это новая переменная, видимая только там, где проверка истинна. После if (... instanceof String s) переменная s доступна внутри if, но не в else.

  • В switch с pattern matching порядок case важен: более общий шаблон должен идти ниже.

  • case null нужно объявлять явно, иначе switch бросит NullPointerException.

  • Record patterns работают только с records — для обычных классов их применить нельзя.

  • Guard используется через ключевое слово when, а не &&.

Совет

Связка sealed + record + pattern matching даёт компилятору достаточно информации, чтобы поймать многие ошибки на этапе сборки. Используйте её для моделирования состояний.

См. также

Примечание

Материал основан на официальной документации Oracle Java SE (docs.oracle.com/en/java/javase) и спецификациях JEP 394, 440, 441 (openjdk.org/jeps/441), распространяемой под лицензией Oracle Free Documentation License. Тексты и примеры написаны заново для AlashEd Wiki.