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.