JavaRush /Java блог /Random UA /Регулярні вирази в Java, частина 3
Professor Hans Noodles
41 рівень

Регулярні вирази в Java, частина 3

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

Спрощуємо вирішення найпоширеніших завдань програмування за допомогою 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містить лише прогалини і символи в нижньому регістрі.
Регулярні вирази у Java, частина 3 - 2

Розбиття тексту

Більшості розробників доводилося хоч раз писати код для розбиття вхідного тексту на складові, наприклад, перетворювати текстовий обліковий запис співробітника на набір полів. Клас 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, частина 3 - 3

Додавання тексту до кінця

Позиція порівняча для додавання в кінець визначає початок тексту зіставника, що додається в кінець об'єкта типу java.lang.StringBuffer. Цю позицію використовують такі методи:
  • Метод Matcher appendReplacement(StringBuffer sb, String replacement)читає символи тексту співставника і приєднує в кінець об'єкта StringBuffer, який посилається аргумент sb. Цей метод припиняє читання на останньому символі, що передує попередньому відповідності шаблону. Далі метод додає символи з об'єкта типу String, на який посилається аргумент replacement, в кінець об'єкта StringBuffer(рядок replacementможе містити посилання на текстові послідовності, захоплені під час попереднього пошуку; вони вказуються за допомогою символів ($)і номерів груп, що захоплюються). Нарешті, метод встановлює значення позиції співставника для додавання в кінець рівним позиції останнього символу, що збігся плюс одиниця, після чого повертає посилання на поточний зіставник.

  • Метод Matcher appendReplacement(StringBuffer sb, String replacement)генерує виняток java.lang.IllegalStateException, якщо співставник ще не знаходив відповідності або попередня спроба пошуку завершилася невдало. Він генерує виняток IndexOutOfBoundsException, якщо рядок replacementзадає відсутню в шаблоні групу, що захоплюється).

  • Метод StringBuffer appendTail(StringBuffer sb)додає весь текст до об'єкта StringBufferі повертає посилання на цей об'єкт. Після останнього виклику методу appendReplacement(StringBuffer sb, String replacement), викличте метод appendTail(StringBuffer sb), щоб скопіювати текст, що залишився в об'єкт StringBuffer.

Захоплювані групи
Як ви пам'ятаєте з частини 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
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ