Предлагаем вашему вниманию перевод краткого руководства по регулярным выражениям в языке Java, написанного Джеффом Фрисеном (Jeff Friesen) для сайта JavaWorld. Для простоты чтения мы разделили статью на несколько частей.

Использование API регулярных выражений в программах на языке Java для распознания и описания шаблонов

Символьный и разнообразные строковые типы данных языка Java обеспечивают низкоуровневую поддержку поиска по шаблону, но их использование в этих целях обычно сильно усложняет код. Более простой и производительный код получается при использовании API Regex ("API регулярных выражений"). Данная инструкция поможет вам приступить к работе с регулярными выражениями и API Regex. Сначала мы обсудим в общем три наиболее интересных класса из пакета java.util.regex, а затем заглянем в класс Pattern и изучим его изощренные конструкции поиска по шаблонам. Внимание: загрузить исходный код (созданный Джеффом Фризеном для сайта JavaWorld) демо-приложения из данной статьи можно отсюда.

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

Регулярное выражение (regular expression/regex/regexp) — это строка, которая является шаблоном (pattern), описывающим некий набор строк. Шаблон определяет, какие именно строки относятся к набору. Шаблон состоит из литералов и метасимволов — символов со специальным, а не буквальным значением. Поиск по шаблону –— это поиск по тексту для нахождения совпадений, то есть строк, соответствующих шаблону регулярного выражения. Java поддерживает поиск по шаблону посредством своего API Regex. Этот API состоит из трех классов: Pattern, Matcher и PatternSyntaxException, расположенных в пакете java.util.regex:
  • oбъекты класса Pattern, называемые также шаблонами, представляют собой скомпилированные регулярные выражения.
  • oбъекты класса Matcher, или сопоставители, представляют собой механизмы интерпретации шаблонов для обнаружения совпадений в символьных последовательностях (объектах, чьи классы реализуют интерфейс java.lang.CharSequence и служат источниками текста).
  • oбъекты класса PatternSyntaxException служат для описания недопустимых шаблонов регулярных выражений.
Java также предоставляет поддержку поиска по шаблону посредством различных методов класса java.lang.String. Например, функция boolean matches (String regex) возвращает true только тогда, когда вызывающая строка в точности соответствует регулярному выражению regex.
Удобные методы
matches() и другие ориентированные на регулярные выражения удобные методы класса String "под капотом" реализованы аналогично API Regex.

RegexDemo

Я создал приложение RegexDemo для демонстрации регулярных выражений языка Java и различных методов классов Pattern, Matcher и PatternSyntaxException. Ниже — исходный код этого демо-приложения. Листинг 1. Демонстрация регулярных выражений
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
public class RegexDemo
{
   public static void main(String[] args)
   {
      if (args.length != 2)
      {
         System.err.println("usage: java RegexDemo regex input");
         return;
      }
      // Преобразуем символьные последовательности начала новой строки (\n) в символы начала строки.
      args[1] = args[1].replaceAll("\\\\n", "\n");
      try
      {
         System.out.println("regex = " + args[0]);
         System.out.println("input = " + args[1]);
         Pattern p = Pattern.compile(args[0]);
         Matcher m = p.matcher(args[1]);
         while (m.find())
            System.out.println("Found [" + m.group() + "] starting at "
                               + m.start() + " and ending at " + (m.end() - 1));
      }
      catch (PatternSyntaxException pse)
      {
         System.err.println("Неправильное регулярное выражение: " + pse.getMessage());
         System.err.println("Описание: " + pse.getDescription());
         System.err.println("Позиция: " + pse.getIndex());
         System.err.println("Неправильный шаблон: " + pse.getPattern());
      }
   }
}
Первое, что делает метод main класса RegexDemo — проверяет свою командную строку. Он требует двух аргументов: первый — регулярное выражение, а второй — входной текст, в котором будет выполняться поиск по этому регулярному выражению. Возможно, вам понадобится использовать внутри входного текста символ новой строки (\n). Сделать это можно только указав символ \, за которым следует символ n. Функция main() преобразует эту символьную последовательность в Unicode-значение 10.
Основная часть кода RegexDemo заключена в конструкцию try-catch. Блок try сначала выводит заданное регулярное выражение и входной текст, после чего создает объект Pattern, сохраняющий скомпилированное регулярное выражение (регулярные выражения компилируются ради улучшения производительности поиска по шаблону). Сопоставитель извлекается из объекта Pattern и используется для многократного поиска совпадений, до тех пор, пока не будут найдены все. Блок catch вызывает несколько методов класса PatternSyntaxException для извлечения полезной информации об исключении. Эта информация последовательно выводится в поток вывода. Необходимости знать детали работы кода пока что нет: они прояснятся, когда мы будем изучать API во второй части статьи. Однако необходимо скомпилировать листинг 1. Возьмите код из листинга 1, после чего наберите в командной строке следующую команду для компиляции RegexDemo: javac RegexDemo.java

Класс Pattern и его конструкции

Класс Pattern, первый из трех классов, составляющих API Regex, представляет собой скомпилированное представление регулярного выражения. Документация по SDK класса Pattern описывает разнообразные конструкции регулярных выражений, но если вы не пользуетесь активно регулярными выражениями, то отдельные части этой документации могут поставить вас в тупик. Что такое квантификаторы (quantifiers) и в чем различие между жадными (greedy), ленивыми (reluctant) и сверхжадными (possessive) квантификаторами? Что такое классы символов (character classes), граничные сопоставители (boundary matchers,), обратные ссылки (back references) и вложенные флаговые выражения (embedded flag expressions)? Я отвечу на эти и другие вопросы в следующих разделах.

Литеральные строки

Простейшая конструкция регулярного выражения — литеральная строка. Для успеха поиска по шаблону какая-либо часть входного текста должен соответствовать шаблону этой конструкции. Рассмотрим следующий пример: java RegexDemo apple applet В этом примере мы пытаемся найти соответствие для шаблона apple во входном тексте applet. В следующем результате показано найденное совпадение:
regex = apple
input = applet
Found [apple] starting at 0 and ending at 4
Мы видим в выводимых данных регулярное выражение и входной текст, а затем указание на успешное обнаружение apple в applet. Кроме того, там приведены начальная и конечная позиции этого совпадения: 0 и 4, соответственно. Начальная позиция указывает на первое место в тексте, где было обнаружено совпадение с шаблоном, а конечная позиция — последнюю точку совпадения. Теперь допустим, что мы задали следующую командную строку: java RegexDemo apple crabapple На этот раз мы получим следующий результат, с другими начальной и конечной позициями:
regex = apple
input = crabapple
Found [apple] starting at 4 and ending at 8
В обратном случае, с applet в качестве регулярного выражения и apple — входного текста, совпадений найдено не будет. Совпасть должно всё регулярное выражение, а в данном случае, входной текст не содержит t после apple.

Метасимволы

Более интересные конструкции регулярных выражений сочетают литеральные символы с метасимволами. Например, в регулярном выражении a.b, метасимвол точки (.) означает любой символ, находящийся между a и b. Рассмотрим следующий пример: java RegexDemo .ox "The quick brown fox jumps over the lazy ox." В этом примере используется .ox в качестве регулярного выражения, и The quick brown fox jumps over the lazy ox. в качестве входного текста. RegexDemo выполняет поиск в тексте совпадений, начинающихся с любого символа и заканчивающихся на ox. Результаты его выполнения – следующие:
regex = .ox
input = The quick brown fox jumps over the lazy ox.
Found [fox] starting at 16 and ending at 18
Found [ ox] starting at 39 and ending at 41
В выведенных результатах видим два совпадения: fox и ox (с символом пробела перед ним). Метасимвол . соответствует символу f в первом случае и пробелу – во втором. Что же произойдет, если заменить .ox метасимволом .? То есть что мы получим в результате следующей командной строки: java RegexDemo . "The quick brown fox jumps over the lazy ox." Поскольку метасимвол точки соответствует любому символу, RegexDemo выведет найденные совпадения для всех символов (включая завершающий символ точки) входного текста:
regex = .
input = The quick brown fox jumps over the lazy ox.
Found [T] starting at 0 and ending at 0
Found [h] starting at 1 and ending at 1
Found [e] starting at 2 and ending at 2
Found [ ] starting at 3 and ending at 3
Found [q] starting at 4 and ending at 4
Found [u] starting at 5 and ending at 5
Found [i] starting at 6 and ending at 6
Found [c] starting at 7 and ending at 7
Found [k] starting at 8 and ending at 8
Found [ ] starting at 9 and ending at 9
Found [b] starting at 10 and ending at 10
Found [r] starting at 11 and ending at 11
Found [o] starting at 12 and ending at 12
Found [w] starting at 13 and ending at 13
Found [n] starting at 14 and ending at 14
Found [ ] starting at 15 and ending at 15
Found [f] starting at 16 and ending at 16
Found [o] starting at 17 and ending at 17
Found [x] starting at 18 and ending at 18
Found [ ] starting at 19 and ending at 19
Found [j] starting at 20 and ending at 20
Found [u] starting at 21 and ending at 21
Found [m] starting at 22 and ending at 22
Found [p] starting at 23 and ending at 23
Found [s] starting at 24 and ending at 24
Found [ ] starting at 25 and ending at 25
Found [o] starting at 26 and ending at 26
Found [v] starting at 27 and ending at 27
Found [e] starting at 28 and ending at 28
Found [r] starting at 29 and ending at 29
Found [ ] starting at 30 and ending at 30
Found [t] starting at 31 and ending at 31
Found [h] starting at 32 and ending at 32
Found [e] starting at 33 and ending at 33
Found [ ] starting at 34 and ending at 34
Found [l] starting at 35 and ending at 35
Found [a] starting at 36 and ending at 36
Found [z] starting at 37 and ending at 37
Found [y] starting at 38 and ending at 38
Found [ ] starting at 39 and ending at 39
Found [o] starting at 40 and ending at 40
Found [x] starting at 41 and ending at 41
Found [.] starting at 42 and ending at 42
Метасимволы кавычек
Чтобы указать . или любой другой метасимвол в качестве литерального символа в конструкции регулярного выражения, необходимо экранировать его одним из следующих способов:
  • поставить перед ним символ обратной косой черты;
  • Поместить этот метасимвол между \Q и \E (например, \Q.\E).
Не забывайте дублировать все встречающиеся в строковом литерале, например, String regex = "\\."; символы обратной косой черты (например, \\. или \\Q.\\E). Не дублируйте те символы обратной косой черты, которые представляют собой часть аргумента командной строки.

Классы символов

Иногда приходится ограничивать искомые совпадения конкретным набором символов. Например, искать в тексте гласные a, e, i, o и u, причем каждое вхождение гласной буквы считается совпадением. В решении подобных задач нам помогут классы символов, которые задают наборы символов между метасимволами квадратных скобок ([ ]). Класс Pattern поддерживает простые классы символов, диапазонные классы, инверсию, объединение, пересечение и вычитание классов. Мы рассмотрим все их сейчас.

Простые классы символов

Простой класс символов состоит из размещенных бок о бок символов и задает соответствие только этим символам. Например, класс [abc] соответствует символам a,b и c. Рассмотрим следующий пример: java RegexDemo [csw] cave Как видно из результатов, в этом примере будет найден только символ c, для которого есть соответствие в cave:
regex = [csw]
input = cave
Found [c] starting at 0 and ending at 0

Инвертированные классы символов

Инвертированный класс символов начинается с метасимвола ^ и задает соответствие только тем символам, которые в нём не содержатся. Например, класс [^abc] соответствует всем символам, кроме a,b и c. Рассмотрим следующий пример: java RegexDemo "[^csw]" cave Обратите внимание, что на моей операционной системе (Windows) необходимы двойные кавычки, так как командная оболочка рассматривает ^ как экранирующий символ. Как видим, в этом примере найдены только символы a, v и e, для которых есть соответствия в cave:
regex = [^csw]
input = cave
Found [a] starting at 1 and ending at 1
Found [v] starting at 2 and ending at 2
Found [e] starting at 3 and ending at 3

Диапазонные классы символов

Диапазонный класс символов состоит из двух символов, разделенных символом дефиса (-). Все символы, начиная с символа слева от дефиса и заканчивая символом справа, относятся к диапазону. Например, диапазон [a-z] соответствует всем латинским буквам в нижнем регистре. Это эквивалентно заданию простого класса [abcdefghijklmnopqrstuvwxyz]. Рассмотрим следующий пример: java RegexDemo [a-c] clown В этом примере будет найден только символ c, для которого есть соответствие в clown:
regex = [a-c]
input = clown
Found [c] starting at 0 and ending at 0
Регулярные выражения в Java, часть 2 Регулярные выражения в Java, часть 3 Регулярные выражения в Java, часть 4 Регулярные выражения в Java, часть 5

Что еще почитать:

Как правильно начать разработку под СУБД Oracle

Топ 5 библиотек машинного обучения для Java

Популярно о лямбда-выражениях в Java. С примерами и задачами. Часть 1

Lambda-выражения на примерах