Упрощаем решение распространенных задач программирования при помощи API Regex
В первой и второй частях этой статьи вы познакомились с регулярными выражениями и API Regex. Вы узнали о существовании классаPattern
, прошлись по примерам, демонстрирующим конструкции регулярных выражений, от простейшего поиска по шаблону на основе литеральных строк до более сложного поиска при помощи диапазонов, граничных сопоставителей и квантификаторов.
В этой и последующих частях мы рассмотрим не охваченные в первой части вопросы, изучим соответствующие методы классов Pattern
, Matcher
и PatternSyntaxException
. Вы также познакомитесь с двумя утилитами, использующими регулярные выражения для упрощения решения распространенных задач программирования. Первая из них извлекает комментарии из кода для документации. Вторая представляет собой библиотеку многоразового кода, предназначенную для выполнения лексического анализа — существенный компонент ассемблеров, компиляторов и тому подобного программного обеспечения.
ЗАГРУЗКА ИСХОДНОГО КОДА
Получить весь исходный код (созданный Джеффом Фризеном для сайта JavaWorld) демо-приложений из данной статьи можно отсюда.Изучаем API Regex
Pattern
, Matcher
и PatternSyntaxException
– три класса, составляющих API Regex. Каждый из них предоставляет методы, позволяющие использовать регулярные выражения в вашем коде.
Методы класса Pattern
Экземпляр классаPattern
представляет собой скомпилированное регулярное выражение, известное также как шаблон. Регулярные выражения компилируются с целью повышения производительности операций поиска по шаблону. Следующие статические методы поддерживают компиляцию.
Pattern compile(String regex)
компилирует содержимоеregex
в промежуточное представление, сохраняемое в новом объектеPattern
. Этот метод или возвращает ссылку на объект, в случае успешного выполнения, или генерирует исключениеPatternSyntaxException
в случае обнаружения некорректного синтаксиса регулярного выражения. Любой объект классаMatcher
, используемый этим объектомPattern
или возвращаемый из него, использует его настройки по умолчанию, например, поиск с учетом регистра. В качестве примера, фрагмент кодаPattern p = Pattern.compile("(?m)^\\.");
создает объектPattern
, хранящий скомпилированное представление регулярного выражения для поиска строк, начинающихся с символа точки.Pattern compile(String regex, int flags)
решает ту же задачу, что иPattern compile(String regex)
, но с учетомflags
: набора битовых констант для побитовых флагов типа ИЛИ. В классеPattern
объявлены константыCANON_EQ, CASE_INSENSITIVE, COMMENTS, DOTALL, LITERAL, MULTILINE, UNICODE_CASE, UNICODE_CHARACTER_CLASS и UNIX_LINES
, которые можно комбинировать при помощи побитового ИЛИ (например,CASE_INSENSITIVE | DOTALL
) и передать в аргументеflags
.
За исключением
CANON_EQ, LITERAL и UNICODE_CHARACTER_CLASS
, эти константы являются альтернативой вложенных флаговым выражениям, продемонстрированным в части 1. При обнаружении флаговой константы, отличающейся от определенных в классе Pattern
, метод Pattern compile(String regex, int flags)
генерирует исключение java.lang.IllegalArgumentException
. Например, Pattern p = Pattern.compile("^\\.", Pattern.MULTILINE);
эквивалентно предыдущему примеру, причем константа Pattern.MULTILINE
и вложенное флаговое выражение (?m)
делают одно и то же.
Pattern
, вместе с используемыми им флагами. Для этого можно вызвать следующие методы:
String pattern()
возвращает исходную строку регулярного выражения, скомпилированную в объектPattern
.int flags()
возвращает флаги объектаPattern
.
Pattern
, он обычно используется для получения объекта Matcher
, для выполнения операций поиска по шаблону. Метод Matcher matcher(Charsequence input)
создает объект Matcher
, ищущий в тексте input
соответствие шаблону объекта Pattern
. При вызове он возвращает ссылку на этот объект Matcher
. Например, команда Matcher m = p.matcher(args[1]);
возвращает Matcher
для объекта Pattern
, на который ссылается переменная p
.
Одноразовый поиск |
---|
Метод static boolean matches(String regex, CharSequence input) класса Pattern позволяет сэкономить на создании объектов Pattern и Matcher при одноразовом поиске по шаблону. Этот метод возвращает true, если в input находится соответствие шаблону regex , в противном случае он возвращает false. Если в регулярном выражении содержится синтаксическая ошибка, метод генерирует исключение PatternSyntaxException . Например, System.out.println(Pattern.matches("[a-z[\\s]]*", "all lowercase letters and whitespace only")); выводит true , подтверждая, что во фразе all lowercase letters and whitespace only содержатся только пробелы и символы в нижнем регистре.
|
Разбиение текста
Большинству разработчиков приходилось хоть раз писать код для разбиения входного текста на составные части, например, преобразовывать текстовую учетную запись сотрудника в набор полей. КлассPattern
предоставляет возможность более удобного решения этой утомительной задачи, при помощи двух методов разбиения текста:
Метод
String[] split(CharSequence text, int limit)
разбиваетtext
в соответствии с найденными соответствиями шаблону объектаPattern
и возвращает результаты в массиве. Каждый элемент массива задает текстовую последовательность, отделенную от следующей последовательности соответствующим шаблону фрагментом текста (или концом текста). Элементы массива находятся в том же порядке, в котором они встречаются вtext
.В этом методе, количество элементов массива зависит от параметра
limit
, контролирующего также и число искомых соответствий.- При положительном значении выполняется поиск не более чем
limit-1
соответствий, а длина массива не превышаетlimit
элементов. - При отрицательном значении выполняется поиск всех возможных соответствий, и длина массива может быть произвольной.
- При равном нулю значении выполняется поиск всех возможных соответствий, длина массива может быть произвольной, а пустые строки в конце отбрасываются.
- При положительном значении выполняется поиск не более чем
- Метод
String[] split(CharSequence text)
вызывает предыдущий метод с 0 в качестве аргумента limit и возвращает результат его вызова.
split(CharSequence text)
по решению задачи расщепления учетной записи сотрудника на отдельные поля имени, возраста, почтового адреса и зарплаты:
Pattern p = Pattern.compile(",\\s");
String[] fields = p.split("John Doe, 47, Hillsboro Road, 32000");
for (int i = 0; i < fields.length; i++)
System.out.println(fields[i]);
В вышеприведенном коде описано регулярное выражение для поиска знака запятой, за которым непосредственно следует одиночный символ пробела. Вот результаты его выполнения:
John Doe
47
Hillsboro Road
32000
Предикаты шаблонов и API Streams
В Java 8 в классеPattern
появился метод Predicate asPredicate()
. Этот метод создает предикат (функцию с булевым значением), используемый для поиска по шаблону. Использование этого метода показано в следующем фрагменте кода:
List progLangs = Arrays.asList("apl", "basic", "c", "c++", "c#", "cobol", "java", "javascript", "perl", "python", "scala");
Pattern p = Pattern.compile("^c");
progLangs.stream().filter(p.asPredicate()).forEach(System.out::println);
Этот код создает список названий языков программирования, затем компилирует шаблон для поиска всех названий, начинающихся с буквы c
. Последняя из вышеприведенных строк кода реализует получение последовательного потока данных с этим списком в качестве источника. Он устанавливает фильтр, использующий булеву функцию asPredicate()
, которая возвращает true, когда название начинается с буквы c
и выполняет итерацию по потоку, выводя подходящие названия в стандартный поток вывода.
Эта последняя строка эквивалентна следующему обычному циклу, знакомому вам по приложению RegexDemo из части 1:
for (String progLang: progLangs)
if (p.matcher(progLang).find())
System.out.println(progLang);
Методы класса Matcher
Экземпляр классаMatcher
описывает механизм выполнения операций поиска по шаблону в последовательности символов путем интерпретации скомпилированного регулярного выражения класса Pattern
. Объекты класса Matcher
поддерживают различные виды операций поиска по шаблону:
Метод
boolean find()
ищет во входном тексте следующее совпадение. Этот метод начинает просмотр или в начале заданного текста, или на первом символе после предыдущего совпадения. Второй вариант возможен только если предыдущий вызов этого метода вернул true и сопоставитель не был сброшен. В любом случае, в случае успешного поиска возвращается булево значение true. Пример этого метода вы можете найти вRegexDemo
из части 1.Метод
boolean find(int start)
сбрасывает сопоставитель и ищет в тексте следующее совпадение. Просмотр начинается с позиции, задаваемой параметромstart
. В случае успешного поиска возвращается булево значение true. Например,m.find(1);
просматривает текст, начиная с позиции1
(позиция 0 игнорируется). Если параметрstart
содержит отрицательное значение или значение, превышающее длину текста сопоставителя, метод генерирует исключениеjava.lang.IndexOutOfBoundsException
.Метод
boolean matches()
пытается сопоставить с шаблоном весь текст. Он возвращает булево значение true, если весь текст соответствует шаблону. Например, кодPattern p = Pattern.compile("\\w*"); Matcher m = p.matcher("abc!"); System.out.println(p.matches());
выводитfalse
, поскольку символ!
не является словообразующим символом.Метод
boolean lookingAt()
пытается сопоставить с шаблоном заданный текст. Этот метод возвращает true, если любая часть текста соответствует шаблону. В отличие от методаmatches();
, весь текст не должен соответствовать шаблону. Например,Pattern p = Pattern.compile("\\w*"); Matcher m = p.matcher("abc!"); System.out.println(p.lookingAt());
выведетtrue
, поскольку начало текстаabc!
состоит только из словообразующих символов.
Pattern
, объекты класса Matcher
сохраняют информацию о состоянии. Иногда может понадобиться сбросить сопоставитель, чтобы очистить эту информацию после окончания поиска по шаблону. Для сброса сопоставителя существуют следующие методы:
Метод
Matcher reset()
сбрасывает состояние сопоставителя, включая позицию для добавления в конец (сбрасываемую в 0). Следующая операция поиска по шаблону начинается в начале текста сопоставителя. Возвращается ссылка на текущий объектMatcher
. Например,m.reset();
сбрасывает сопоставитель, на который ссылаетсяm
.Метод
Matcher reset(CharSequence text)
сбрасывает состояние сопоставителя и задает новый текст сопоставителя, равныйtext
. Следующая операция поиска по шаблону начинается в начале нового текста сопоставителя. Возвращается ссылка на текущий объектMatcher
. Например,m.reset("new text");
сбрасывает сопоставитель, на который ссылаетсяm
и задает в качестве нового текста сопоставителя значение"new text"
.
Добавление текста в конец
Позиция сопоставителя для добавления в конец задает начало текста сопоставителя, добавляемого в конец объекта типаjava.lang.StringBuffer
. Эту позицию используют следующие методы:
Метод
Matcher appendReplacement(StringBuffer sb, String replacement)
читает символы текста сопоставителя и присоединяет их в конец объектаStringBuffer
, на который ссылается аргументsb
. Этот метод прекращает чтение на последнем символе, предшествующем предыдущему соответствию шаблону. Далее, метод добавляет символы из объекта типаString
, на который ссылается аргументreplacement
, в конец объектаStringBuffer
(строкаreplacement
может содержать ссылки на текстовые последовательности, захваченные во время предыдущего поиска; они указываются при помощи символов($)
и номеров захватываемых групп). Наконец, метод устанавливает значение позиции сопоставителя для добавления в конец равным позиции последнего совпавшего символа плюс единица, после чего возвращает ссылку на текущий сопоставитель.Метод
StringBuffer appendTail(StringBuffer sb)
добавляет весь текст в объектStringBuffer
и возвращает ссылку на этот объект. После последнего вызова методаappendReplacement(StringBuffer sb, String replacement)
, вызовите методappendTail(StringBuffer sb)
, чтобы скопировать оставшийся текст в объектStringBuffer
.
Метод Matcher appendReplacement(StringBuffer sb, String replacement)
генерирует исключение java.lang.IllegalStateException
, если сопоставитель еще не находил соответствия или предыдущая попытка поиска завершилась неудачно. Он генерирует исключение IndexOutOfBoundsException
, если строка replacement
задает отсутствующую в шаблоне захватываемую группу).
Захватываемые группы |
---|
Как вы помните из части 1, захватываемая группа — это последовательность символов, заключенная в метасимволы круглых скобок (() ). Цель этой конструкции состоит в сохранении найденных символов для дальнейшего повторного использования во время поиска по шаблону. Все символы из захватываемой группы рассматриваются во время поиска по шаблону как единое целое.
|
appendReplacement(StringBuffer sb, String replacement)
и appendTail(StringBuffer sb
для замены в исходном тексте всех вхождений последовательности символов cat
на caterpillar
:
Pattern p = Pattern.compile("(cat)");
Matcher m = p.matcher("one cat, two cats, or three cats on a fence");
StringBuffer sb = new StringBuffer();
while (m.find())
m.appendReplacement(sb, "$1erpillar");
m.appendTail(sb);
System.out.println(sb);
Использование захватываемой группы и ссылки на неё в замещающем тексте указывает программе вставлять erpillar
после каждого вхождения cat
. Результат выполнения данного кода выглядит следующим образом:
one caterpillar, two caterpillars, or three caterpillars on a fence
Замена текста
КлассMatcher
предоставляет нам два метода для текстовой замены, дополняющих метод appendReplacement(StringBuffer sb, String replacement)
. С помощью этих методов можно заменять или первое вхождение [замещаемого текста] или все вхождения:
Метод
String replaceFirst(String replacement)
сбрасывает сопоставитель, создает новый объектString
, копирует в эту строку все символы текста сопоставителя (вплоть до первого совпадения), добавляет в её конец символы изreplacement
, копирует в строку оставшиеся символы и возвращает объектString
(в строкеreplacement
можно указывать ссылки на захваченные во время предыдущего поиска текстовые последовательности, при помощи символов доллара и номеров захватываемых групп).Метод
String replaceAll(String replacement)
действует аналогично методуString replaceFirst(String replacement)
, но заменяет символами из строкиreplacement
всё найденные совпадения.
\s+
служит для поиска одного или более пробельных символов во входном тексте. Ниже, мы воспользуемся этим регулярным выражением и вызовем метод replaceAll(String replacement)
для удаления дублирующихся пробелов:
Pattern p = Pattern.compile("\\s+");
Matcher m = p.matcher("Удаляем \t\t лишние пробелы. ");
System.out.println(m.replaceAll(" "));
Вот результаты:
Удаляем лишние пробелы.
Регулярные выражения в Java, часть 4
Регулярные выражения в Java, часть 5
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ