Pattern.compile: примеры (JAVA)
Pattern.compile(String regex): PatternОписание Pattern.compile()
Метод Pattern.compile() из пакета java.util.regex выполняет компиляцию текста регулярного выражения в immutable объект Pattern. Это базовый шаг для последующего поиска, сопоставления и замены по шаблону с помощью Matcher или методов классов-обёрток.
Сигнатуры:
static Pattern compile(String regex)- компиляция шаблона без флагов.static Pattern compile(String regex, int flags)- компиляция с набором флагов, передаваемых как побитовая комбинация констант.
Возвращаемое значение: объект Pattern, представляющий скомпилированный регулярный шаблон. При некорректном синтаксисе шаблона генерируется PatternSyntaxException.
Часто используемые флаги (константы класса Pattern):
CASE_INSENSITIVE- нечувствительность к регистру.MULTILINE- многострочный режим, ^ и $ соответствуют началу/концу строки, а не всего текста.DOTALL- точка (.) соответствует также символу перевода строки.UNICODE_CASE- учитывать Unicode при сопоставлении регистров (в сочетании с CASE_INSENSITIVE).UNICODE_CHARACTER_CLASS- классы символов (\w, \d и т. п.) опираются на Unicode категории.COMMENTS- игнорирование пробелов и комментариев в шаблонах (позволяет разносить шаблон по строкам).LITERAL- трактовать входную строку полностью как литерал (эквивалентно вызовуPattern.quote()).UNIX_LINES- считать перевод строки только символом '\n' для поведения ^ и $.CANON_EQ- сравнение с канонической равностью Unicode (редко используется и медленнее).
Примечания по использованию:
- Производительность: компиляция регулярного выражения дороже сопоставления. Для многократных операций рекомендуется хранить
Patternв статическом поле вместо повторной компиляции. - Потокобезопасность:
Patternявляется immutable и безопасен для многопоточного доступа.Matcherне потокобезопасен и должен использоваться отдельно в каждом потоке или получаться черезpattern.matcher(input). - Экранирование: при формировании строки-литерала в Java следует учитывать двойное экранирование (например, в коде строка для \d пишется как "\\d"). Для динамического экранирования пользовательского ввода существует
Pattern.quote().
Короткие примеры использования
1) Простая компиляция и проверка совпадения:
import java.util.regex.*;
Pattern p = Pattern.compile("abc");
Matcher m = p.matcher("abc");
boolean b = m.matches();
System.out.println(b);
true
2) Флаг нечувствительности к регистру:
Pattern p = Pattern.compile("hello", Pattern.CASE_INSENSITIVE);
System.out.println(p.matcher("HeLLo").matches());
true
3) DOTALL: точка захватывает перенос строки:
Pattern p = Pattern.compile("a.*b", Pattern.DOTALL);
System.out.println(p.matcher("a\nline\nb").find());
true
4) MULTILINE: ^ и $ для строк внутри текста:
Pattern p = Pattern.compile("^start", Pattern.MULTILINE);
Matcher m = p.matcher("line1\nstart of second");
System.out.println(m.find());
true
5) Экранирование пользовательского ввода через Pattern.quote:
String user = ".*+?";
Pattern p = Pattern.compile(Pattern.quote(user));
System.out.println(p.matcher(".*+?").matches());
true
6) Ошибка синтаксиса шаблона (PatternSyntaxException):
try {
Pattern.compile("(unclosed");
} catch (PatternSyntaxException e) {
System.out.println(e.getMessage());
}
Unclosed group near index 8
(unclosed
^
Аналоги и связанные инструменты в Java
В Java имеются способы, пересекающиеся по функциональности с Pattern.compile():
String.matches(regex)- простая проверка, эквивалентная вызовуPattern.matches(regex, str), но выполняет компиляцию при каждом вызове; удобна для редких проверок.String.split(regex),String.replaceAll(regex, repl),String.replaceFirst- удобства для разбиения и замены, используют внутренне регулярные выражения.Matcher- основной инструмент для поиска совпадений, извлечения групп и последовательной обработки; используется вместе сPattern.- Библиотеки: Apache Commons Lang/Regex или сторонние движки предоставляют дополнительные возможности и утилиты (например, предкомпиляция шаблонов, расширенные синтаксисы и пр.).
Выбор зависит от сценария: для повторных операций предпочтительна явная компиляция в Pattern, для одноразовой проверки допустима String.matches. Для сложной логики и извлечения групп обязательна работа через Matcher.
Альтернативы в других языках
Краткое сравнение и особенности в популярных языках, примеры и выводы:
- JavaScript (RegExp): синтаксис близок, конструктор
new RegExp(pattern, flags)или литералы/pat/flags. Флаги:i,m,s(ES2018 добавил dotAll). Пример:
const re = /hello/i;
console.log(re.test('HeLLo'));
true
- Python (re):
re.compile(pattern, flags), флагиre.IGNORECASE,re.DOTALLи пр. Поведение Unicode зависит от версии интерпретатора. Пример:
import re
p = re.compile(r"hello", re.IGNORECASE)
print(bool(p.match('HeLLo')))
True
- PHP (PCRE): функции
preg_match,preg_replaceс модификаторами типаi,s,m. PCRE поддерживает расширенный синтаксис, отличающийся от Java.
<?
echo preg_match('/hello/i', 'HeLLo');
?>
1
- C# (System.Text.RegularExpressions.Regex):
Regex.CompileToAssembly и опция компиляции в «native» код для ускорения. ФлагиRegexOptions.IgnoreCase,Singlelineи др. Пример:
using System.Text.RegularExpressions;
Console.WriteLine(Regex.IsMatch("HeLLo", "hello", RegexOptions.IgnoreCase));
True
- Go (regexp): пакет
regexp,CompileиMustCompile. Синтаксис ближе к RE2, не поддерживает произвольные бэктреки и некоторые сложные функции, зато устойчив к катастрофическому бэктрекингу.
package main
import (
"fmt"
"regexp"
)
func main(){
r := regexp.MustCompile("hello")
fmt.Println(r.MatchString("HeLLo"))
}
false
Примечание: в Go по умолчанию регистрозависимо; для IGNORECASE требуется префикс (?i).
- Kotlin: использует JVM-реализацию, синтаксически близка к Java, есть удобные расширения. Пример:
Regex("pat"). - Lua: собственный простой механизм шаблонов, отличающийся от PCRE/Java и не поддерживающий полноценные регулярные выражения; для PCRE доступна библиотека Lrexlib.
- SQL: в разных СУБД разные механизмы: LIKE (простые подстановки), POSIX regex или встроенные функции (например, PostgreSQL ~ operator). Ограничения по синтаксису и производительности.
Ключевые отличия от Java:
- Синтаксическая несовместимость между движками (PCRE, RE2, Java regex и пр.).
- Различия в поддержке Unicode и поведении флагов.
- Некоторые реализации (Go/RE2) проектированы, чтобы избегать экспоненциального бэктрекинга, в то время как Java позволяет более богатые конструкции, но с риском производительности.
Типичные ошибки и примеры
1) Неправильное экранирование в Java-литерале:
// Ошибка компиляции: illegal escape character
Pattern p = Pattern.compile("\d+"); // неверно написано в исходнике как "\d+"? (в примере видно некорректную строку)
Если в исходном коде написать "\d+" как "\d+" с одинарным слэшем, компилятор Java выдаст ошибку 'illegal escape character'. В корректном коде в строке должно быть "\\d+".
Корректный пример:
Pattern p = Pattern.compile("\\d+");
System.out.println(p.matcher("123").matches());
true
2) PatternSyntaxException при некорректном шаблоне:
try { Pattern.compile("("); } catch (PatternSyntaxException e) { System.out.println("Syntax: " + e.getDescription()); }
Syntax: Unterminated group
3) Катастрофическое бэктрекинг-поведение:
String s = "aaaaaaaaaaaaaaaaaaaaaaaa!";
Pattern p = Pattern.compile("(a+)+!");
System.out.println(p.matcher(s).find());
Работа может быть крайне медленной или привести к задержкам при большом объеме входа. Решение: использовать possessive-квантификаторы или атомарные группы.
4) Неправильный выбор флага MULTILINE vs DOTALL:
Pattern p = Pattern.compile("^start$");
// ожидание, что '^' и '$' найдут строки внутри текста - требуется MULTILINE
Без Pattern.MULTILINE символы ^ и $ соответствуют началу и концу всего ввода, а не внутренних строк.
5) Ошибка при многопоточном использовании Matcher:
Pattern p = Pattern.compile("\d+");
Matcher m = p.matcher("12345");
// совместное использование одного Matcher в нескольких потоках приведет к некорректному поведению
Matcher является не потокобезопасным; в многопоточном коде следует либо создавать новый Matcher через pattern.matcher(input) в каждом потоке, либо синхронизировать доступ.
Изменения и эволюция
Исторически движок регулярных выражений доступен в Java с версии 1.4. Среди заметных изменений:
- Добавление флага
UNICODE_CHARACTER_CLASSи улучшений поддержки Unicode (вплоть до Java 7). - Периодические обновления поддержки версии Unicode в составе JDK при релизах платформы, что влияет на распознавание категорий символов и поведение \p{...}.
- Мелкие оптимизации и исправления производительности в разных релизах JDK; багфиксы в обработке граничных случаев шаблонов и в классе
Pattern/Matcher.
Специфичных революционных изменений в самом API метода compile в последних релизах не было; изменения в основном касаются поддержки Unicode, производительности и исправления ошибок.
Расширенные и редко встречающиеся примеры
1) Именованные группы и извлечение:
Pattern p = Pattern.compile("(?[a-z]+)=(?\\d+)");
Matcher m = p.matcher("x=123 y=456");
while (m.find()) {
System.out.println(m.group("key") + ":" + m.group("val"));
}
x:123 y:456
2) Встроенные флаги и комбинирование:
Pattern p = Pattern.compile("(?i)(?s)hello.*world");
// (?i) - ignore case, (?s) - dotall
System.out.println(p.matcher("Hello\nWorld").find());
true
3) Предотвращение бэктрекинга: possessive-квантификаторы и атомарные группы:
// проблемный шаблон
Pattern bad = Pattern.compile("(a+)+b");
// улучшенный вариант с possessive
Pattern good = Pattern.compile("(a++)+b");
System.out.println(good.matcher("aaaaaaaaaaaaab").matches());
true
4) Повторное использование Matcher и reset:
Pattern p = Pattern.compile("\\d+");
Matcher m = p.matcher("");
for (String s : new String[]{"1","22","333"}) {
m.reset(s);
System.out.println(m.matches());
}
true true true
5) Pattern как статическое поле для повышения производительности:
public class Utils {
private static final Pattern EMAIL = Pattern.compile("[\\w.%+-]+@[\\w.-]+\\.[A-Za-z]{2,6}");
public static boolean isEmail(String s) { return EMAIL.matcher(s).matches(); }
}
Позволяет избежать повторной компиляции при каждом запросе в приложении.
6) Тайм-аут для потенциально медленного сопоставления (обходной путь):
import java.util.concurrent.*;
ExecutorService ex = Executors.newSingleThreadExecutor();
Future f = ex.submit(() -> Pattern.compile("(a+)+$").matcher(longInput).find());
try {
boolean found = f.get(200, TimeUnit.MILLISECONDS);
System.out.println(found);
} catch (TimeoutException te) {
f.cancel(true);
System.out.println("regex timeout");
}
ex.shutdownNow();
regex timeout // при длительной обработке регулярного выражения
7) Использование LITERAL-флага для точного сопоставления без экранирования:
Pattern p = Pattern.compile(".*?+[]()", Pattern.LITERAL);
System.out.println(p.matcher(".*?+[]()").matches());
true
8) Разбор табличных данных с помощью lookaround и захвата групп:
String csv = "name,age,city\nJohn,30,NY\nAnna,25,LA";
Pattern p = Pattern.compile("(?m)^(?[^,]+),(?\\d+),(?[^,]+)$");
Matcher m = p.matcher(csv);
while (m.find()) System.out.println(m.group("name") + " -> " + m.group("age"));
name -> age // первая строчка заголовок; при поиске реальных записей выводятся соответствующие значения