JavaRush /Java блог /Java Developer /Регулярные выражения в Java, часть 4
Автор
Pavlo Plynko
Java-разработчик в CodeGym

Регулярные выражения в Java, часть 4

Статья из группы Java Developer
Предлагаем вашему вниманию перевод краткого руководства по регулярным выражениям в языке Java, написанного Джеффом Фрисеном (Jeff Friesen) для сайта javaworld. Для простоты чтения мы разделили статью на несколько частей. Регулярные выражения в Java, часть 4 - 1 Регулярные выражения в Java, часть 1 Регулярные выражения в Java, часть 2 Регулярные выражения в Java, часть 3

Методы для работы с захватываемыми группами

Исходный код приложения RegexDemo включает вызов метода m.group(). Метод group() – один из нескольких методов класса Matcher, ориентированных на работу с захватываемыми группами:
  • Метод int groupCount() возвращает число захватываемых групп в шаблоне сопоставителя. Это количество не учитывает специальную захватываемую группу номер 0, соответствующую шаблону в целом.

  • Метод String group() возвращает символы предыдущего найденного совпадения. Чтобы сообщить об успешном поиске по пустой строке, этот метод возвращает пустую строку. Если сопоставитель еще не выполнял поиска или предыдущая операция поиска завершилась неудачей, генерируется исключение IllegalStateException.

  • Метод String group(int group) напоминает предыдущий метод, за исключением того, что возвращает символы предыдущего найденного совпадения, захваченные группой, номер которой задается параметром group. Обратите внимание, что group(0) эквивалентно group(). Если в шаблоне нет захватываемой группы с заданным номером, метод генерирует исключение IndexOutOfBoundsException. Если сопоставитель еще не выполнял поиска или предыдущая операция поиска завершилась неудачей, генерируется исключение IllegalStateException.

  • Метод String group(String name) возвращает символы предыдущего найденного совпадения, захваченные группой name. Если захватываемой группы name в шаблоне нет, генерируется исключение IllegalArgumentException. Если сопоставитель еще не выполнял поиска или предыдущая операция поиска завершилась неудачей, генерируется исключение IllegalStateException.

Следующий пример демонстрирует использование методов 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 <= m.groupCount(); i++)
System.out.println(i + ": " + m.group(i));
Результаты выполнения:

3
0: abc
1: abc 
2: bc 
3: c
Регулярные выражения в Java, часть 4 - 2

Методы для определения позиций совпадений

Класс Matcher предоставляет несколько методов, возвращающих начальную и конечную позиции совпадения:
  • Метод int start() возвращает начальную позицию предыдущего найденного совпадения. Если сопоставитель еще не выполнял поиска или предыдущая операция поиска завершилась неудачей, генерируется исключение IllegalStateException.

  • Метод int start(int group) напоминает предыдущий метод, но возвращает начальную позицию предыдущего найденного совпадения для группы, номер которой задается параметром group. Если в шаблоне нет захватываемой группы с заданным номером, метод генерирует исключение IndexOutOfBoundsException. Если сопоставитель еще не выполнял поиска или предыдущая операция поиска завершилась неудачей, генерируется исключение IllegalStateException.

  • Метод int start(String name) напоминает предыдущий метод, но возвращает начальную позицию предыдущего найденного совпадения для группы с названием name. Если захватываемой группы name в шаблоне нет, генерируется исключение IllegalArgumentException. Если сопоставитель еще не выполнял поиска или предыдущая операция поиска завершилась неудачей, генерируется исключение IllegalStateException.

  • Метод int end() возвращает позицию последнего из символов предыдущего найденного совпадения плюс 1. Если сопоставитель еще не выполнял поиска или предыдущая операция поиска завершилась неудачей, генерируется исключение IllegalStateException.

  • Метод int end(int group) напоминает предыдущий метод, но возвращает конечную позицию предыдущего найденного совпадения для группы, номер которой задается параметром group. Если в шаблоне нет захватываемой группы с заданным номером, метод генерирует исключение IndexOutOfBoundsException. Если сопоставитель еще не выполнял поиска или предыдущая операция поиска завершилась неудачей, генерируется исключение IllegalStateException.

  • Метод int end(String name) напоминает предыдущий метод, но возвращает конечную позицию предыдущего найденного совпадения для группы с названием name. Если захватываемой группы name в шаблоне нет, генерируется исключение IllegalArgumentException. Если сопоставитель еще не выполнял поиска или предыдущая операция поиска завершилась неудачей, генерируется исключение IllegalStateException.

Следующий пример демонстрирует два метода определения местоположения совпадений, выводящих начальную/конечную позиции совпадения для захватываемой группы номер 2:

Pattern p = Pattern.compile("(.(.(.)))");
Matcher m = p.matcher("abcabcabc");
while (m.find())
{
   System.out.println("Найдено " + m.group(2));
   System.out.println("  начинается с позиции " + m.start(2) +
                      " и заканчивается на позиции " + (m.end(2) - 1));
   System.out.println();
}
В результате выполнения этого примера выводится следующее:

Найдено bc
начинается с позиции 1 и заканчивается на позиции 2
Найдено bc
начинается с позиции 4 и заканчивается на позиции 5 
Найдено bc
начинается с позиции 7 и заканчивается на позиции 8

Методы класса PatternSyntaxException

Экземпляр класса PatternSyntaxException описывает синтаксическую ошибку в регулярном выражении. Генерирует такое исключение из методов compile() и matches() класса Pattern, а формируется посредством следующего конструктора: PatternSyntaxException(String desc, String regex, int index) Этот конструктор сохраняет указанное описание (desc), регулярное выражение (regex) и позицию, на которой произошла синтаксическая ошибка. Если место синтаксической ошибки неизвестно, значение index устанавливается равным -1. Скорее всего, вам никогда не понадобится создавать экземпляры класса PatternSyntaxException. Тем не менее, нужно будет извлекать вышеупомянутые значения при создании форматированного сообщения об ошибке. Для этого можно воспользоваться следующими методами:
  • Метод String getDescription() возвращает описание синтаксической ошибки.
  • Метод int getIndex() возвращает или позицию, на которой произошла ошибка, или -1, если позиция неизвестна.
  • Метод String getPattern() возвращает ошибочное регулярное выражение.
Кроме того, унаследованный метод String getMessage() возвращает многострочную строку со значениями, возвращенными из предыдущих методов вместе с визуальным указанием на место синтаксической ошибки в шаблоне. Что представляет собой синтаксическая ошибка? Вот пример: java RegexDemo (?itree Treehouse В данном случае мы забыли указать закрывающий метасимвол скобки ()) во вложенном флаговом выражении. Вот что выводится в результате этой ошибки:

regex = (?itree
input = Treehouse
Неправильное регулярное выражение: Unknown inline modifier near index 3
(?itree
   ^
Описание: Unknown inline modifier
Позиция: 3
Неправильный шаблон: (?itree

Создание полезных приложений с регулярными выражениями при помощи API Regex

Регулярные выражения позволяют создавать приложения для обработки текста, обладающие большими возможностями. В этом разделе мы покажем вам два удобных приложения, которые, надеемся, побудят вас далее исследовать классы и методы API Regex. Во втором приложении вы познакомитесь с Lexan: библиотекой многоразового кода для выполнения лексического анализа. Регулярные выражения в Java, часть 4 - 3

Регулярные выражения и документация

Документирование – одна из обязательных задач при разработке профессионально выполненного программного обеспечения. К счастью, регулярные выражения могут помочь вам со многими аспектами создания документации. Код в листинге 1 извлекает строки, содержащие однострочные и многострочные комментарии в стиле языка C, из исходного файла и записывает их в другой файл. Чтобы код работал, комментарии должны быть расположены в одной строке. Листинг 1. Извлечение комментариев

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

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

public class ExtCmnt
{
   public static void main(String[] args)
   {
      if (args.length != 2)
      {
         System.err.println("Способ применения: java ExtCmnt infile outfile");
         return;
      }

      Pattern p;
      try
      {
         // Следующий шаблон определяет многострочные комментарии,
         // располагающиеся в одной строке (например, /* одна строка */)
            // и однострочные комментарии (например, // какая-то строка).
            // Комментарий может располагаться в любом месте строки.

         p = Pattern.compile(".*/\\*.*\\*/|.*//.*$");
      }
      catch (PatternSyntaxException pse)
      {
         System.err.printf("Синтаксическая ошибка в регулярном выражении: %s%n", pse.getMessage());
         System.err.printf("Описание ошибки: %s%n", pse.getDescription());
         System.err.printf("Позиция ошибки: %s%n", pse.getIndex());
         System.err.printf("Ошибочный шаблон: %s%n", pse.getPattern());
         return;
      }

      try (FileReader fr = new FileReader(args[0]);
           BufferedReader br = new BufferedReader(fr);
           FileWriter fw = new FileWriter(args[1]);
           BufferedWriter bw = new BufferedWriter(fw))
      {
         Matcher m = p.matcher("");
         String line;
         while ((line = br.readLine()) != null)
         {
            m.reset(line);
            if (m.matches()) /* Должна соответствовать вся строка */
            {
               bw.write(line);
               bw.newLine();
            }
         }
      }
      catch (IOException ioe)
      {
         System.err.println(ioe.getMessage());
         return;
      }
   }
}
Метод main() из листинга 1 сначала проверяет правильность синтаксиса командной строки, после чего компилирует предназначенное для обнаружения одно- и многострочных комментариев регулярное выражение в объект класса Pattern. Если не возникает исключения PatternSyntaxException, метод main() открывает исходный файл, создает целевой файл, получает сопоставитель для сопоставления каждой прочитанной строки с шаблоном, после чего читает исходный файл построчно. Для каждой строки выполняется сопоставление её с шаблоном комментария. В случае успеха, метод main() записывает строку (с последующим символом новой строки) в целевой файл (мы рассмотрим логику файлового ввода/вывода в будущем учебном пособии Java 101). Скомпилируйте листинг 1 следующим образом: javac ExtCmnt.java Выполните приложение с файлом ExtCmnt.java в качестве входного: java ExtCmnt ExtCmnt.java out Вы должны получить в файле out следующие результаты:

         // Следующий шаблон определяет многострочные комментарии,
         // располагающиеся в одной строке (например, /* одна строка */)
            // и однострочные комментарии (например, // какая-то строка).
            // Комментарий может располагаться в любом месте строки.
        p = Pattern.compile(".*/\\*.*\\*/|.*//.*$");
            if (m.matches()) /* Должна соответствовать вся строка */
В строке шаблона .*/\\*.*\\*/|.*//.*$, метасимвол вертикальной черты | играет роль логического оператора ИЛИ, указывающего на необходимость сопоставителю использовать левый операнд из конструкции данного регулярного выражения для поиска соответствия в тексте сопоставителя. Если соответствий нет, сопоставитель использует правый операнд из конструкции данного регулярного выражения для еще одной попытки поиска (метасимволы скобок в захватываемой группе тоже формируют логический оператор). Регулярные выражения в Java, часть 5
Комментарии (12)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Максим Уровень 40
23 июля 2022
Кривой regex.. скопировал и попробовал на java17 - не ищет однострочный(//) комментарий, не учитывает перенос строк в многострочном комментарии (/*....*/) Моя версия рабочая. Проверил на этом java-файле

"(//.*|(?s)(/\\*.*?\\*/))"
(?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 <= m.groupCount(); i++)
System.out.println(i + ": " + m.group(i));
В вашем шаблоне 3 группs захвата, поэтому .groupCount() возвращает 3. Обратите внимание, что это не возвращает, сколько совпадений было найдено!!!!
Ivan D Уровень 35
2 сентября 2020
Прям кайфую от чтения красивого кода с объяснениями.
Тимофей Уровень 26
15 июля 2020
Не работает, если реально есть в файле многострочный комментарий. Вот это регулярное выражение - ".*/\\*.*\\*/|.*//.*$"
Denis Уровень 41
9 апреля 2020
Привет, что нужно изменить, чтобы заработало на macos ?

".*/\\*.*\\*/|.*//.*$"
в частности символ “/“
Burakov Vladimir Уровень 41
14 января 2020
Что ищет (.(.(.))) ?
NastyaGermanovich Уровень 35 Expert
31 октября 2017
В пункте "Методы класса PatternSyntaxException" вставьте код к примеру синтаксической ошибки(использование методов).