Pattern: примеры (JAVA)

Примеры использования Pattern в Java
Раздел: Верификация (валидация), Регулярные выражения
Pattern(String regex)

Описание класса Pattern и его основных методов

В Java класс java.util.regex.Pattern представляет скомпилированное регулярное выражение. Он не выполняет сопоставление самостоятельно: для поиска используется связанный объект Matcher, создаваемый методом pattern.matcher(CharSequence). Pattern используется для предкомпиляции паттернов, управления флагами и выполнения операций, требующих регулярных выражений.

Когда применяется: при валидации, поиске, разбиении строк, замене по шаблону, извлечении подстрок по шаблону и при необходимости многократного повторного использования одного регулярного выражения (компиляция один раз повышает производительность).

Главные статические методы и их назначение:

  • Pattern.compile(String regex) - компилирует регулярное выражение. Возвращает объект Pattern. Может бросить PatternSyntaxException при некорректном синтаксисе.
  • Pattern.compile(String regex, int flags) - то же, но с флагами. Флаги перечислены ниже.
  • Pattern.matches(String regex, CharSequence input) - удобный метод: компилирует regex и проверяет, совпадает ли вся строка целиком. Возвращает boolean. Неэффективен при многократном вызове с одним шаблоном.
  • Pattern.quote(String s) - экранирует все метасимволы шаблона и возвращает литеральную форму шаблона. Возвращает строку, которую безопасно использовать как regex для поиска точной подстроки.
  • pattern.split(CharSequence input) и pattern.split(CharSequence input, int limit) - разбивают строку по шаблону. Возвращают массив String[]. Поведение с limit аналогично String.split.

Ключевые флаги (значения из Pattern):

  • CASE_INSENSITIVE - нечувствительность к регистру.
  • MULTILINE - ^ и $ соответствуют началу/концу строк, а не только всей строки.
  • DOTALL - точка . совпадает с любым символом, включая перевод строки.
  • UNICODE_CASE - учитывать правила Unicode при сравнениях регистров.
  • UNICODE_CHARACTER_CLASS - классы типа \w, \d используют правила Unicode вместо ASCII.
  • COMMENTS - включить режим свободного формата: пробелы игнорируются и допускаются комментарии #.
  • LITERAL - трактовать весь шаблон как литерал (равносильно использованию Pattern.quote).
  • CANON_EQ - сравнение в канонической эквивалентности Unicode (редко используется, влияет на производительность).

Примеры возвращаемых значений и исключений:

  • Pattern.compile возвращает Pattern или бросает PatternSyntaxException.
  • Pattern.matches возвращает boolean.
  • pattern.split возвращает String[].

Рекомендации: при многократном использовании шаблона компилировать его один раз и переиспользовать объект Pattern. Для одноразовой проверки короткой строки можно применять Pattern.matches ради краткости.

Короткие примеры использования Pattern

1) Компиляция и использование с Matcher:

import java.util.regex.*;

Pattern p = Pattern.compile("\\d{3}-\\d{2}-\\d{4}");
Matcher m = p.matcher("123-45-6789");
boolean matches = m.matches();
System.out.println(matches);
true

2) Pattern.matches (весь ввод должен соответствовать):

boolean ok = Pattern.matches("\\w+@\\w+\\.com", "user@example.com");
System.out.println(ok);
true

3) Флаги: нечувствительность к регистру:

Pattern p = Pattern.compile("hello", Pattern.CASE_INSENSITIVE);
System.out.println(p.matcher("HeLLo").matches());
true

4) split с лимитом:

Pattern p = Pattern.compile(",\s*");
String[] parts = p.split("a, b, c, d", 3);
for (String s : parts) System.out.println(s);
a
b
c, d

5) Экранирование литерала:

String literal = Pattern.quote("file(1).txt");
Pattern p = Pattern.compile(literal);
System.out.println(p.matcher("file(1).txt").find());
true

Похожие средства в Java и особенности

  • java.util.regex.Matcher - выполняет сопоставления, поиск, замену. Предпочтителен для итеративных поисков, сложных замен с группами и повторного использования Pattern.
  • String.matches - вызывает Pattern.matches под капотом. Удобен для однократной проверки, но неэффективен при повторных вызовах потому что компилирует выражение заново.
  • String.split и String.replaceAll - более короткие способы использования регулярных выражений при простых задачах. Если необходимо повторять шаблон в нескольких местах - лучше скомпилировать Pattern и применять Matcher.

Когда что выбирать: для одноразовых простых операций допустимы методы String. Для производительности и гибкости - Pattern + Matcher. Для сложных замен с сохранением частей строки и управлением позициями предпочтительнее Matcher.appendReplacement/appendTail.

Аналоги Pattern в других языках и ключевые отличия

Краткие примеры и особенности:

  • JavaScript: класс RegExp. Регекспы встроены в язык. Флаги: i, m, s (ES2018), u для Unicode. Пример:
// JavaScript
const re = /\d{3}-\d{2}-\d{4}/;
console.log(re.test('123-45-6789'));
true
  • Python: модуль re. Важное отличие - наличие сырого строкового литерала r"..." для удобства. Флаги через re.I, re.M, re.S.
# Python
import re
p = re.compile(r"\d{3}-\d{2}-\d{4}")
print(bool(p.fullmatch('123-45-6789')))
True
  • PHP: функции preg_match, preg_split, синтаксис шаблонов в виде /.../. Пример:
// PHP
$pattern = '/\d{3}-\d{2}-\d{4}/';
var_dump(preg_match($pattern, '123-45-6789'));
int(1)
  • C#: System.Text.RegularExpressions.Regex. Поддерживает похожие флаги, есть статические методы и возможность кэширования. Пример:
// C#
using System.Text.RegularExpressions;
Console.WriteLine(Regex.IsMatch("123-45-6789", "\\d{3}-\\d{2}-\\d{4}"));
True
  • Go: пакет regexp. Синтаксис близок к RE2, без некоторых расширений PCRE (например, нет lookbehind). Пример:
// Go
import "regexp"
re := regexp.MustCompile(`\d{3}-\d{2}-\d{4}`)
fmt.Println(re.MatchString("123-45-6789"))
true
  • Lua: встроенные шаблоны проще и не равны регекспам PCRE; для полноценной поддержки используются библиотеки типа Lrexlib (PCRE).
  • SQL: в разных СУБД разные возможности: LIKE (простой), MySQL REGEXP, PostgreSQL ~ и ~* и поддержка POSIX регулярных выражений. Ограничения и синтаксические различия важны при переносе шаблонов.

Отличия от Java: синтаксис регекспов в большинстве языков одинаков для базовых конструкций, но разница в доступных расширениях (lookbehind, обратные ссылки, расширенные флаги) и в том, как выполняется компиляция и кэширование шаблонов.

Типичные ошибки при работе с Pattern

  • Неправильное экранирование в строковых литералах. В Java обратный слэш нужно удваивать: "\\d+" а не "\d+". Пример ошибки:
Pattern p = Pattern.compile("\d+"); // некорректно в Java строковом литерале
Ошибка компиляции или неожиданные результаты: в строке символы экранирования будут потеряны
  • Ожидание, что String.matches ведет себя как find. Метод требует полного совпадения строки. Пример:
System.out.println("123abc".matches("\\d+"));
false  // потому что вся строка не состоит только из цифр
  • Использование дорогостоящих шаблонов без предкомпиляции. Компрометирует производительность при многократных вызовах.
  • Катастрофическая перекомпенсация (catastrophic backtracking) при плохо сконструированных шаблонах (жадные повторения с альтернативами). Может приводить к сильному падению скорости или тайм-аутам при обработке больших строк.
  • PatternSyntaxException при ошибках синтаксиса, например незакрытые скобки, неправильные группы.

Пример PatternSyntaxException:

Pattern.compile("(abc");
Exception in thread "main" java.util.regex.PatternSyntaxException: Unclosed group

Изменения в реализации и поддержке в последних выпусках JDK

В ядре API класса Pattern существенные изменения встречаются редко. В последних версиях JDK основное развитие касалось поддержки новых версий Unicode и улучшения производительности регулярного движка. Ключевые моменты:

  • Обновления таблиц Unicode с каждой версией JDK, что влияет на поведение флагов UNICODE_CASE и UNICODE_CHARACTER_CLASS.
  • Постепенные оптимизации производительности и исправления багов в движке регулярных выражений.
  • Новые языковые возможности (например, флаг s в JavaScript или улучшения в других реализациях) не напрямую меняют API Pattern, но влияют на переносимость шаблонов между платформами.

Рекомендация: при обновлении JDK проверять критичные регулярные выражения на тестовом наборе данных, так как небольшие изменения в классификации символов Unicode могут изменить поведение сопоставлений.

Расширенные и редкие примеры использования

1) Использование именованных групп и извлечение по имени:

Пример java
import java.util.regex.*;

Pattern p = Pattern.compile("(?\\d{3})-(?\\d{3})-(?\\d{4})");
Matcher m = p.matcher("123-456-7890");
if (m.find()) {
    System.out.println(m.group("area") + ", " + m.group("prefix") + ", " + m.group("line"));
}
123, 456, 7890

2) Использование режима COMMENTS для читабельных многострочных шаблонов:

Пример java
Pattern p = Pattern.compile("(?x)   # verbose mode\n  (\\d{3})  # area\n  -\\s*    # dash and optional spaces\n  (\\d{2})  # prefix\n  -\\s*(\\d{4})");
Matcher m = p.matcher("123-45-6789");
System.out.println(m.find());
true

3) Эффективные замены с сохранением частей строки (appendReplacement/appendTail):

Пример java
Pattern p = Pattern.compile("(\\w+)=(\\d+)");
Matcher m = p.matcher("a=1 b=20 c=300");
StringBuffer sb = new StringBuffer();
while (m.find()) {
    // поменять местами имя и значение
    m.appendReplacement(sb, m.group(2) + ":" + m.group(1));
}
m.appendTail(sb);
System.out.println(sb.toString());
1:a 20:b 300:c

4) Ограничение региона поиска (region) для локального анализа подстроки без создания новой строки:

Пример java
Pattern p = Pattern.compile("\\w+");
Matcher m = p.matcher("alpha beta gamma");
// анализировать только часть строки (start=6, end=10 -> "beta" часть)
m.region(6, 10);
while (m.find()) System.out.println(m.group());
beta

5) Предотвращение backtracking: использование reluctant квантификаторов и явных границ. Пример уязвимости и исправления:

Пример java
// Плохо: потенциальное большое время выполнения
Pattern bad = Pattern.compile("(a+)+b");
Matcher mbad = bad.matcher("aaaa...aaaac");
System.out.println(mbad.find());

// Лучше: более конкретный шаблон или ограничение повторений
Pattern good = Pattern.compile("a{1,100}+b");
false
(во избежание долгого backtracking)

6) Комбинация флагов и встроенные флаги в строке шаблона:

Пример java
Pattern p = Pattern.compile("(?i)hello"); // встроенный флаг нечувствительности к регистру
System.out.println(p.matcher("HeLLo").matches());
true

7) Использование Pattern.LITERAL для поиска точной подстроки без экранирования:

Пример java
Pattern p = Pattern.compile(Pattern.quote("$^*+?()[]{}\\"));
System.out.println(p.matcher("Find $^*+?()[]{}\\ here").find());
true

8) Применение для потоковой обработки: компиляция паттерна вне цикла, затем использование в многопоточной среде - Pattern потокобезопасен, но Matcher не является. Следует создавать новый Matcher для каждого потока или синхронизировать доступ.

9) Получение всех совпадений с индексами позиций:

Пример java
Pattern p = Pattern.compile("\\w+");
Matcher m = p.matcher("one two three");
while (m.find()) {
    System.out.println(m.group() + " at " + m.start() + "-" + m.end());
}
one at 0-3
two at 4-7
three at 8-13

джава Pattern function comments

En
Pattern A compiled representation of a regular expression