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) Использование именованных групп и извлечение по имени:
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 для читабельных многострочных шаблонов:
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):
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) для локального анализа подстроки без создания новой строки:
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 квантификаторов и явных границ. Пример уязвимости и исправления:
// Плохо: потенциальное большое время выполнения
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) Комбинация флагов и встроенные флаги в строке шаблона:
Pattern p = Pattern.compile("(?i)hello"); // встроенный флаг нечувствительности к регистру
System.out.println(p.matcher("HeLLo").matches());
true
7) Использование Pattern.LITERAL для поиска точной подстроки без экранирования:
Pattern p = Pattern.compile(Pattern.quote("$^*+?()[]{}\\"));
System.out.println(p.matcher("Find $^*+?()[]{}\\ here").find());
true
8) Применение для потоковой обработки: компиляция паттерна вне цикла, затем использование в многопоточной среде - Pattern потокобезопасен, но Matcher не является. Следует создавать новый Matcher для каждого потока или синхронизировать доступ.
9) Получение всех совпадений с индексами позиций:
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