String ·
‹ Предыдущий Следующий ›
⏱ 5 минут чтения Обновлено: 2026-10-17

Регулярные выражения в Java (regex)

Вызов "192.168.0.1".split(".") возвращает не четыре числа, а пустой массив длины 0. Причина в том, что split() принимает не обычный текст, а регулярное выражение, и точка в нём означает «любой символ» — разделителем становится вся строка. Чтобы такие сюрпризы не отнимали час отладки, достаточно один раз разобраться в синтаксисе regex.

Регулярные выражения в Java (regex) — это шаблоны для поиска, проверки, замены и разбиения текста, записанные на специальном языке метасимволов. Работа с ними сосредоточена в пакете java.util.regex: класс Pattern хранит скомпилированный шаблон, класс Matcher применяет его к строке. Кроме того, регулярные выражения принимают методы String: matches(), split(), replaceAll() и replaceFirst().

Что такое регулярные выражения в Java

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

Например, есть текст:

Используются файлы file1.doc, file2.txt.
А ещё было бы неплохо обратить внимание на файл file3.img.
Также просмотрите содержимое file4.doc.

Из него нужно извлечь все имена файлов: file1.doc, file2.txt, file3.img, file4.doc. Для этого подходит регулярное выражение:

[a-zA-Z0-9]+\.[a-z]{3}

Разберём его по частям: [a-zA-Z0-9]+ — одна или более латинских букв или цифр (имя файла), \. — сама точка (без обратного слэша точка означала бы «любой символ»), [a-z]{3} — ровно три строчные латинские буквы (расширение). Цифры в первом наборе обязательны: шаблон [a-zA-Z]+\.[a-z]{3} не найдёт ни одного имени из примера, потому что перед точкой стоит цифра.

В Java-коде обратный слэш внутри строкового литерала удваивается, поэтому \. записывается как "\\.":

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class FileNamesExample {
    public static void main(String[] args) {
        String text = "Используются файлы file1.doc, file2.txt.\n"
                + "А ещё было бы неплохо обратить внимание на файл file3.img.\n"
                + "Также просмотрите содержимое file4.doc.";
        Pattern pattern = Pattern.compile("[a-zA-Z0-9]+\\.[a-z]{3}");
        Matcher matcher = pattern.matcher(text);
        while (matcher.find()) {
            System.out.println(matcher.group());
        }
    }
}
// Вывод:
// file1.doc
// file2.txt
// file3.img
// file4.doc

Важно

В этом уроке шаблоны вне Java-кода записаны так, как их видит движок регулярных выражений (\d, \.), а в Java-коде — со вторым обратным слэшем ("\\d", "\\."). Запись "\d" в Java-строке не компилируется: illegal escape character. А чтобы найти в тексте сам обратный слэш, в Java-строке нужно написать четыре: "\\\\".

Pattern и Matcher: как применить regex в Java

Чтобы применить регулярное выражение в Java, шаблон компилируют методом Pattern.compile(), затем вызывают pattern.matcher(text) и получают объект Matcher, который ищет совпадения в конкретной строке. Публичных конструкторов у обоих классов нет: Pattern создаётся только через compile(), Matcher — только через matcher().

Pattern — скомпилированное, неизменяемое представление регулярного выражения. Компиляция проверяет синтаксис (ошибка даёт PatternSyntaxException) и строит внутреннюю структуру для быстрого поиска, поэтому один Pattern выгодно создать один раз (например, в поле static final) и использовать многократно.

Matcher — «искатель», который хранит состояние поиска по одной строке: текущую позицию, найденное совпадение и его группы. Для каждой новой строки создают новый Matcher или вызывают reset(newText).

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class PatternMatcherExample {
    public static void main(String[] args) {
        Pattern pattern = Pattern.compile("[a-z]+");
        Matcher matcher = pattern.matcher("code 2 learn java tutorial");
        while (matcher.find()) {
            System.out.println(matcher.group() + " [" + matcher.start() + ", " + matcher.end() + ")");
        }
    }
}
// Вывод:
// code [0, 4)
// learn [7, 12)
// java [13, 17)
// tutorial [18, 26)

Шаблон [a-z]+ — это последовательность из одной или более строчных латинских букв, поэтому цифра 2 и пробелы в совпадения не попадают. Метод start() возвращает индекс первого символа совпадения, end() — индекс символа после последнего (как у substring()).

Основные методы класса Pattern:

Метод Что делает Нюанс
Pattern.compile(regex) Компилирует шаблон Синтаксическая ошибка — PatternSyntaxException
Pattern.compile(regex, flags) Компилирует шаблон с флагами Флаги объединяются через |, см. раздел Флаги
pattern.matcher(text) Создаёт Matcher для строки Принимает любой CharSequence, в том числе StringBuilder
Pattern.matches(regex, text) Проверяет, что вся строка соответствует шаблону Компилирует шаблон при каждом вызове
pattern.split(text) Разбивает строку по совпадениям splitAsStream() (Java 8) возвращает Stream<String>
Pattern.quote(s) Превращает строку в шаблон, совпадающий с ней буквально Удобно для текста от пользователя
asPredicate() / asMatchPredicate() Возвращают Predicate<String> для фильтрации Первый работает как find() (Java 8), второй — как matches() (Java 11)

Основные методы класса Matcher:

Метод Что делает Граничный случай
matches() Шаблон совпадает со всей строкой "\\d+" на "abc123" — false
lookingAt() Шаблон совпадает с началом строки "\\d+" на "123abc" — true
find() Ищет следующее совпадение в любом месте строки Каждый вызов продолжает с места предыдущего совпадения
group(), group(n), group("name") Текст совпадения или группы До успешного поиска — IllegalStateException: No match found
start(), end() Индексы начала и конца (не включительно) совпадения Тоже требуют успешного поиска
groupCount() Количество групп в шаблоне Группа 0 (всё совпадение) не считается
replaceAll(r), replaceFirst(r) Заменяют все или первое совпадение В замене $1 — ссылка на группу
results() Поток всех совпадений Stream<MatchResult> Java 9+
reset(), reset(text) Сбрасывает позицию поиска, может сменить строку Позволяет переиспользовать Matcher

Начиная с Java 9 все совпадения удобно собрать в список одной цепочкой вызовов через results():

import java.util.List;
import java.util.regex.MatchResult;
import java.util.regex.Pattern;

public class ResultsExample {
    public static void main(String[] args) {
        List<String> numbers = Pattern.compile("\\d+")
                .matcher("a1b22c333")
                .results()
                .map(MatchResult::group)
                .toList();
        System.out.println(numbers);
    }
}
// Вывод: [1, 22, 333]

Метод toList() у Stream появился в Java 16; в более ранних версиях используйте collect(Collectors.toList()).

matches(), find() и lookingAt(): в чём разница

matches() возвращает true, только если шаблон описывает всю строку целиком; lookingAt() — если шаблон совпадает с началом строки; find() — если совпадение есть хотя бы где-то внутри. Перепутать их — самая частая причина, почему «правильная» регулярка возвращает false.

import java.util.regex.Pattern;

public class MatchesFindExample {
    public static void main(String[] args) {
        Pattern pattern = Pattern.compile("\\d+");
        for (String s : new String[]{"123", "123abc", "abc123"}) {
            System.out.println(s
                    + ": matches=" + pattern.matcher(s).matches()
                    + ", lookingAt=" + pattern.matcher(s).lookingAt()
                    + ", find=" + pattern.matcher(s).find());
        }
    }
}
// Вывод:
// 123: matches=true, lookingAt=true, find=true
// 123abc: matches=false, lookingAt=true, find=true
// abc123: matches=false, lookingAt=false, find=true
Строка matches() lookingAt() find()
"123" true true true
"123abc" false true true
"abc123" false false true

Для каждого метода в примере создаётся новый Matcher: find() продолжает поиск с позиции, на которой закончилось предыдущее совпадение, поэтому повторные вызовы на одном и том же объекте без reset() могут дать другой результат.

Ещё один пример: шаблон a*b (любое количество букв a, затем b):

import java.util.regex.Pattern;

public class StarExample {
    public static void main(String[] args) {
        Pattern pattern = Pattern.compile("a*b");
        System.out.println(pattern.matcher("aaab").matches());
        System.out.println(pattern.matcher("b").matches());
        System.out.println(pattern.matcher("aaabc").matches());
        System.out.println(pattern.matcher("aaabc").find());
    }
}
// Вывод:
// true
// true
// false
// true

Строка "b" подходит, потому что * допускает ноль повторений. Строка "aaabc" не проходит matches() из-за лишней буквы c в конце, но find() находит в ней фрагмент aaab.

Синтаксис регулярных выражений: метасимволы

Метасимволы — это символы с особым значением в шаблоне: . ^ $ | ( ) [ ] { } \ ? * +. Все остальные символы совпадают сами с собой. Чтобы найти метасимвол как обычный символ, перед ним ставят обратный слэш (\., \$, \() или помещают его в набор [.].

Метасимвол Значение Пример Совпадает с
. Любой символ, кроме перевода строки (с флагом DOTALL — любой) j.va java, j3va
^ Начало строки (с флагом MULTILINE — начало каждой строки текста) ^Java Java в начале текста
$ Конец строки (с MULTILINE — конец каждой строки текста) \.txt$ .txt в конце текста
| «Или»: альтернатива между подвыражениями cat|dog cat или dog
( ) Группа: объединяет часть шаблона и запоминает совпадение (ab)+ ab, abab
[ ] Набор символов: один любой из перечисленных [0-9] Одна цифра
{ } Точное число повторений \d{4} 2026
? * + Квантификаторы: 0 или 1, 0 и более, 1 и более colou?r color, colour
\ Экранирование метасимвола или начало класса (\d, \s) \$\d+ $100

Классы символов: \d, \s, \w и наборы [ ]

Класс символов совпадает ровно с одним символом из заданного множества. Набор задаётся в квадратных скобках ([abc], диапазон — [a-c], отрицание — [^abc]), а для частых множеств есть готовые сокращения: \d — цифра, \s — пробельный символ, \w — символ слова.

Конструкция Что совпадает В Java-строке Пример: да / нет
[abc] Набор: один из символов a, b или c "[abc]" b / d
[a-c] Диапазон: любой символ от a до c включительно "[a-c]" a / z
[^abc] Любой символ, кроме перечисленных "[^abc]" x / a
[a-zA-Z0-9] Латинская буква любого регистра или цифра "[a-zA-Z0-9]" Q, 7 / _
\d Цифра, то же что [0-9] "\\d" 5 / x
\D Любой символ, кроме цифры "\\D" x / 5
\s Пробельный символ: пробел, табуляция \t, перевод строки \n, \r, \f, \x0B "\\s" пробел / a
\S Любой непробельный символ "\\S" a / пробел
\w Символ слова: латинская буква, цифра или подчёркивание, то же что [a-zA-Z_0-9] "\\w" a, _ / я, -
\W Любой символ, кроме символа слова "\\W" -, я / a
\b Граница слова (не символ, а позиция) "\\bjava\\b" java в «I like java» / в «javascript»
\p{L} Любая буква Юникода, включая кириллицу "\\p{L}" я, a / 5

Сочетание \s+ (один или более пробельных символов подряд) — самый частый шаблон для разбиения текста на слова и схлопывания лишних пробелов; примеры — в разделах split() и Методы String.

Нюанс кириллицы

По умолчанию \w и \b в Java понимают только латиницу: шаблон \w+ не найдёт слово «Привет». Диапазон [а-я] тоже неполный — буква ё лежит в Юникоде вне его, поэтому пишите [а-яё] или используйте \p{L} и флаг UNICODE_CHARACTER_CLASS (пример — в разделе Флаги Pattern).

Квантификаторы: ?, *, +, {n,m}

Квантификатор указывает, сколько раз должен повториться предшествующий ему элемент: символ, класс символов или группа. По умолчанию квантификаторы жадные — захватывают как можно больше текста; с добавленным ? они становятся ленивыми и захватывают как можно меньше.

Квантификатор Сколько раз Пример Совпадает с
a? 0 или 1 раз (необязательный элемент) colou?r color, colour
a* 0 и более раз ab*c ac, abc, abbbc
a+ 1 и более раз ab+c abc, abbbc, но не ac
a{n} Ровно n раз \d{4} 2026
a{n,} n и более раз \w{3,} java, regex, но не go
a{n,m} От n до m раз включительно \d{2,4} 12, 123, 2026
*?, +?, ??, {n,m}? Ленивые варианты: минимально возможное число повторений '.*?' Каждая строка в кавычках отдельно
*+, ++, ?+ Притяжательные (possessive): жадные, но без возврата при неудаче \d++ Все цифры подряд, без отката

Что выведет этот код? Шаблоны отличаются одним знаком вопроса:

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class GreedyLazyExample {
    public static void main(String[] args) {
        String text = "'a' и 'b'";

        Matcher greedy = Pattern.compile("'.*'").matcher(text);
        while (greedy.find()) {
            System.out.println("жадный: " + greedy.group());
        }

        Matcher lazy = Pattern.compile("'.*?'").matcher(text);
        while (lazy.find()) {
            System.out.println("ленивый: " + lazy.group());
        }
    }
}
// Вывод:
// жадный: 'a' и 'b'
// ленивый: 'a'
// ленивый: 'b'

Жадный .* сначала захватывает всё до конца строки, а затем отступает назад до последней кавычки — поэтому получается одно длинное совпадение. Ленивый .*? останавливается на первой подходящей кавычке. Того же результата, что у ленивого варианта, можно добиться набором с отрицанием: '[^']*' — он ещё и работает быстрее, потому что не делает откатов.

Альтернация (OR) и группы

Оператор | означает «или»: шаблон Java|Kotlin|Scala совпадает с любым из трёх слов. Круглые скобки ( ) объединяют часть шаблона в группу: они ограничивают действие | и квантификаторов, а текст, совпавший с группой, можно получить через group(n) или подставить в замену как $n.

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class AlternationExample {
    public static void main(String[] args) {
        Matcher matcher = Pattern.compile("Java|Kotlin|Scala")
                .matcher("Kotlin и Scala работают на JVM, как и Java");
        while (matcher.find()) {
            System.out.println(matcher.group());
        }

        System.out.println(Pattern.compile("^cat|dog$").matcher("hotdog").find());
        System.out.println(Pattern.compile("^(cat|dog)$").matcher("hotdog").find());
    }
}
// Вывод:
// Kotlin
// Scala
// Java
// true
// false

У | самый низкий приоритет: шаблон ^cat|dog$ читается как «cat в начале строки» или «dog в конце строки», поэтому он находит совпадение в "hotdog". Чтобы альтернатива относилась к обоим якорям, её заключают в скобки: ^(cat|dog)$. Если запоминать совпадение не нужно, используйте незахватывающую группу (?:cat|dog).

Группы нумеруются по открывающим скобкам слева направо, начиная с 1; группа 0 — всё совпадение. С Java 7 группам можно давать имена: (?<year>\d{4}).

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class GroupsExample {
    public static void main(String[] args) {
        Pattern date = Pattern.compile("(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})");
        Matcher matcher = date.matcher("Релиз: 2026-10-16");
        if (matcher.find()) {
            System.out.println(matcher.group());
            System.out.println(matcher.group(1));
            System.out.println(matcher.group("day"));
            System.out.println(matcher.groupCount());
        }

        String ru = "2026-10-16".replaceAll("(\\d{4})-(\\d{2})-(\\d{2})", "$3.$2.$1");
        System.out.println(ru);
    }
}
// Вывод:
// 2026-10-16
// 2026
// 16
// 3
// 16.10.2026

Флаги Pattern: регистр, многострочный режим, кириллица

Флаги меняют поведение всего шаблона: поиск без учёта регистра, многострочный режим для ^ и $, поддержку Юникода. Их передают вторым аргументом Pattern.compile(regex, flags), объединяя через |, или включают прямо в шаблоне встроенной записью вроде (?i).

Флаг В шаблоне Что делает
CASE_INSENSITIVE (?i) Поиск без учёта регистра — по умолчанию только для латиницы (US-ASCII)
UNICODE_CASE (?u) Вместе с CASE_INSENSITIVE — регистр по правилам Юникода (кириллица и другие алфавиты)
MULTILINE (?m) ^ и $ совпадают с началом и концом каждой строки текста
DOTALL (?s) Точка совпадает и с переводом строки
UNICODE_CHARACTER_CLASS (?U) \w, \d, \b и другие классы работают по Юникоду (Java 7+); включает UNICODE_CASE
COMMENTS (?x) Пробелы в шаблоне игнорируются, после # — комментарий
LITERAL — Весь шаблон трактуется как обычный текст
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class FlagsExample {
    public static void main(String[] args) {
        System.out.println(Pattern.compile("привет", Pattern.CASE_INSENSITIVE)
                .matcher("ПРИВЕТ").matches());
        System.out.println(Pattern.compile("привет", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE)
                .matcher("ПРИВЕТ").matches());
        System.out.println("JAVA".matches("(?i)java"));

        Matcher ascii = Pattern.compile("\\w+").matcher("Привет, Java");
        while (ascii.find()) {
            System.out.println("без флага: " + ascii.group());
        }
        Matcher unicode = Pattern.compile("\\w+", Pattern.UNICODE_CHARACTER_CLASS).matcher("Привет, Java");
        while (unicode.find()) {
            System.out.println("UNICODE_CHARACTER_CLASS: " + unicode.group());
        }
    }
}
// Вывод:
// false
// true
// true
// без флага: Java
// UNICODE_CHARACTER_CLASS: Привет
// UNICODE_CHARACTER_CLASS: Java

Первая строка вывода — неочевидный момент: флаг CASE_INSENSITIVE сам по себе не сравнивает кириллицу без учёта регистра, для русского текста к нему нужен UNICODE_CASE.

split(): разбиение строки по регулярному выражению

Метод split() разбивает строку на массив подстрок, считая разделителем каждое совпадение с регулярным выражением. Он есть у класса String (str.split(regex)) и у класса Pattern (pattern.split(str)); результат одинаковый, но Pattern удобнее, если один и тот же разделитель используется многократно.

import java.util.Arrays;
import java.util.regex.Pattern;

public class PatternSplitExample {
    public static void main(String[] args) {
        String text = "java5tiger 77 java6mustang";

        Pattern pattern = Pattern.compile("\\d+\\s?");
        System.out.println(Arrays.toString(pattern.split(text)));

        System.out.println(Arrays.toString(text.split("\\s*\\d+\\s*")));
    }
}
// Вывод:
// [java, tiger , java, mustang]
// [java, tiger, java, mustang]

Обратите внимание на "tiger " с пробелом в первой строке вывода: шаблон \d+\s? захватывает пробел только после цифр, а пробел перед 77 остаётся в предыдущем элементе. Шаблон \s*\d+\s* забирает пробелы с обеих сторон.

Разбиение по пробелам через \s+ и несколько неочевидных случаев:

import java.util.Arrays;
import java.util.regex.Pattern;

public class SplitEdgeCases {
    public static void main(String[] args) {
        String words = "  Java   regex\tэто  просто ";
        System.out.println(Arrays.toString(words.split("\\s+")));
        System.out.println(Arrays.toString(words.strip().split("\\s+")));

        String ip = "192.168.0.1";
        System.out.println(ip.split(".").length);
        System.out.println(Arrays.toString(ip.split("\\.")));

        System.out.println(Arrays.toString("a|b|c".split("|")));
        System.out.println(Arrays.toString("a|b|c".split(Pattern.quote("|"))));

        System.out.println(Arrays.toString("a,b,,".split(",")));
        System.out.println(Arrays.toString("a,b,,".split(",", -1)));
    }
}
// Вывод:
// [, Java, regex, это, просто]
// [Java, regex, это, просто]
// 0
// [192, 168, 0, 1]
// [a, |, b, |, c]
// [a, b, c]
// [a, b]
// [a, b, , ]
Вызов Результат Почему
" a b".split("\\s+") Первый элемент — пустая строка Перед ведущими пробелами есть «пустое» слово; уберите их через strip() (Java 11) или trim()
"1.2.3".split(".") Пустой массив Точка — любой символ; все элементы пустые, а хвостовые пустые строки отбрасываются
"a|b".split("|") Разбиение по каждому символу | с пустыми альтернативами совпадает с пустой строкой; нужен "\\|" или Pattern.quote("|")
"a,b,,".split(",") [a, b] Хвостовые пустые строки удаляются; split(",", -1) их сохраняет

Методы String с регулярными выражениями

Класс String принимает регулярные выражения в четырёх методах: matches(), split(), replaceAll() и replaceFirst(). Метод replace() регулярные выражения не использует и заменяет текст буквально — путаница между replace() и replaceAll() встречается постоянно. Остальные методы String разобраны в уроке Java: класс String и его методы.

Метод String Что делает Эквивалент через Pattern
matches(regex) Вся строка соответствует шаблону Pattern.matches(regex, str)
split(regex), split(regex, limit) Разбивает строку по совпадениям Pattern.compile(regex).split(str)
replaceAll(regex, replacement) Заменяет все совпадения Pattern.compile(regex).matcher(str).replaceAll(r)
replaceFirst(regex, replacement) Заменяет первое совпадение ...matcher(str).replaceFirst(r)
replace(target, replacement) Заменяет все вхождения буквально, без regex —
public class StringRegexExample {
    public static void main(String[] args) {
        System.out.println("a.b.c".replace(".", "-"));
        System.out.println("a.b.c".replaceAll(".", "-"));
        System.out.println("a.b.c".replaceAll("\\.", "-"));
        System.out.println("[" + "  Java   regex  ".replaceAll("\\s+", " ") + "]");
        System.out.println("12345".matches("\\d+"));
        System.out.println("Цена: 100".replaceAll("100", "200$"));
    }
}
// Вывод:
// a-b-c
// -----
// a-b-c
// [ Java regex ]
// true
// Exception in thread "main" java.lang.IllegalArgumentException: Illegal group reference: group index is missing

В строке замены у replaceAll() символ $ обозначает ссылку на группу, а \ — экранирование, поэтому "200$" приводит к исключению. Если замена содержит произвольный текст, пропустите её через Matcher.quoteReplacement("200$") — тогда результат будет Цена: 200$.

Методы String.matches() и replaceAll() компилируют шаблон при каждом вызове. В цикле по тысячам строк вынесите Pattern в поле private static final и используйте pattern.matcher(s).

Примеры: проверка домена и поиск email

На практике регулярные выражения чаще всего используют для двух задач: проверить, что строка целиком имеет нужный формат (matches()), и найти в тексте все фрагменты нужного вида (find() в цикле). Ниже — по примеру на каждую задачу.

Проверка доменного имени

import java.util.regex.Pattern;

public class DomainExample {
    private static final Pattern DOMAIN = Pattern.compile(".+\\.(com|ua|ru)");

    public static void main(String[] args) {
        System.out.println(test("google.com"));
        System.out.println(test("reference1.ua"));
        System.out.println(test("reference1.org"));
    }

    public static boolean test(String testString) {
        return DOMAIN.matcher(testString).matches();
    }
}
// Вывод:
// true
// true
// false

Шаблон требует, чтобы строка заканчивалась точкой и одной из трёх зон, перечисленных через |. Это учебная проверка: .+ пропустит и пробелы, например "a b.com". Для настоящих URL надёжнее разбирать строку классом java.net.URI.

Поиск email в тексте

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class EmailExample {
    private static final Pattern EMAIL = Pattern.compile("([\\w.+-]+)@([\\w.-]+\\.[a-z]{2,})");

    public static void main(String[] args) {
        String text = "Адреса эл. почты: [email protected], [email protected]!";
        Matcher matcher = EMAIL.matcher(text);
        while (matcher.find()) {
            System.out.println("e-mail: " + matcher.group()
                    + ", логин: " + matcher.group(1)
                    + ", домен: " + matcher.group(2));
        }
    }
}
// Вывод:
// e-mail: [email protected], логин: ivanov, домен: gmail.com
// e-mail: [email protected], логин: sidorov, домен: bsu.by

Группа 1 захватывает логин (буквы, цифры, _ . + -), группа 2 — домен с зоной из двух и более латинских букв. Полная проверка email по стандарту RFC 5322 регулярным выражением практически нечитаема, поэтому в реальных проектах regex используют как первичный фильтр, а адрес подтверждают письмом.

Типичные промахи при работе с regex

Большинство ошибок с регулярными выражениями в Java не ловится компилятором: шаблон — это строка, и проблема проявляется только при выполнении — исключением, неверным результатом или резким падением скорости. Частые случаи и исправления:

Код Что произойдёт Как исправить
Pattern.compile("\d+") Ошибка компиляции: illegal escape character Удвоить слэш: "\\d+"
Pattern.compile("[a-z") PatternSyntaxException: Unclosed character class near index 3 Закрыть скобку: "[a-z]"
"abc123".matches("\\d+") false, хотя цифры в строке есть Для поиска внутри строки — matcher.find()
matcher.group() без вызова find() IllegalStateException: No match found Сначала if (matcher.find()) или matches()
"1.2.3".split(".") Пустой массив split("\\.") или Pattern.quote(".")
replaceAll("100", "200$") IllegalArgumentException: Illegal group reference Matcher.quoteReplacement("200$")
Вложенные квантификаторы вроде "(a+)+b" Риск катастрофического отката (ReDoS): на длинной строке без совпадения число вариантов перебора растёт экспоненциально Не вкладывать квантификаторы друг в друга ("a+b"), использовать притяжательные ++
s.matches(regex) в цикле Шаблон компилируется на каждой итерации private static final Pattern + matcher(s)

Часто задаваемые вопросы

Потокобезопасны ли Pattern и Matcher?

Pattern неизменяем и потокобезопасен: один объект в поле static final можно использовать из любого количества потоков. Matcher хранит состояние поиска и потокобезопасным не является — каждый поток должен создавать свой через pattern.matcher(text).

Как проверить, что строка содержит только цифры?

str.matches("[0-9]+") вернёт true только для непустой строки из цифр; для пустой строки результат false, а шаблон [0-9]* пропустил бы и её. Если строку потом нужно превратить в число, учтите, что Integer.parseInt() бросит NumberFormatException для слишком длинных чисел, даже если regex их пропустил.

Как одним регулярным выражением проверить сложность пароля?

С помощью опережающих проверок (lookahead) (?=...): они проверяют условие, не сдвигая позицию. password.matches("(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z]).{8,}") требует минимум 8 символов, хотя бы одну цифру, одну строчную и одну заглавную латинскую букву. Есть и ретроспективные проверки: (?<=...) и (?<!...).

Можно ли в Java не удваивать обратный слэш в шаблоне?

Нет. Сырые строковые литералы (JEP 326) планировались в Java 12, но были отозваны. Текстовые блоки из Java 15 тоже обрабатывают escape-последовательности, поэтому в них слэш удваивается так же. Уменьшить их число помогают наборы ([.] вместо \\.) и Pattern.quote().

Практика по теме

Закрепите урок на задачах — у каждой есть уровень и оценка времени.

Все задачи модуля →

Видео объяснение

Предпочитаете видеоформат? Посмотрите этот урок с примерами и объяснениями.

Комментарии

Зарегистрируйтесь или войдите, чтобы иметь возможность оставить комментарий.