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

Разбор метода компиляции шаблонов в java.util.regex
Раздел: Регулярные выражения
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) Именованные группы и извлечение:

Пример java
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) Встроенные флаги и комбинирование:

Пример java
Pattern p = Pattern.compile("(?i)(?s)hello.*world");
// (?i) - ignore case, (?s) - dotall
System.out.println(p.matcher("Hello\nWorld").find());
true

3) Предотвращение бэктрекинга: possessive-квантификаторы и атомарные группы:

Пример java
// проблемный шаблон
Pattern bad = Pattern.compile("(a+)+b");
// улучшенный вариант с possessive
Pattern good = Pattern.compile("(a++)+b");
System.out.println(good.matcher("aaaaaaaaaaaaab").matches());
true

4) Повторное использование Matcher и reset:

Пример java
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 как статическое поле для повышения производительности:

Пример java
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) Тайм-аут для потенциально медленного сопоставления (обходной путь):

Пример java
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-флага для точного сопоставления без экранирования:

Пример java
Pattern p = Pattern.compile(".*?+[]()", Pattern.LITERAL);
System.out.println(p.matcher(".*?+[]()").matches());
true

8) Разбор табличных данных с помощью lookaround и захвата групп:

Пример java
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  // первая строчка заголовок; при поиске реальных записей выводятся соответствующие значения

джава Pattern.compile function comments

En
Pattern.compile Компилирует регулярное выражение