Що таке регулярне вираження RegEx?
Насправді регулярне вираз (RegEx в Java) – це шаблон пошуку рядка в тексті. Java вихідним поданням цього шаблону завжди є рядок, тобто об'єкт класу String. Однак не будь-який рядок може бути скомпільований у регулярний вираз, а лише той, який відповідає правилам написання регулярного виразу – синтаксису, визначеному у специфікації мови. Для написання регулярного виразу використовуються буквені та цифрові символи, а також метасимволи – символи, що мають спеціальне значення у синтаксисі регулярних виразів. Наприклад:String regex = "java"; // шаблон рядка "java";
String regex = "\\d{3}"; // шаблон рядка із трьох цифрових символів;
Створення регулярних виразів у Java
Щоб створити RegEx в Java, потрібно зробити два простих кроки:- написати його у вигляді рядка з урахуванням синтаксису регулярних виразів;
- скомпілювати цей рядок у регулярний вираз;
Pattern
. Для цього необхідно викликати один із двох наявних у класі статичних методів compile
. Перший метод приймає один аргумент – рядковий літерал регулярного виразу, а другий – плюс ще параметр, що включає режим порівняння шаблону з текстом:
public static Pattern compile (String literal)
public static Pattern compile (String literal, int flags)
Список можливих значень параметра flags
визначено у класі Pattern
та доступний нам як статичні змінні класу. Наприклад:
Pattern pattern = Pattern.compile("java", Pattern.CASE_INSENSITIVE);//Пошук збігів з шаблоном буде здійснюватися без урахування регістру символів.
По суті, клас Pattern
– це конструктор регулярних виразів. Під «капотом» метод compile
викликає закритий конструктор класу Pattern
створення скомпаборованного уявлення. Такий спосіб створення екземпляра шаблону реалізований з метою створення його у вигляді незмінного об'єкта. Під час створення проводиться синтаксична перевірка регулярного висловлювання. За наявності помилок у рядку – генерується виняток PatternSyntaxException
.
Синтаксис регулярних виразів
Синтаксис регулярних виразів ґрунтується на використанні символів<([{\^-=$!|]})?*+.>
, які можна поєднувати з літерними символами. Залежно від ролі їх можна поділити на кілька груп:
Метасимвол | Призначення |
---|---|
^ | початок рядка |
$ | кінець рядка |
\b | межа слова |
\B | не межа слова |
\A | початок введення |
\G | кінець попереднього збігу |
\Z | кінець введення |
\z | кінець введення |
Метасимвол | Призначення |
---|---|
\d | цифровий символ |
\D | нецифровий символ |
\s | символ пропуску |
\S | непробільний символ |
\w | буквено-цифровий символ або знак підкреслення |
\W | будь-який символ, окрім буквеного, цифрового або знак підкреслення |
. | будь-який символ |
Метасимвол | Призначення |
---|---|
\t | символ табуляції |
\n | символ нового рядка |
\r | символ повернення каретки |
\f | перехід на нову сторінку |
0085 | символ наступного рядка |
\u 2028 | символ поділу рядків |
\u 2029 | символ поділу абзаців |
Метасимвол | Призначення |
---|---|
[а Б В] | будь-який із перерахованих (а,б, або в) |
[^ Абв] | будь-який, крім перерахованих (не а, б, в) |
[a-zA-Z] | злиття діапазонів (латинські символи від a до z без урахування регістру) |
[ad[mp]] | об'єднання символів (від a до d і від m до p) |
[az&&[def]] | перетин символів (символи d, e, f) |
[az&&[^bc]] | віднімання символів (символи a, dz) |
Метасимвол | Призначення |
---|---|
? | один або відсутній |
* | нуль або більше разів |
+ | один або більше разів |
{n} | n раз |
{n,} | n разів і більше |
{n, m} | не менше n разів і не більше m разів |
Жадібний режим квантифікатора
Особливістю квантифікаторів є можливість використання їх у різних режимах: жадібному, наджадібному та лінивому. Наджадібний режим включається додаванням символу «+
» після квантифікатора, а лінивий – символу « ?
». Наприклад:
"А.+а" // жадібний режим
"А.++а" // Над жадібний режим
"А.+?а" // лінивий режим
Спробуємо з прикладу цього шаблону розібратися у роботі квантифікаторів у різних режимах. За замовчуванням квантифікатор працює у жадібному режимі. Це означає, що він шукає максимально довгий збіг у рядку. В результаті виконання цього коду:
public static void main(String[] args) {
String text = "Єгор Алла Олександр";
Pattern pattern = Pattern.compile("А.+а");
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
System.out.println(text.substring(matcher.start(), matcher.end()));
}
}
ми отримаємо такий висновок: Алла Алекса Алгоритм пошуку за заданим шаблоном " А.+а
", виконується в наступній послідовності:
-
У заданому шаблоні перший символ – це символ літери
А
.Matcher
зіставляє його з кожним символом тексту, починаючи з нульової позиції. На нульовій позиції в тексті знаходиться символЕ
, томуMatcher
перебирає послідовно символи в тексті, доки не зустріне збіг з шаблоном. У прикладі це символ на позиції №5. -
Після того, як знайдено збіг з першим символом шаблону,
Matcher
звіряє відповідність з другим символом шаблону. У нашому випадку це символ «.
», який означає будь-який символ.На шостій позиції – символ літери
л
. Вочевидь, він відповідає шаблону «будь-який символ». -
Matcher
переходить до перевірки наступного символу із шаблону. У нашому шаблоні він поставлений за допомогою квантифікатора «.+
». Оскільки кількість повторень «будь-якого символу» у шаблоні – один і більше разів,Matcher
бере по черзі наступний символ з рядка і перевіряє його на відповідність шаблону, доки буде виконуватися умова «будь-який символ», у нашому прикладі – до кінця рядка ( з поз. №7 - №18 тексту).Власне,
Matcher
, захоплює все рядок остаточно – у цьому таки виявляється його «жадібність». -
Після того як
Matcher
дійшов до кінця тексту і закінчив перевірку для частини шаблону «А.+
», Matcher починає перевірку для частини шаблону, що залишилася - символ літериа
. Оскільки текст у прямому напрямку закінчився, перевірка відбувається у зворотному напрямку, починаючи з останнього символу: -
Matcher
«Пам'ятає» кількість повторень у шаблоні «.+
», при якому він дійшов до кінця тексту, тому він зменшує кількість повторень на одиницю і перевіряє відповідність шаблону тексту, доки не буде знайдено збіг:
Надмірний режим квантифікатора
У наджадібному режимі робота матчера аналогічна механізму жадібного режиму. Відмінність у тому, що з захоплення тексту остаточно рядка пошук у зворотному напрямі немає. Тобто перші три етапи при наджадібному режимі будуть аналогічні жадібному режиму. Після захоплення всього рядка матчер додає залишок шаблону і порівнює із захопленим рядком. У нашому прикладі під час виконання методу main з шаблоном "А.++а
" збігів не буде знайдено.
Лінивий режим квантифікатора
-
У цьому режимі на початковому етапі, як і в жадібному режимі, шукається збіг з першим символом шаблону:
-
Далі шукається збіг із наступним символом шаблону – будь-яким символом:
-
На відміну від жадібного режиму, в лінивому шукається найкоротший збіг у тексті, тому після знаходження збігу з другим символом шаблону, який заданий точкою та відповідає символу на позиції №6 тексту, перевірятиме відповідність тексту
Matcher
залишку шаблону – символу «а
» -
Оскільки збіг із шаблоном у тексті не знайдено (на позиції №7 у тексті знаходиться символ «
л
»),Matcher
додає ще один «будь-який символ» у шаблоні, оскільки він заданий як один і більше разів, і знову порівнює шаблон з текстом на позиціях з №5 по №8: -
У нашому випадку знайдено збіг, але кінець тексту ще не досягнуто. Тому з позиції №9 перевірка починається з пошуку першого символу шаблону за аналогічним алгоритмом і далі повторюється до закінчення тексту.
main
при використанні шаблону А.+?а
ми отримаємо наступний результат: Алла Алекса Як видно з нашого прикладу, при використанні різних режимів квантифікатора для одного і того ж шаблону ми отримали різні результати. Тому необхідно враховувати цю особливість та вибирати потрібний режим залежно від бажаного результату під час пошуку.
Екранування символів у регулярних виразах
Оскільки регулярний вираз у Java, а точніше — його вихідне уявлення задається за допомогою рядкового літералу, необхідно враховувати правила специфікації Java, які стосуються рядкових літералів. Зокрема, символ зворотної косої риси «\
» у рядкових літералах у вихідному коді Java інтерпретується як символ послідовності, що управляє, який попереджає компілятор, що наступний за ним символ — спеціальний і що його потрібно особливим чином інтерпретувати. Наприклад:
String s = "The root directory is \nWindows";//перенесення Windows на новий рядок
String s = "The root directory is \u00A7Windows";//вставка символу параграфа перед Windows
Тому в рядкових літералах, які описують регулярний вираз, і використовують символ « \
» (наприклад, для метасимволів) його потрібно подвоювати , щоб компілятор байт-коду Java не інтерпретував його по-своєму. Наприклад:
String regex = "\\s"; // шаблон для пошуку символів пропуску
String regex = "\"Windows\""; // шаблон для пошуку рядка "Windows"
Подвійний символ зворотної косої риси також слід використовувати для екранування символів, які задіяні як спеціальні, якщо ми плануємо їх використовувати як «звичайні» символи. Наприклад:
String regex = "How\\?"; // шаблон пошуку рядка “How?”
Методи класу Pattern
У класіPattern
є й інші методи для роботи з регулярними виразами: String pattern()
- Повертає вихідне рядкове подання регулярного виразу, з якого був створений об'єкт Pattern
:
Pattern pattern = Pattern.compile("abc");
System.out.println(Pattern.pattern())//"abc"
static boolean matches(String regex, CharSequence input)
– дозволяє перевірити регулярний вираз, переданий у параметрі regex на відповідність тексту, переданому у параметрі input
. Повертає: true – якщо текст відповідає шаблону; false – інакше; Приклад:
System.out.println(Pattern.matches("А.+а","Алла"));//true
System.out.println(Pattern.matches("А.+а","Єгор Алла Олександр"));//false
int flags()
– повертає значення параметра flags
шаблону, які були встановлені під час його створення, або 0, якщо цей параметр не встановлено. Приклад:
Pattern pattern = Pattern.compile("abc");
System.out.println(pattern.flags());// 0
Pattern pattern = Pattern.compile("abc",Pattern.CASE_INSENSITIVE);
System.out.println(pattern.flags());// 2
String[] split(CharSequence text, int limit)
– розбиває текст, переданий як параметр масив елементів String
. Параметр limit
визначає граничну кількість збігів, які шукаються в тексті:
- при
limit>0
- виконується пошукlimit-1
збігів; - при
limit<0
– виконується пошук усіх збігів у тексті - при
limit=0
– виконується пошук усіх збігів у тексті, у своїй порожні рядки наприкінці масиву відкидаються;
public static void main(String[] args) {
String text = "Єгор Алла Анна";
Pattern pattern = Pattern.compile("\\s");
String[] strings = pattern.split(text,2);
for (String s : strings) {
System.out.println(s);
}
System.out.println("---------");
String[] strings1 = pattern.split(text);
for (String s : strings1) {
System.out.println(s);
}
}
Ще один метод класу для створення об'єкта розглянемо нижче Matcher
.
Методи класу Matcher
Matcher
є клас, з якого створюється об'єкт для пошуку збігів за шаблоном. Matcher
- Це «пошуковик», «движок» регулярних виразів. Для пошуку йому треба дати дві речі: шаблон пошуку та «адресау», за якою шукати. Для створення об'єкта Matcher
передбачений наступний метод у класі Pattern
: рublic Matcher matcher(CharSequence input)
Як аргумент метод приймає послідовність символів, в якому буде здійснюватися пошук. Це об'єкти класів, що реалізують інтерфейс CharSequence
. як аргумент можна передати як String
, а й StringBuffer
, StringBuilder
, Segment
і CharBuffer
. Шаблоном для пошуку є об'єкт класу Pattern
, на якому викликається метод matcher
. Приклад створення матчера:
Pattern p = Pattern.compile("a*b");// скомпілювали регулярне вираження на виставу
Matcher m = p.matcher("aaaaab");//Створабо пошуковик у тексті “aaaaab” за шаблоном "a*b"
Тепер за допомогою нашого «пошукача» ми можемо шукати збіги, дізнаватися про позицію збігу в тексті, замінювати текст за допомогою методів класу. Метод boolean find()
шукає черговий збіг у тексті із шаблоном. За допомогою цього методу та оператора циклу можна проводити аналіз усього тексту за подієвою моделлю (здійснювати необхідні операції при настанні події – знаходженні збігу в тексті). Наприклад, за допомогою методів цього класу int start()
можна int end()
визначати позиції збігу в тексті, а за допомогою методів String replaceFirst(String replacement)
і String replaceAll(String replacement)
замінювати в тексті збіги на інший текст replacement. Приклад:
public static void main(String[] args) {
String text = "Єгор Алла Анна";
Pattern pattern = Pattern.compile("А.+?а");
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
int start=matcher.start();
int end=matcher.end();
System.out.println("Знайдено збіг" + text.substring(start,end) + " с "+ start + "за" + (end-1) + "позицію");
}
System.out.println(matcher.replaceFirst("Іра"));
System.out.println(matcher.replaceAll("Ольга"));
System.out.println(text);
}
Висновок програми: Знайдено збіг Алла з 5 по 8 позицію Знайдено збіг Анна зreplaceFirst
10 по replaceAll
13 String
позицію шаблоном замінено на текст, переданий методом як аргумент. Причому метод replaceFirst
замінює лише перший збіг, а replaceAll
– всі збіги у тесті. Початковий текст залишається без змін. Використання інших методів класу Matcher
, а також приклади регулярних виразів можна переглянути в цьому циклі статей . Найбільш часті операції з регулярними виразами при роботі з текстом із класів Pattern
та Matcher
вбудовані в класString
. Це такі методи як split
, matches
, replaceFirst
, replaceAll
. Але насправді «під капотом» вони використовують класи Pattern
та Matcher
. Тому, якщо потрібно замінити текст або порівняти рядки в програмі без написання зайвого коду, використовуйте методи класу String
. Якщо вам потрібні розширені можливості – згадайте про класи Pattern
і Matcher
.
Висновок
Регулярний вираз описується в Java-програмі за допомогою рядків, які підходять під визначений правилами шаблон. При виконанні коду Java перекомпілює цей рядок на об'єкт класPattern
і використовує об'єкт класу Matcher
для пошуку відповідності в тексті. Як я вже казав на початку, регулярні висловлювання дуже часто відкладають на потім, вважаючи складною темою. Однак якщо розібратися з основами синтаксису, метасимволами, екрануванням та вивчити приклади регулярних виразів, вони виявляються набагато простішими, ніж здаються на перший погляд.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ