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, 03:11
Кривой regex.. скопировал и попробовал на java17 - не ищет однострочный(//) комментарий, не учитывает перенос строк в многострочном комментарии (/*....*/) Моя версия рабочая. Проверил на этом java-файле
"(//.*|(?s)(/\\*.*?\\*/))"
(?s) - включает многострочный режим только для 3-ей группы И внутри третьей группы:
(/\\*.*?\\*/)
обязательно подчеркнутую часть нужно сделать ленивой, иначе загребать будет жадно от первого найденного /* по самый последний */. https://regex101.com/ - здесь можно посмотреть эффект жадности если убрать знак "?" из regex'а и вставить код из прикрепленного файла.
Евгения
Уровень 47
19 февраля 2022, 10:31
Помогите, пожалуйста, с последним примером. Я его скопировала, вставила в Идею. Далее сказано: Выполните приложение с файлом ExtCmnt.java в качестве входного: java ExtCmnt ExtCmnt.java out Если эту строчку вставить в параметры, ничего не работает. Я уже пыталась и полный путь к файлу прописать - ничего не получается. Подскажите, как можно это сделать.
milyasow
Уровень 34
12 сентября 2022, 21:42
В параметрах должно быть так:
ExtCmnt.java out
где ExtCmnt.java - это входной файл с кодом, out - выходной файл с результатом. И лучше с полным путем указывать, примерно так:
d:\javarush\examples\ExtCmnt.java d:\javarush\examples\out.txt
Макс Дудин
Уровень 41
18 мая 2021, 20:24
Вот это прикольно... особенно последнее
Дмитрий
Уровень 24
11 октября 2020, 19:04
Следующий пример демонстрирует использование методов 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, 17:36
Прям кайфую от чтения красивого кода с объяснениями.
Тимофей
Уровень 26
15 июля 2020, 08:09
Не работает, если реально есть в файле многострочный комментарий. Вот это регулярное выражение - ".*/\\*.*\\*/|.*//.*$"
Николай Т.
Уровень 40
23 октября 2020, 22:11
Может забыли добавить в паттерн многострочный режим?
Denis
Уровень 41
9 апреля 2020, 15:23
Привет, что нужно изменить, чтобы заработало на macos ?
".*/\\*.*\\*/|.*//.*$"
в частности символ “/“
Burakov Vladimir
Уровень 41
14 января 2020, 19:20
Что ищет (.(.(.))) ?
Роман
Уровень 27
30 января 2020, 18:29
Ищет 3 любых символа, идущих друг за другом. Эквивалентно "..." Скобки стоят для разделения на группы и последующего обращения к ним.
NastyaGermanovich
Уровень 35
Expert
31 октября 2017, 14:30
В пункте "Методы класса PatternSyntaxException" вставьте код к примеру синтаксической ошибки(использование методов).