Что такое pattern matching в Java и как он используется?

Ответ

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

Основные формы pattern matching в Java:

1. Pattern matching для instanceof (Java 16+)

// Старый подход (до Java 16)
if (obj instanceof String) {
    String s = (String) obj;
    System.out.println(s.length());
}

// Новый подход с pattern matching
if (obj instanceof String s) {
    // Переменная 's' автоматически приводится к String
    System.out.println(s.length());
}

// Можно использовать в условиях
if (obj instanceof String s && s.length() > 5) {
    System.out.println("Длинная строка: " + s);
}

2. Pattern matching в switch (Java 21+, preview в Java 17-20)

// Типизированный pattern matching в switch
String format(Object obj) {
    return switch (obj) {
        case Integer i -> String.format("int: %d", i);
        case Long l    -> String.format("long: %d", l);
        case Double d  -> String.format("double: %f", d);
        case String s  -> String.format("String: %s", s);
        case null      -> "null";
        default        -> obj.toString();
    };
}

// С guard-условиями (when)
String checkNumber(Number n) {
    return switch (n) {
        case Integer i when i > 0 -> "Положительное целое";
        case Integer i when i == 0 -> "Ноль";
        case Integer i -> "Отрицательное целое";
        case Double d when d > 0 -> "Положительное дробное";
        default -> "Другое число";
    };
}

3. Record patterns (Java 21+, preview)

record Point(int x, int y) {}

// Деструктуризация record в pattern matching
void printPoint(Object obj) {
    if (obj instanceof Point(int x, int y)) {
        System.out.printf("Точка: x=%d, y=%d%n", x, y);
    }
}

// Вложенные record patterns
record Rectangle(Point topLeft, Point bottomRight) {}

double area(Object obj) {
    return switch (obj) {
        case Rectangle(Point(var x1, var y1), Point(var x2, var y2)) -> 
            Math.abs((x2 - x1) * (y2 - y1));
        default -> 0.0;
    };
}

Преимущества pattern matching:

  1. Упрощение кода — меньше boilerplate-кода для проверок и приведений типов
  2. Безопасность — исключаются ошибки приведения типов
  3. Выразительность — код лучше отражает намерения разработчика
  4. Поддержка null — явная обработка null в switch

Ограничения и особенности:

  • Pattern matching для instanceof доступен с Java 16 (final)
  • Pattern matching в switch — с Java 21 (final), был в preview с Java 17
  • Record patterns — с Java 21 (preview)
  • Переменные в pattern matching имеют ограниченную область видимости (scope)

Практическое применение:

  • Обработка разнотипных данных (парсинг, сериализация)
  • Реализация посетителей (visitor pattern) без наследования
  • Обработка AST (abstract syntax trees) в компиляторах и анализаторах

Ответ 18+ 🔞

Да ты посмотри, что эти умники в Java придумали! Pattern matching, блядь. Ну, наконец-то, ёпта! Сидели-сидели, смотрели на Scala и Kotlin, и мозги, наконец, прочистило.

Раньше-то, сука, как было? Хочешь проверить, что объект — строка, и с ней поработать?

if (obj instanceof String) {
    String s = (String) obj; // Вот этот кастинг, блядь! Рука отвалится писать!
    System.out.println(s.length());
}

Сплошное издевательство над клавиатурой и здравым смыслом. А теперь, с шестнадцатой Java, можно просто:

if (obj instanceof String s) {
    // И всё, блядь! Переменная 's' уже тут, готовая, приведённая. Красота!
    System.out.println(s.length());
}

Чувствуешь разницу? Как будто тебе с горы спустились, а не в гору карабкаться. И сразу в условии можно её использовать, хитрая жопа!

if (obj instanceof String s && s.length() > 5) {
    System.out.println("Длинная строка: " + s);
}

А это, блядь, просто песня! Всё в одну строчку, чисто, ясно. Никакого лишнего мусора.

Но это, конечно, цветочки. Ягодки начались, когда в switch это запихнули (Java 21, окончательно, а то preview-версии — это отдельный пиздец, доверия ебать ноль).

Смотри, как красивше стало:

String format(Object obj) {
    return switch (obj) {
        case Integer i -> String.format("int: %d", i);
        case Long l    -> String.format("long: %d", l);
        case Double d  -> String.format("double: %f", d);
        case String s  -> String.format("String: %s", s);
        case null      -> "null"; // А нуля теперь не боится, сука! Явно указал — и спи спокойно.
        default        -> obj.toString();
    };
}

Раньше бы тут на if-ах мозг сломать можно было, а теперь — раз, и готово. И guard-условия (when) добавили, вообще огонь:

String checkNumber(Number n) {
    return switch (n) {
        case Integer i when i > 0 -> "Положительное целое";
        case Integer i when i == 0 -> "Ноль";
        case Integer i -> "Отрицательное целое"; // Сюда уже все остальные целые попадут
        case Double d when d > 0 -> "Положительное дробное";
        default -> "Другое число";
    };
}

Ну и вершина, блядь, этого цирка — record patterns (в Java 21 пока preview, но уже близко). Это когда у тебя есть record:

record Point(int x, int y) {}

И ты его можешь, сука, разобрать на запчасти прямо в условии! В рот меня чих-пых!

void printPoint(Object obj) {
    if (obj instanceof Point(int x, int y)) { // Смотри! Просто взял и вытащил x и y!
        System.out.printf("Точка: x=%d, y=%d%n", x, y);
    }
}

А если record вложенный? Да похуй!

record Rectangle(Point topLeft, Point bottomRight) {}

double area(Object obj) {
    return switch (obj) {
        case Rectangle(Point(var x1, var y1), Point(var x2, var y2)) -> 
            Math.abs((x2 - x1) * (y2 - y1)); // И тут, блядь, всё разобрал! Красота-то какая!
        default -> 0.0;
    };
}

В чём, собственно, соль, а?

  1. Код короче. Меньше этой boilerplate-хуйни с кастами и проверками. Глаза не болят.
  2. Надёжнее. Шанс накосячить с приведением типа — ноль ебать. Компилятор сам всё проверит.
  3. Понятнее. Смотришь на код и сразу видишь, что происходит, а не разгадываешь китайскую грамоту из if-ов.
  4. null не страшен. В switch его теперь явно ловить можно, а не гадать, вылетит NPE или нет.

Но и подводные камни есть, куда без них:

  • instanceof с паттернами — с 16-й Java, всё стабильно.
  • switch с паттернами — только с 21-й окончательно. До этого были preview-версии — играть в угадайку, что завтра сломают.
  • Record patterns — пока ещё в preview, так что терпения ебать нужно.
  • Область видимости у переменных из паттерна — хитрая. Не везде её можно использовать, компилятор следит.

Где это всё, блядь, пригодится?

  • Разбираешь какую-нибудь хуйню — JSON, XML, данные из сети. Всякие объекты разных типов приходят — вот тут pattern matching царь и бог.
  • Писал бы Visitor pattern раньше? Теперь можно часто без этого овердохуища наследования обойтись.
  • Компиляторы, анализаторы кода, AST (Abstract Syntax Tree) — тут вообще родная стихия для паттернов.

Короче, вещь нужная. Не то чтобы прям революция, но жить определённо стало проще. Как будто тяжёлую сумку с балкона сбросил.