Обработка строк в Java. Часть II: Pattern, Matcher
Что Вы знаете о обработке строк в Java? Как много этих знаний и насколько они углублены и актуальны? Давайте попробуем вместе со мной разобрать все вопросы, связанные с этой важной, фундаментальной и часто используемой частью языка. Наш маленький гайд будет разбит на две публикации:
- String, StringBuffer, StringBuilder (реализация строк)
- Pattern, Matcher (регулярные выражения)
Регулярные выражения
Регулярные выражения (regular expressions, далее РВ) — мощное и эффективное средство для обработки текста. Они впервые были использованы в текстовых редакторах операционной системы UNIX (ed и QED) и сделали прорыв в электронной обработке текстов конца XX века. В 1987 году более сложные РВ возникли в первой версии языка Perl и были основаны на пакете Henry Spencer (1986), написанном на языке С. А в 1997 году, Philip Hazel разработал Perl Compatible Regular Expressions (PCRE) — библиотеку, что точно наследует функциональность РВ в Perl. Сейчас PCRE используется многими современными инструментами, например Apache HTTP Server.

Большинство современных языков программирования поддерживают РВ, Java не является исключением.
Механизм
Существует две базовые технологии, на основе которых строятся механизмы РВ:
- Недетерминированный конечный автомат (НКА) — «механизм, управляемый регулярным выражением»
- Детерминированный конечный автомат (ДКА) — «механизм, управляемый текстом»
ДКА — механизм, который анализирует строку и следит за всеми «возможными совпадениями». Его работа зависит от каждого просканированного символа текста (то есть ДКА «управляется текстом»). Даний механизм сканирует символ текста, обновляет «потенциальное совпадение» и резервирует его. Если следующий символ аннулирует «потенциальное совпадение», то ДКА возвращается к резерву. Нет резерва — нет совпадений.
Логично, что ДКА должен работать быстрее чем НКА (ДКА проверяет каждый символ текста не более одного раза, НКА — сколько угодно раз пока не закончит разбор РВ). Но НКА предоставляет возможность определять ход дальнейших событий. Мы можем в значительной степени управлять процессом за счет правильного написания РВ.
Регулярные выражения в Java используют механизм НКА.
Эти виды конечных автоматов более детально рассмотрены в статье «Регулярные выражения изнутри».
Подход к обработке
В языках программирования существует три подхода к обработке РВ:
- интегрированный
- процедурный
- объектно-ориентированный
Для обработки регулярных выражений в Java используют объектно-ориентированный подход.
Реализация
Для работы с регулярными выражениями в Java представлен пакет java.util.regex. Пакет был добавлен в версии 1.4 и уже тогда содержал мощный и современный прикладной интерфейс для работы с регулярными выражениями. Обеспечивает хорошую гибкость из-за использования объектов, реализующих интерефейс CharSequence.
Все функциональные возможности представлены двумя классами, интерфейсом и исключением:

Pattern
Класс Pattern представляет собой скомпилированное представление РВ. Класс не имеет публичных конструкторов, поэтому для создания объекта данного класса необходимо вызвать статический метод compile и передать в качестве первого аргумента строку с РВ:
// XML тэг в формате Pattern pattern = Pattern.compile("^<([a-z]+)([^>]+)*(?:>(.*)|\\s+\\/>)$");
Также в качестве второго параметра в метод compile можно передать флаг в виде статической константы класса Pattern, например:
// email адрес в формате xxx@xxx.xxx (регистр букв игнорируется) Pattern pattern = Pattern.compile("^([a-z0-9_\\.-]+)@([a-z0-9_\\.-]+)\\.([a-z\\.])$", Pattern.CASE_INSENSITIVE);
Таблица всех доступных констант и эквивалентных им флагов:
| № | Constant | Equivalent Embedded Flag Expression |
|---|---|---|
| 1 | Pattern.CANON_EQ | — |
| 2 | Pattern.CASE_INSENSITIVE | (?i) |
| 3 | Pattern.COMMENTS | (?x) |
| 4 | Pattern.MULTILINE | (?m) |
| 5 | Pattern.DOTALL | (?s) |
| 6 | Pattern.LITERAL | — |
| 7 | Pattern.UNICODE_CASE | (?u) |
| 8 | Pattern.UNIX_LINES | (?d) |
Иногда нам необходимо просто проверить есть ли в строке подстрока, что удовлетворяет заданному РВ. Для этого используют статический метод matches, например:
// это hex код цвета? if (Pattern.matches("^#?([a-f0-9]|[a-f0-9])$", "#8b2323")) < // вернет true // делаем что-то >
Также иногда возникает необходимость разбить строку на массив подстрок используя РВ. В этом нам поможет метод split:
Pattern pattern = Pattern.compile(":|;"); String[] animals = pattern.split("cat:dog;bird:cow"); Arrays.asList(animals).forEach(animal -> System.out.print(animal + " ")); // cat dog bird cow
Matcher и MatchResult
Matcher — класс, который представляет строку, реализует механизм согласования (matching) с РВ и хранит результаты этого согласования (используя реализацию методов интерфейса MatchResult). Не имеет публичных конструкторов, поэтому для создания объекта этого класса нужно использовать метод matcher класса Pattern:
// будем искать URL String regexp = "^(https?:\\/\\/)?([\\da-z\\.-]+)\\.([a-z\\.])([\\/\\w \\.-]*)*\\/?$"; String url = "http://habrahabr.ru/post/260767/"; Pattern pattern = Pattern.compile(regexp); Matcher matcher = pattern.matcher(url);
Но результатов у нас еще нет. Чтобы их получить нужно воспользоваться методом find. Можно использовать matches — этот метод вернет true только тогда, когда вся строка соответствует заданному РВ, в отличии от find, который пытается найти подстроку, которая удовлетворяет РВ. Для более детальной информации о результатах согласования можно использовать реализацию методов интерфейса MatchResult, например:
// IP адрес String regexp = "(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.)(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"; // для сравнения работы find() и matches() String goodIp = "192.168.0.3"; String badIp = "192.168.0.3g"; Pattern pattern = Pattern.compile(regexp); Matcher matcher = pattern.matcher(goodIp); // matches() - true, find() - true matcher = pattern.matcher(badIp); // matches() - false, find() - true // а теперь получим дополнительную информацию System.out.println(matcher.find() ? "I found '"+matcher.group()+"' starting at index "+matcher.start()+" and ending at index "+matcher.end()+"." : "I found nothing!"); // I found the text '192.168.0.3' starting at index 0 and ending at index 11.
Также можно начинать поиск с нужной позиции используя find(int start). Стоит отметить что существует еще один способ поиска — метод lookingAt. Он начинает проверку совпадений РВ с начала строки, но не требует полного соответствия, в отличии от matches.
Класс предоставляет методы для замены текста в указанной строке:
| appendReplacement(StringBuffer sb, String replacement) | Реализует механизм «добавление-и-замена» (append-and-replace). Формирует обьект StringBuffer (получен как параметр) добавляя replacement в нужные места. Устанавливает позицию, которая соответствует end() последнего результата поиска. После этой позиции ничего не добавляет. |
| appendTail(StringBuffer sb) | Используется после одного или нескольких вызовов appendReplacement и служит для добавления оставшейся части строки в объект класса StringBuffer, полученного как параметр. |
| replaceFirst(String replacement) | Заменяет первую последовательность, которая соответствует РВ, на replacement. Использует вызовы методов appendReplacement и appendTail. |
| replaceAll(String replacement) | Заменяет каждую последовательность, которая соответствует РВ, на replacement. Также использует методы appendReplacement и appendTail. |
| quoteReplacement(String s) | Возвращает строку, в которой коса черта (‘ \ ‘) и знак доллара (‘ $ ‘) будут лишены особого смысла. |
Pattern pattern = Pattern.compile("a*b"); Matcher matcher = pattern.matcher("aabtextaabtextabtextb the end"); StringBuffer buffer = new StringBuffer(); while (matcher.find()) < matcher.appendReplacement(buffer, "-"); // buffer = "-" ->"-text-" -> "-text-text-" -> "-text-text-text-" > matcher.appendTail(buffer); // buffer = "-text-text-text- the end"
PatternSyntaxException
Неконтролируемое (unchecked) исключение, возникает при синтаксической ошибке в регулярном выражении. В таблице ниже приведены все методы и их описание.
| getDescription() | Возвращает описание ошибки. |
| getIndex() | Возвращает индекс строки, где была найдена ошибка в РВ |
| getPattern() | Возвращает ошибочное РВ. |
| getMessage() | getDescription() + getIndex() + getPattern() |
Matcher find java как работает
Кривой regex.. скопировал и попробовал на java17 — не ищет однострочный(//) комментарий, не учитывает перенос строк в многострочном комментарии (/*. */) Моя версия рабочая. Проверил на этом java-файле
(?s) — включает многострочный режим только для 3-ей группы И внутри третьей группы:
обязательно подчеркнутую часть нужно сделать ленивой, иначе загребать будет жадно от первого найденного /* по самый последний */. https://regex101.com/ — здесь можно посмотреть эффект жадности если убрать знак «?» из regex’а и вставить код из прикрепленного файла.
Евгения Уровень 47
19 февраля 2022
Помогите, пожалуйста, с последним примером. Я его скопировала, вставила в Идею. Далее сказано: Выполните приложение с файлом ExtCmnt.java в качестве входного: java ExtCmnt ExtCmnt.java out Если эту строчку вставить в параметры, ничего не работает. Я уже пыталась и полный путь к файлу прописать — ничего не получается. Подскажите, как можно это сделать.
Макс Дудин Уровень 41
18 мая 2021
Вот это прикольно. особенно последнее
Дмитрий Уровень 24
11 октября 2020
Следующий пример демонстрирует использование методов groupCount() и group(int group): Pattern p = Pattern.compile("(.(.(.)))"); Matcher m = p.matcher("abc"); m.find(); System.out.println(m.groupCount()); for (int i = 0; i
В вашем шаблоне 3 группs захвата, поэтому .groupCount() возвращает 3. Обратите внимание, что это не возвращает, сколько совпадений было найдено.
Ivan D Уровень 35
2 сентября 2020
Прям кайфую от чтения красивого кода с объяснениями.
Тимофей Уровень 26
15 июля 2020
Не работает, если реально есть в файле многострочный комментарий. Вот это регулярное выражение - ".*/\\*.*\\*/|.*//.*$"
Matcher find java как работает

Комментарии
Популярные По порядку
Не удалось загрузить комментарии.
ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ
ТОП-6 алгоритмов сортировки на Java для новичков
Изучение алгоритмов сортировки на языке Java поможет не изобретать велосипеды и быстро выскочить на лесенку карьерного роста.
ТОП-10 лучших книг по Java для программистов
Не имеет значения, хотите вы улучшить скилл или только собираетесь начать изучение, здесь вы найдете лучшие книги по Java для программистов.
6 книг по Java для программистов любого уровня
Подборка материалов по Java. Если вы изучаете его, то обязательно найдете для себя что-то полезное и неважно на какой стадии изучения вы находитесь.
Java Matcher find is false but matches is true [закрыт]
Вопрос вызван проблемой, которая больше не воспроизводится, или опечаткой. Хотя похожие вопросы могут быть уместны на этом сайте, решение для этого вопроса вряд ли поможет будущим посетителям. Обычно можно избежать подобных вопросов написанием и исследованием минимальной программы для воспроизведения проблемы до публикации вопроса.
Закрыт 1 год назад .
Я попытался разобрать строку с помощью регулярного выражения, но столкнулся с каким-то странным поведением
System.out.println(m.matches()); //returns true System.out.println(m.find()); //returns false
Похоже, у меня есть несколько ошибок в моем регулярном выражении. Весь код приведен ниже
import java.util.regex.Matcher; import java.util.regex.Pattern; public class testRegexp < public static void main(String[] args) < String string = "Alexandr TheMakedonian 32"; Pattern pattern = Pattern.compile("(.*)\\s(.*)\\s(.*)"); Matcher m = pattern.matcher(string); System.out.println(m.matches()); System.out.println(m.find()); System.out.println(m.groupCount()); if( m.find() && m.groupCount() >= 3) < String firstName = m.group(1); String secondName = m.group(2); String ages = m.group(3); System.out.println(firstName); System.out.println(secondName); System.out.println(ages); >> >
Отслеживать
Serg Sergy
задан 20 июн 2022 в 15:29
Serg Sergy Serg Sergy
9 3 3 бронзовых знака
Please translate your question to the Russian language, because this segment of SO is for the Russian-speaking people
20 июн 2022 в 15:31
1 ответ 1
Сортировка: Сброс на вариант по умолчанию
Вызов String.matches() сравнивает целую строку с шаблоном и после этого позиция для нового поиска перемещается в конец и соответственно последующий вызов find вернёт false , так как будет сравниваться пустая строка с шаблоном, в котором есть как минимум два пробела.
Если к примеру переставить вызовы данных методов местами или "сбросить" поиск при помощи Matcher::reset , оба вызова вернут true :
System.out.println(m.find()); // true System.out.println(m.matches()); // true Matcher m1 = pattern.matcher(string); System.out.println(m1.matches()); // true m1.reset(); System.out.println(m1.find()); // true