Рубріки: Теорія

Регулярні вирази RegEx у Java: приклади і опис роботи

Андрей Галадей

Регулярні вирази у мовах програмування — потужний, але складний в опануванні інструмент. Тому не дивно, що багато хто відкладає його на потім або намагається використовувати по мінімуму.

Але це найкращий спосіб опрацювання текстів, а також метод розв’язання завдань, які пов’язані з пошуком, редагуванням, заміною тексту у файлах. Тому регулярні вирази варто знати. Сьогодні мова піде про регулярні вирази в популярній кросплатформенній мові Java.

Регулярні вирази в Java

Регулярні вирази в Java — це певні послідовності символів, за допомогою яких можна обробляти рядки тексту за спеціальними синтаксичними шаблонами.

Приклад регулярних виразів має такий вигляд:

String regex="java"; // шаблон рядка "java";
String regex="\\d{3}"; // шаблон рядка з трьох цифрових символів;

Ця методика дає змогу дізнатися, чи входить певний символ або їхня послідовність у рядок. Інакше кажучи, це методика для обробки рядків.

Наприклад, вона може застосовуватися, якщо потрібно перевірити коректність введення електронної пошти. У ньому обов’язково має бути присутнім символ @, адреса домену, крапка після нього і доменна зона. Тобто система перевіряє, щоб адреса виглядала як user@gmail.com (сама адреса без лапок, зрозуміло), а не user.gmail.com або user@gmail,com, або навіть user@gmail.com.

  • Matcher — за його допомогою рядок перевіряється за шаблоном, що дає змогу знайти збіги в рядках, які подаються на вхід. Наприклад, в адресі електронної пошти.
  • Pattern — видає вже скомпільовану версію регулярного виразу.
  • PatternSyntaxException — у межах цього класу надаються винятки, які не перевіряються. За допомогою цього методу можна шукати. Детальніше поговоримо нижче.

Методи класу Pattern

Клас Java, який називається Pattern — це базове рішення для роботи з регулярними виразами в Java. Саме його використовують при підключенні RegEx до коду програми.

Цей клас можна використовувати двома різними способами (методами). По-перше, це Pattern.matches(). Він застосовується для швидкої перевірки тексту на відповідність заданому регулярному виразу. Але він перевіряє тільки один текст або рядок.

По-друге, це Pattern.compile() — скомпільований екземпляр Pattern. Ось його вже можна використовувати кілька разів, щоб скласти регулярний вираз для кількох текстів.

Наприклад:

String regex="java"; // шаблон рядка "java";
String regex="\\d{3}"; // шаблон рядка з трьох цифрових символів;

Методи класу Matcher

Ще один клас 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

Ще один клас — 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());
      }
   }
}

Створення регулярних виразів у Java

Для створення регулярного виразу задіюються рядки або об’єкти класу String. Але далеко не кожен рядок автоматично може скомпілюватися в регулярний вираз. Він має бути відповідним чином написаний — з використанням синтаксису, який визначається в офіційних специфікаціях Java.

У цьому разі йдеться про використання букв, цифр і метасимволів, які мають спеціальне значення саме в рамках регулярних виразів.

Синтаксис регулярних виразів

Синтаксичні конструкції регулярних виразів, як сказано вище, охоплюють літерні, цифрові та метасимволи.

Серед них:

  • Літерали — цим словом називають звичайні літери, які не мають іншого позначення. Тобто це стандартний алфавіт. Ці символи застосовуються для пошуку — приміром, якщо шаблон містить слово dog, то літерали задіють для пошуку, а у видачі опиняться результати, що починаються з поєднання літер dog. Тобто там будуть і dog, і doghunter.
  • Метасимволи. Це, наприклад, квадратні дужки [] і циркумфлекс (). Також іноді їх можна називати спеціальними символами. Наприклад, якщо написати ( [ dog]] ), то система буде шукати все, що не містить у собі поєднання букв dog.

Для пошуку меж рядків, слів і тому подібних даних використовуються такі метасимволи:

  • ^ — метасимвол початку оброблюваного рядка;
  • $ — метасимвол закінчення оброблюваного рядка;
  • - — так задається метасимвол, який має бути поза дужками, причому він повинен бути тільки один;
  • \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("НЕ Збігається");
}
}
}

На виході отримаємо такий результат:

  • Знайдено значення: Хрещення Київської Русі відбулося 988 року! Чи не так?
  • Знайдено значення: Хрещення Київської Русі відбулося у 98
  • Знайдено значення: 8

Квантифікатори, жадібний, наджадібний і ледачий режими

Крім символів у регулярних виразах застосовуються квантифікатори. Це обмежувачі, за допомогою яких задається частота появи певного символу або декількох. Їх потрібно записувати після самих символів.

Основні квантифікатори:

  • ? — символ не з’являється зовсім або ж з’являється лише раз;
  • - — символу зовсім немає в рядку або він з’являється, або з’являється більше ніж 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

Приклади використання регулярних виразів у Java

У цьому розділі ми розглянемо деякі приклади, які допоможуть зрозуміти, як все це працює на ділі.

Перевірка введеної адреси електронної пошти на коректність

Раніше ми згадували, що один із базових прикладів — перевірка адреси електронної пошти на валідність і коректність. Тобто щоб стояв символ @, крапка і доменна зона.

Ось так ця перевірка має вигляд у коді:

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-адресу) на правильність

А ось клас для визначення валідності 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 широко використовуються і дають змогу реалізовувати перевірки на коректність даних на льоту, хоча багато програмістів не люблять їх через складність. Але в умілих руках це потужний засіб для пошуку даних, роботи з рядками тощо.

Також ознайомитися з регулярними виразами можна у відео:

Останні статті

Українців запрошують на перший безплатний глобальний ETHKyiv хакатон

21-23 червня українське Ethereum ком’юніті ETHKyiv проведе офлайн-хакатон, який об'єднає талановитих розробників, студентів та ентузіастів…

30.04.2024

SET University пропонує військовим та їх родинам безплатну магістратуру за 4 спеціальностями

Некомерційний навчальний заклад SET University запускає стипендійну програму за рядом напрямків. Українці та українки можуть…

29.04.2024

Від РЕБ до «плащів-невидимок»: за рік в рамках Brave1 створили 1 671 інноваційну розробку

За рік в рамках defense-tech кластеру Brave1 963 розробники створили 1 671 інноваційну розробку —…

29.04.2024

Треба «дешева» робоча сила: Google повністю звільнила команду Python в США

Корпорація Google звільнила всю команду Python в США. Про це стало відомо з публікації Social.coop…

29.04.2024

Українські школярі перемогли на міжнародному ШІ-хакатоні зі застосунком для вивчення жестової мови

Команда з ліцею КПІ PL of KPI Igor Sikorsky перемогла на міжнародному хакатоні зі штучного…

29.04.2024

Більше 50% Go i Ruby розробників з досвідом 3+ роки найняли на $5000. PHP — на самому дні

Більше половини Go i Ruby розробників з досвідом 3+ роки найняли на $5000 або більше.…

26.04.2024