Регулярни изрази
Проблем
Шестнайсет реда за една дата
Формулярът за резервация иска дата във вида дд.мм.гггг. Проверяваш го символ по символ:
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"));truefalsefalseШестнайсет реда, а методът проверява само формата: две цифри, точка, две цифри, точка, четири цифри. Ако искаш да приемаш и / вместо точка, или ден с една цифра, ще добавиш още толкова. Номер на телефон, имейл или регистрационен номер ще ти струват по още един такъв метод.
Идеята
Шаблон вместо код
Описваш как изглежда текстът, а 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"));truefalsefalseШаблонът се чете отляво надясно: \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);Стъпка по стъпка, с думи
- Редът е поредица от знаци, всеки със своя позиция.
- От позиция 0: „1“ и „2“ са цифри, но после идва „0“ вместо точка. Опитът пада.
- Нов опит от всеки следващ знак: „20 “, „0 “, „ €“… все неуспехи.
- От позиция 9 всичките десет части пасват: първото съвпадение.
- Търсенето продължава след съвпадението, от запетаята, и намира второто.
- 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("[А-Яа-я]+"));truefalsetruetrueЗа текст на кирилица пиши [А-Яа-я] или сложи (?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));Без \ точката значи „какъвто и да е символ“, затова тирето също минава и 05-03-2026 е вярно. Третият текст има само две цифри за година, а шаблонът иска четири. Проверката изглежда, че работи, докато не дойде грешен вход. Затова пробвай шаблона и с текстове, които не трябва да минат.
Идеята
Колко пъти
След символ или клас казваш колко пъти може да се повтори. Това е квантификатор:
| Квантификатор | Значи |
|---|---|
? |
нула или едно (може да го има, може да го няма) |
* |
нула или повече |
+ |
едно или повече |
{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));truetruefalsefalse| значи „или“. Скобите държат \+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));truetruetruefalse([.,]\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"));truetrue| Искаш в шаблона | Пишеш в Java |
|---|---|
\d (цифра) |
"\\d" |
\. (истинска точка) |
"\\." |
\\ (истинска обратна черта) |
"\\\\" |
Символите, които трябва да са със \, когато ги искаш буквално, са . * + ? ( ) [ ] { } | ^ $ \.
Пробвай
Намери грешката
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0 at Main.main(Main.java:5)Избери реда с грешката.
split приема регулярен израз. "." е „всеки символ“, затова всеки символ е разделител и между тях няма нищо. Празните части накрая се махат и масивът остава с дължина 0, а грешката излиза чак на следващия ред, при parts[0]. Поправката е split("\\.").Идеята
Замяна и разделяне по шаблон
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(""));\\d{4}: точно четири цифри. Понеже matches проверява целия текст, три или пет цифри не минават.Упражнение
Проверки с шаблон
while (in.hasNextLine()). В урока „Файлове“ ще четеш същите редове от файл.1. Дата. Провери всяка дата дали е във вида дд/мм/гггг. Денят е от 01 до 31, а месецът от 01 до 12. Подсказка: денят е 0[1-9] или [12]\d, или 3[01].
01/01/202032/01/202015/05/202101/01/2020 - Валидна32/01/2020 - Невалидна15/05/2021 - Валидна2. Мобилен номер. Хората пишат номера с интервали и тирета. Махни ги с replaceAll и провери това, което остава, с шаблона за мобилен номер от урока.
0888 123 456+359 87 765 432102 812 34560899-12-3450888 123 456 - валиден+359 87 765 4321 - валиден02 812 3456 - невалиден0899-12-345 - невалиден3. Имейл. Провери дали всеки ред прилича на имейл: една или повече латински букви, цифри, ., + или -, после @, после име на домейн с поне една точка (firma.bg, mail.firma.bg).
elena.koleva@firma.bgelena@firma@firma.bgelena koleva@firma.bgelena.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и[^...].
Тест