Регулярні вирази у мовах програмування — потужний, але складний в опануванні інструмент. Тому не дивно, що багато хто відкладає його на потім або намагається використовувати по мінімуму.
Але це найкращий спосіб опрацювання текстів, а також метод розв’язання завдань, які пов’язані з пошуком, редагуванням, заміною тексту у файлах. Тому регулярні вирази варто знати. Сьогодні мова піде про регулярні вирази в популярній кросплатформенній мові Java.
Зміст
Регулярні вирази в Java — це певні послідовності символів, за допомогою яких можна обробляти рядки тексту за спеціальними синтаксичними шаблонами.
Приклад регулярних виразів має такий вигляд:
String regex="java"; // шаблон рядка "java"; String regex="\\d{3}"; // шаблон рядка з трьох цифрових символів;
Ця методика дає змогу дізнатися, чи входить певний символ або їхня послідовність у рядок. Інакше кажучи, це методика для обробки рядків.
Наприклад, вона може застосовуватися, якщо потрібно перевірити коректність введення електронної пошти. У ньому обов’язково має бути присутнім символ @, адреса домену, крапка після нього і доменна зона. Тобто система перевіряє, щоб адреса виглядала як user@gmail.com (сама адреса без лапок, зрозуміло), а не user.gmail.com або user@gmail,com, або навіть user@gmail.com.
Клас Java, який називається Pattern — це базове рішення для роботи з регулярними виразами в Java. Саме його використовують при підключенні RegEx до коду програми.
Цей клас можна використовувати двома різними способами (методами). По-перше, це Pattern.matches()
. Він застосовується для швидкої перевірки тексту на відповідність заданому регулярному виразу. Але він перевіряє тільки один текст або рядок.
По-друге, це Pattern.compile()
— скомпільований екземпляр Pattern. Ось його вже можна використовувати кілька разів, щоб скласти регулярний вираз для кількох текстів.
Наприклад:
String regex="java"; // шаблон рядка "java"; String regex="\\d{3}"; // шаблон рядка з трьох цифрових символів;
Ще один клас Java для регулярних виразів — Matcher (java.util.regex.Matcher) — він необхідний для пошуку декількох входжень того чи іншого набору символів у межах одного тексту, а також застосовується для пошуку в різних текстах, які подаються на вхід у межах одного шаблону. У ньому є низка своїх методів, які використовуються для роботи.
Серед основних відзначимо такі:
boolean matches()
: цей метод повертає значення true
у разі збігу рядка з шаблоном;boolean find()
: цей метод повертає значення true
у разі виявлення підрядка, що збігається з шаблоном, після чого перейде до нього;int start()
: цей метод повертає значення індексу відповідності;int end()
: використовуючи цей метод, можна отримати показники індексу відповідності;String replaceAll(String str)
: а ось цей метод виводить значення, яке задається підрядком str
при зміні основного рядка.А ось такий вигляд має приклад із використанням Pattern і Matcher:
імпорт java.util.regex.Matcher; import java.util.regex.Pattern; public class Main { public static void main (String[] args) { Pattern pattern1 = Pattern.compile ("[x-z]+");//Пошук відбуватиметься від x до z включно. //Пошук відбуватиметься тільки за символами нижнього регістру. //Щоб вимкнути чутливість до регістру, можна використовувати Pattern.CASE_INSENSITIVE. Matcher matcher1 = pattern1.matcher ("x y z 1 2 3 4 "); System.out.println (matcher1.find()); //Пошук будь-якого збігу з шаблоном. //Виводиться значення true, оскільки в рядку є символи шаблону. Matcher matcher2 = pattern1.matcher ("X Y Z 1 2 3 4"); System.out.println (matcher2.find()); //Виводиться значення false. //Так як у рядку немає символів, що підходять за шаблоном. Шаблон pattern2 = Pattern.compile ("[a-zA-Z0-9]"); //Додається пошук за символами нижнього і верхнього регістру, а також цифр. Matcher matcher3 = pattern2.matcher ("A B C D X Y Z a b c d x y z 1 2 3 4"); System.out.println (matcher3.find()); //Виводиться значення true
Ще один клас — PatternSyntaxException. Він виводить повідомлення про неперевірене виключення, якщо в синтаксисі регулярного виразу припустилися помилки. Йдеться про некоректні символи в шаблоні, наприклад, про друкарську помилку.
Клас оголошується ось так:
public class PatternSyntaxException extends IllegalArgumentException
У переліку методів класу відзначимо такі:
getDescription ()
: метод отримує опис помилок;int getIndex ()
: цей метод отримує індекс помилки;getMessage ()
: у цьому методі повертається багаторядковий рядок, що містить опис помилки, індекс, шаблон із помилкою та навіть візуально показує, де в шаблоні допущена помилка;getPattern ()
: цей метод, своєю чергою, отримує помилковий шаблон регулярного виразу.Приклад класу має такий вигляд:
Жива демонстрація package com.tutorialspoint; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; public class PatternSyntaxExceptionDemo { private static String REGEX = "[""; private static String INPUT = "Собака каже мяу" + "Усі собаки кажуть мяу."; private static String REPLACE = "cat"; public static void main(String[] args) { try{ Pattern pattern = Pattern.compile(REGEX); // отримати об'єкт matcher Matcher matcher = pattern.matcher(INPUT); INPUT = matcher.replaceAll(REPLACE); } catch(PatternSyntaxException e){ System.out.println("PatternSyntaxException: "); System.out.println("Description: "+ e.getDescription()); System.out.println("Index: "+ e.getIndex()); System.out.println("Message: "+ e.getMessage()); System.out.println("Pattern: "+ e.getPattern()); } } }
Для створення регулярного виразу задіюються рядки або об’єкти класу String. Але далеко не кожен рядок автоматично може скомпілюватися в регулярний вираз. Він має бути відповідним чином написаний — з використанням синтаксису, який визначається в офіційних специфікаціях Java.
У цьому разі йдеться про використання букв, цифр і метасимволів, які мають спеціальне значення саме в рамках регулярних виразів.
Синтаксичні конструкції регулярних виразів, як сказано вище, охоплюють літерні, цифрові та метасимволи.
Серед них:
Для пошуку меж рядків, слів і тому подібних даних використовуються такі метасимволи:
^
— метасимвол початку оброблюваного рядка;$
— метасимвол закінчення оброблюваного рядка;-
— так задається метасимвол, який має бути поза дужками, причому він повинен бути тільки один;\b
— таким чином задається закінчення слова;\B
— так задається умова, коли слово не закінчене;\A
— так задається старт введення;\Z
— так задається закінчення введення.Також можна вибирати певні класи символів:
\d
— будь-який цифровий символ;\D
— будь-який нецифровий;\s
— символ пробілу;\S\
— символ, що не має пробілу;.
— задати один довільний символ;\w
— абетково-цифровий символ;\W
— будь-який символ, за винятком абетково-цифрового.Для завдання діапазону можна використовувати -
, у цьому разі регулярний вираз (a-z) шукатиме всі символи в заданому діапазоні.
Серед інших способів використання косої риски відзначимо:
\n
— перенесення рядка; \\+
— екранування символу, оскільки коса риска використовується як спецсимвол.Детальніше про це поговоримо нижче у відповідному розділі.
Приклад найпростішого регулярного виразу для перевірки адреси електронної пошти на правильність має такий вигляд:
^[A-Z0-9+_.-]+@[A-Z0-9.-]+$
Тут перевіряється входження символу @
, крапки-роздільника і доменної зони, які є обов’язковими для електронної пошти.
А ось так виглядає складніший приклад, який шукає цифрові символи в рядку:
імпорт java.util.regex.Matcher; import java.util.regex.Pattern; public class RegexMatches { public static void main( String args[] ) { // Рядок для сканування, щоб знайти шаблон String str = "Хрещення Київської Русі відбулося в 988 році! Чи не так?"; String pattern = "(.*)(\\d+)(.*)"; // Створення Pattern об'єкта Pattern r = Pattern.compile(pattern); // Створення matcher об'єкта Matcher m = r.matcher(str); if (m.find( )) { System.out.println("Знайдено значення: " + m.group(0)); System.out.println("Знайдено значення: " + m.group(1)); System.out.println("Знайдено значення: " + m.group(2)); }інакше { System.out.println("НЕ Збігається"); } } }
На виході отримаємо такий результат:
Крім символів у регулярних виразах застосовуються квантифікатори. Це обмежувачі, за допомогою яких задається частота появи певного символу або декількох. Їх потрібно записувати після самих символів.
Основні квантифікатори:
?
— символ не з’являється зовсім або ж з’являється лише раз;-
— символу зовсім немає в рядку або він з’являється, або з’являється більше ніж 0 разів;+
— символ з’являється щонайменше двічі або більшу кількість разів;{n}
— символ з’являється задане число разів (n разів);{n,}
— символ з’являється задане число разів з умовою (n і більше);{n,m}
— не менше n, але не більше m разів.Квантифікатори можуть функціонувати в одному з трьох режимів: жадібному, наджадібному та ледачому. Базовий — жадібний режим — саме в ньому регулярні вирази працюють за замовчуванням.
У жадібному режимі програма буде шукати символи максимальної довжини, що збігаються в рамках рядка. Прохід здійснюється спочатку зліва направо, а потім справа наліво.
Наджадібний режим функціонує подібним чином, але він не використовує зворотний прохід (коли перевірка рядка йде справа наліво) — так званий реверсивний пошук. Це в деяких випадках дає змогу прискорити процес.
Лінивий режим шукає найкоротше входження символів, які збігаються, та задовольняє всім умовам, заданим у пошуковому шаблоні.
Давайте перейдемо до прикладів.
Ось такий вигляд має жадібний режим:
"A.+a"// Шукає максимальний за довжиною збіг у рядку. //Приклад жадібного квантифікатора import java.util.regex.Matcher; import java.util.regex.Pattern; public class Main { public static void main(String[] args) { Pattern p = Pattern.compile("a+"); Matcher m = p.matcher("aaa"); while (m.find()) System.out.println("Шаблон знайдено з " + m.start() + " to " + (m.end()-1))); } }
На виході буде таке:
Pattern found from 0 to 2
Наджадібний режим:
"А.++а"?"// Працює так само, як і жадібний режим, але не здійснює реверсивний пошук під час захоплення рядка. // Приклад наджадібного квантифікатора import java.util.regex.Matcher; import java.util.regex.Pattern; public class Main { public static void main(String[] args) { Pattern p = Pattern.compile("a++"); Matcher m = p.matcher("aaa"); while (m.find()) System.out.println("Шаблон знайдено з " + m.start() + " to " + (m.end()-1))); } }
На виході буде таке:
Pattern found from 0 to 2
Ледачий режим:
"A.+?a"// Шукає найкоротший збіг. //Приклад ледачого квантифікатора import java.util.regex.Matcher; import java.util.regex.Pattern; public class Main { public static void main(String[] args) { Pattern p = Pattern.compile("g+?"); Matcher m = p.matcher("ggg"); while (m.find()) System.out.println("Шаблон знайдено з " + m.start() + " to " + (m.end()-1))); } }
Висновок буде таким:
Pattern found from 0 to 0 Pattern found from 1 to 1 Pattern found from 2 to 2
У цьому розділі ми розглянемо деякі приклади, які допоможуть зрозуміти, як все це працює на ділі.
Раніше ми згадували, що один із базових прикладів — перевірка адреси електронної пошти на валідність і коректність. Тобто щоб стояв символ @
, крапка і доменна зона.
Ось так ця перевірка має вигляд у коді:
List emails = new ArrayList(); emails.add("name@gmail.com"); //Неправильний імейл: emails.add("@gmail.com"); String regex = "^[A-Za-z0-9+_.-]+@(.+)$"; Pattern pattern = Pattern.compile(regex); for(String email : emails){ Matcher matcher = pattern.matcher(email); System.out.println(email +" : "+ matcher.matches()); } Результат: name@gmail.com : true @gmail.com : false
Цей регулярний вираз перевіряє валідність номера телефону. Виглядає він так:
import java.util.regex.Matcher; import java.util.regex.Pattern; public class ValidatePhoneNumber { public static void main(String[] argv) { String sPhoneNumber = "605-8889999"; // String sPhoneNumber = "605-88899991"; // String sPhoneNumber = "605-888999A"; Pattern pattern = Pattern.compile("\d{3}-\d{7}"); Matcher matcher = pattern.matcher(sPhoneNumber); if (matcher.matches()) { System.out.println("Phone Number Valid"); } іще { System.out.println("Номер телефону повинен бути у формі XXX-XXXXXXXXX"); } } }
А ось клас для визначення валідності IP-адреси, записаної в десятковому вигляді:
private static boolean checkIP(String input) { return input.matches("((0|1\\d{0,2}|2([0-4][0-9]|5[0-5]))\\.){3}(0|1\\d{0,2}|2([0-4][0-9]|5[0-5]))"); }
За правилами, кількість відкритих дужок має збігатися з кількістю закритих, оскільки в іншому випадку порушується логіка роботи. Це перевіряється так:
private static boolean checkExpression(String input) { Pattern pattern = Pattern.compile("\\([\\d+/*-]*\\)"); Matcher matcher = pattern.matcher(input); do { input = matcher.replaceAll(""); matcher = pattern.matcher(input); } while (matcher.find()); return input.matches("[\\d+/*-]*"); }
Тепер спробуємо витягти дату з рядка. Це робиться таким чином:
private static String[] getDate(String desc) { int count = 0; String[] allMatches = new String[2]; Matcher m = Pattern.compile("(0[1-9]|[12][0-9]|3[01])[- /.](0[1-9]|1[012])[- /.](19|20)\\d\\\d").matcher(desc); while (m.find()) { allMatches[count] = m.group(); count++; } return allMatches; }
Перевірка:
public static void main(String[] args) throws Exception{ String[] dates = getDate("coming from the 25/11/2020 to the 30/11/2020"); System.out.println(dates[0]); System.out.println(dates[1]); }
Результат:
25/11/2020 30/11/2020
Як бачимо, регулярні вирази Java широко використовуються і дають змогу реалізовувати перевірки на коректність даних на льоту, хоча багато програмістів не люблять їх через складність. Але в умілих руках це потужний засіб для пошуку даних, роботи з рядками тощо.
Також ознайомитися з регулярними виразами можна у відео:
21-23 червня українське Ethereum ком’юніті ETHKyiv проведе офлайн-хакатон, який об'єднає талановитих розробників, студентів та ентузіастів…
Некомерційний навчальний заклад SET University запускає стипендійну програму за рядом напрямків. Українці та українки можуть…
За рік в рамках defense-tech кластеру Brave1 963 розробники створили 1 671 інноваційну розробку —…
Корпорація Google звільнила всю команду Python в США. Про це стало відомо з публікації Social.coop…
Команда з ліцею КПІ PL of KPI Igor Sikorsky перемогла на міжнародному хакатоні зі штучного…
Більше половини Go i Ruby розробників з досвідом 3+ роки найняли на $5000 або більше.…