Към съдържанието
wannacode

Регулярни изрази

Проблем

Шестнайсет реда за една дата

Формулярът за резервация иска дата във вида дд.мм.гггг. Проверяваш го символ по символ:

static boolean isDate(String s) {
if (s.length() != 10) {
return false;
}
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (i == 2 || i == 5) {
if (c != '.') {
return false;
}
} else if (c < '0' || c > '9') {
return false;
}
}
return true;
}
System.out.println(isDate("05.03.2026"));
System.out.println(isDate("5.3.2026"));
System.out.println(isDate("05/03/2026"));
Изход
true
false
false

Шестнайсет реда, а методът проверява само формата: две цифри, точка, две цифри, точка, четири цифри. Ако искаш да приемаш и / вместо точка, или ден с една цифра, ще добавиш още толкова. Номер на телефон, имейл или регистрационен номер ще ти струват по още един такъв метод.

Идеята

Шаблон вместо код

Описваш как изглежда текстът, а Java проверява:

static boolean isDate(String s) {
return s.matches("\\d{2}\\.\\d{2}\\.\\d{4}");
}
System.out.println(isDate("05.03.2026"));
System.out.println(isDate("5.3.2026"));
System.out.println(isDate("05/03/2026"));
Изход
true
false
false

Шаблонът се чете отляво надясно: \d е цифра, {2} значи „два пъти“, \. е точка. Такъв шаблон се казва регулярен израз (regular expression, regex).

matches връща true, ако целият текст отговаря на шаблона, от първия до последния символ.

Защо в кода пише \\d, а не \d? Обратната наклонена черта е специална и за Java низа, затова се пише двойна. Ще го видим подробно след малко. Засега чети \\ в кода като едно \ в шаблона.

Виж го

Как шаблонът търси

replaceAll не проверява целия текст, а търси парчетата, които отговарят на шаблона, и ги заменя. Ето как минава по реда:

String line = "120 € на 05.03.2026, падеж 15.04.2026";
String masked = line.replaceAll("\\d{2}\\.\\d{2}\\.\\d{4}", "[дата]");
System.out.println(masked);
Стъпка по стъпка, с думи
  1. Редът е поредица от знаци, всеки със своя позиция.
  2. От позиция 0: „1“ и „2“ са цифри, но после идва „0“ вместо точка. Опитът пада.
  3. Нов опит от всеки следващ знак: „20 “, „0 “, „ €“… все неуспехи.
  4. От позиция 9 всичките десет части пасват: първото съвпадение.
  5. Търсенето продължава след съвпадението, от запетаята, и намира второто.
  6. replaceAll заменя двете съвпадения с „[дата]“.

Шаблонът се пробва от всяка позиция поред. Щом някоя част не съвпадне, опитът пада и започва нов от следващия символ. Затова 120 не мина: две цифри, а после 0 вместо точка. Когато намери съвпадение, търсенето продължава след него.

Идеята

Класове от символи

В квадратни скоби изброяваш кои символи са позволени на едно място. Регистрационен номер на кола е една или две букви, четири цифри и две букви:

String[] plates = { "СА1234ВХ", "В7788КК", "СА12345ВХ", "са1234вх" };
for (String plate : plates) {
System.out.println(plate + " " + plate.matches("[А-Я]{1,2}\\d{4}[А-Я]{2}"));
}
Изход
СА1234ВХ true
В7788КК true
СА12345ВХ false
са1234вх false
Шаблон Съвпада с един символ
[АВЕ] А, В или Е
[0-9], [А-Я] от интервала: цифра, главна буква на кирилица
[^0-9] всичко освен цифра (^ в началото на скобите)
\d цифра, същото като [0-9]
\s интервал, табулация, нов ред
\w латинска буква, цифра или _
. какъвто и да е символ (без нов ред)

Главните букви обръщат смисъла: \D е „не цифра“, \S „не интервал“, \W „не \w“.

Внимавай с \w: в Java по подразбиране той е само латиница.

System.out.println("Sofia".matches("\\w+"));
System.out.println("София".matches("\\w+"));
System.out.println("София".matches("(?U)\\w+"));
System.out.println("София".matches("[А-Яа-я]+"));
Изход
true
false
true
true

За текст на кирилица пиши [А-Яа-я] или сложи (?U) в началото на шаблона: тогава \w познава буквите на всички езици.

По-точно, за любопитните
  • [А-Я] е интервал по кодовете на символите. В него влизат и руските Ы и Э, но не и Ё, която стои отделно в таблицата. За българския текст [А-Яа-я] стига. Ако те интересува и „ѝ“, добави го: [А-Яа-яѝЍ].
  • Истинските български номера ползват само 12 букви, които изглеждат еднакво на кирилица и латиница: [АВЕКМНОРСТУХ].

Пробвай

Точката

Някой е забравил обратната черта пред точките:

Какво ще отпечата програмата?

String pattern = "\\d{2}.\\d{2}.\\d{4}";
System.out.println("05.03.2026".matches(pattern));
System.out.println("05-03-2026".matches(pattern));
System.out.println("05.03.26".matches(pattern));
Отговор

Идеята

Колко пъти

След символ или клас казваш колко пъти може да се повтори. Това е квантификатор:

Квантификатор Значи
? нула или едно (може да го има, може да го няма)
* нула или повече
+ едно или повече
{4} точно 4
{1,2} от 1 до 2
{2,} 2 или повече

Мобилен номер започва с 0 или +359, после 87, 88 или 89 и още 7 цифри:

String mobile = "(\\+359|0)8[789]\\d{7}";
System.out.println("0888123456".matches(mobile));
System.out.println("+359877654321".matches(mobile));
System.out.println("0888 123 456".matches(mobile));
System.out.println("088812345".matches(mobile));
Изход
true
true
false
false

| значи „или“. Скобите държат \+359|0 заедно: без тях „или“ би разделило целия шаблон на две. Скобите правят още неща, които ще видим в следващия урок. + в началото е със \, защото иначе е квантификатор.

Квантификаторът важи и за цяла група в скоби. Цена с дробна част или без нея:

String price = "\\d+([.,]\\d{1,2})? €";
System.out.println("12 €".matches(price));
System.out.println("12.5 €".matches(price));
System.out.println("12,50 €".matches(price));
System.out.println(".50 €".matches(price));
Изход
true
true
true
false

([.,]\d{1,2})? значи: „точка или запетая и една-две цифри“, а целият този блок може да го няма.

Идеята

Две обратни черти

Шаблонът минава през два слоя. Първо Java чете низа в кавичките, после regex чете шаблона.

В Java низ \ започва специален символ: \n е нов ред, \" е кавичка. \d не е такъв, затова програмата не се компилира:

String date = "05.03.2026";
System.out.println(date.matches("\d{2}\.\d{2}\.\d{4}"));
Грешка при компилиране
Main.java:4: error: illegal escape character
System.out.println(date.matches("\d{2}\.\d{2}\.\d{4}"));
^

Затова всяко \ от шаблона в Java се пише \\:

String date = "05.03.2026";
System.out.println(date.matches("\\d{2}\\.\\d{2}\\.\\d{4}"));
System.out.println("C:\\Temp".matches("C:\\\\Temp"));
Изход
true
true
Искаш в шаблона Пишеш в Java
\d (цифра) "\\d"
\. (истинска точка) "\\."
\\ (истинска обратна черта) "\\\\"

Символите, които трябва да са със \, когато ги искаш буквално, са . * + ? ( ) [ ] { } | ^ $ \.

Пробвай

Намери грешката

Програмата трябва да отпечата „12 € и 50 цента“, а спира така:
Грешка
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
at Main.main(Main.java:5)

Избери реда с грешката.

Идеята

Замяна и разделяне по шаблон

replaceAll и split приемат шаблон. Така чистиш и разбиваш текст, който хората пишат както им падне:

String address = "Доставка: ул. Шипка 12, вход Б";
System.out.println(address.replaceAll("\\s+", " "));
String[] items = "хляб, мляко;кафе чай".split("[,;\\s]+");
System.out.println(items.length + ": " + String.join(" | ", items));
System.out.println("Код SUMMER10, код summer25".replaceAll("(?i)summer\\d+", "***"));
Изход
Доставка: ул. Шипка 12, вход Б
4: хляб | мляко | кафе | чай
Код ***, код ***
  • \s+ е „един или повече интервала“: всяка група интервали става един.
  • [,;\s]+ е „запетаи, точки и запетаи или интервали, колкото и да са“: разделителят може да е всеки от тях.
  • (?i) в началото е флаг: шаблонът не гледа главни и малки букви.

replace (без All) също заменя всички появи, но търси обикновен текст, не шаблон. Ако не ти трябва шаблон, ползвай него: не трябва да мислиш за точки и обратни черти.

\b е граница на дума: мястото между буква и нещо, което не е буква. С него заменяш само цели думи. И тук (?U) е задължителен за кирилица:

String text = "стар и старо";
System.out.println(text.replaceAll("стар", "нов"));
System.out.println(text.replaceAll("\\bстар\\b", "нов"));
System.out.println(text.replaceAll("(?U)\\bстар\\b", "нов"));
Изход
нов и ново
стар и старо
нов и старо

Без \b „старо“ става „ново“. С \b, но без (?U), Java не вижда кирилицата като букви и не заменя нищо.

По-точно, за любопитните
  • \B е обратното на \b: място вътре в дума. "(?U)\\Bстар" намира „стар“ само когато не е в началото на дума: в „застаря“, но не в „старо“.
  • ^ е началото на текста, а $ краят: replaceAll("^0+", "") маха водещите нули. При matches не ти трябват, защото той и без това гледа целия текст.
  • Флаговете може да се дадат и като константи на класа Pattern: Pattern.CASE_INSENSITIVE е същото като (?i), Pattern.UNICODE_CHARACTER_CLASS като (?U). Класа Pattern ще ползваме в следващия урок.
  • "a|b".split("|") е капан като точката: | е „или“ и шаблонът е празен. Пиши split("\\|").

Пробвай

Пощенски код

Българският пощенски код е точно четири цифри. Какво липсва, за да отпечата програмата true за "1000" и false за "100" или "10000"?

String code = "1000";
System.out.println(code.matches(""));

Упражнение

Проверки с шаблон

Реши задачите в редактора си. Чети редове, докато има: while (in.hasNextLine()). В урока „Файлове“ ще четеш същите редове от файл.

1. Дата. Провери всяка дата дали е във вида дд/мм/гггг. Денят е от 01 до 31, а месецът от 01 до 12. Подсказка: денят е 0[1-9] или [12]\d, или 3[01].

Вход
01/01/2020
32/01/2020
15/05/2021
Изход
01/01/2020 - Валидна
32/01/2020 - Невалидна
15/05/2021 - Валидна

2. Мобилен номер. Хората пишат номера с интервали и тирета. Махни ги с replaceAll и провери това, което остава, с шаблона за мобилен номер от урока.

Вход
0888 123 456
+359 87 765 4321
02 812 3456
0899-12-345
Изход
0888 123 456 - валиден
+359 87 765 4321 - валиден
02 812 3456 - невалиден
0899-12-345 - невалиден

3. Имейл. Провери дали всеки ред прилича на имейл: една или повече латински букви, цифри, ., + или -, после @, после име на домейн с поне една точка (firma.bg, mail.firma.bg).

Вход
elena.koleva@firma.bg
elena@firma
@firma.bg
elena koleva@firma.bg
Изход
elena.koleva@firma.bg - да
elena@firma - не
@firma.bg - не
elena koleva@firma.bg - не

4. Списък за пазаруване. Продуктите са разделени със запетаи, точки и запетаи, вертикални черти или интервали, понякога по няколко наведнъж. Отпечатай ги номерирани.

Вход
хляб, мляко;кафе чай|сирене
Изход
1. хляб
2. мляко
3. кафе
4. чай
5. сирене
По-точно, за любопитните
  • Шаблонът за дата приема и 31/02/2026. Дали денят съществува в месеца, regex не може да каже разумно. За това има java.time.LocalDate, а шаблонът е само първото сито.
  • Пълното правило за имейл адрес (RFC 5322) позволява кавички, коментари и интервали в определени места. Шаблон, който го покрива изцяло, е стотици символи и никой не го поддържа. Истинската проверка е да изпратиш писмо с връзка за потвърждение.

Още задачи

  • Парола. Провери дали паролата е поне 8 символа и има поне една цифра. Направи го с два matches: ".{8,}" и ".*\\d.*".
  • Час. Провери час във вида чч:мм от 00:00 до 23:59.
  • Само цифри. От "Тел.: +359 (88) 812-34-56" остави само цифрите и + с един replaceAll и [^...].

Тест

Провери се

1. Какво проверява "2026".matches("\\d{4}")?
2. С кое съвпада . (точка) в шаблон?
3. Кое значи „едно или повече“?
4. Съвпада ли "София" с \w+ в Java?
5. Колко части дава "хляб, мляко;кафе".split("[,;]\\s*")?

Създаден от Иво Гергов като част от кампанията на Evo Tech за достъпно обучение по програмиране ·