Liigu peamise sisu juurde

Regulaaravaldised

Sissejuhatus

Regulaaravaldised (regex) on mustriõhine viis tekstist info leidmiseks, valideerimiseks ja teksti muundamiseks. Need on kasulikud olukordades, kus on vaja kontrollida, kas tekst vastab mingile kindlale formaadile (näiteks e-posti aadressid), tekstist kindlate osade eraldamiseks või alamsõnede otsinguks ja asendamiseks.

Regulaaravaldised said kaetud aines "ITI0102 - Programmeerimise algkursus", põhjalikemate materjalidega saate tutvuda PyDocis. Kuna regulaaravaldiste süntaks on enam-vähem sarnane üle kõikide programmeerimis keelte, siis antud peatükk keskendub rohkem Java spetsiifikale.

Süntaksi meeldetuletus

Tuletame esmalt kiirelt meelde regulaaravaldiste süntaksi. Siin on lühike ülevaade kõige levinumatest ehitusplokkidest:

SümbolTähendusNäidismusterMida näiteks tuvastab
.Suvaline sümbola.cabc, a1c, a c
\dNumber (0-9)\d{3}123, 007
\wSõna (täht, number, _)\w+hello, var_1
\sTühimärk (tühik, tab, reavahetus)a\sba b
*Null või rohkem kordusiab*cac, abc, abbc
+Üks või rohkem kordusiab+cabc, abbc
?Null või üks kordustcolou?rcolor, colour
{n}Täpselt n korda\d{4}2025
{n,m}n kuni m korda\d{2,4}12, 123, 1234
[abc]Üks märk ette antud märkide hulgast[aeiou]a, e, i
[^abc]Ükskõik milline märk peale ette antud märkide[^0-9]a, !
^Sõne algus^HelloHello world
$Sõne lõppworld$Hello world
(...)Grupp(\d{2})-(\d{2})12-31 (grupid: 12, 31)
|Alternatiiv (VÕI)cat|dogcat, dog

Escape character'id Javas

Enamus keeltes kirjutatakse numbri sobitamiseks \d. Javas juba kasutatakse sõnedes \ erimärkide jaoks (\n, \t jne), seega regulaaravaldiste puhul tuleb \ sümbolit kahekordselt kirjutada.

Pythoni näide:

pattern ? r"\d+\.\d+"

Java näide:

String pattern = "\\d+\\.\\d+";  // matches "3.14", "0.5"

Mõlemad mustrid käituvad samamoodi, lihtsalt kirjapilt erineb korduste tõttu.

Veel näiteid:

Mida otsitakseRegulaaravaldisJava sõne
Arvu\d"\\d"
Punkti (.)\."\\."
Sõnapiire\b"\\b"
Langevat kriipsu\\"\\\\"

Sõne meetodid, mis kasutavad regulaaravaldisi

Java String klassil on mitu meetodid, mis võtavad sisendina vastu regulaaravaldisi. Lihtsamate operatsioonide jaoks peaks ainuüksi nendest piisama.

matches()

Kontrollib, kas sõne vastab mustrile terviklikult:

String email = "student@taltech.ee";
boolean valid = email.matches("[\\w.+-]+@[\\w-]+\\.[a-z]{2,}"); // true

String phone = "+372 5123 4567";
boolean isPhone = phone.matches("\\+\\d{3} \\d{4} \\d{4}"); // true
hoiatus

matches() kontrollib vastavust kogu sõne ulatuses. See sisuliselt käitub, nagu oleks muster mähitud ^...$ sisse:

String text = "Hello 123 World";

text.matches("\\d+"); // false — the whole string is not just digits
text.matches(".*\\d+.*"); // true — now the pattern accounts for surrounding text

Osalise vastavuse kontrollimiseks tuleks kasutada Pattern ja Matcher klasse, millega tutvume lähemalt allpool.

split()

Viilutab sõne regulaaravaldise järgi ning tagastab massiivi:

String csv = "apple,banana,,cherry";
String[] fruits = csv.split(",");
// ["apple", "banana", "", "cherry"]

// Split by one or more whitespace characters
String text = "Hello World Java";
String[] words = text.split("\\s+");
// ["Hello", "World", "Java"]

// Split by multiple delimiters (comma, semicolon, or pipe)
String data = "one,two;three|four";
String[] parts = data.split("[,;|]");
// ["one", "two", "three", "four"]

Võimalik on ka lisada juurde piir, et mitu viilutust maksimaalselt tehakse:

String text = "a:b:c:d:e";

String[] all = text.split(":"); // ["a", "b", "c", "d", "e"]
String[] first3 = text.split(":", 3); // ["a", "b", "c:d:e"]

replaceAll() ja replaceFirst()

Asendab sõnes alamsõned mõne teise sõnega, mis vastavad ette antud mustrile:

// Remove all non-alphanumeric characters
String messy = "Hello, World! (2025)";
String clean = messy.replaceAll("[^a-zA-Z0-9 ]", ""); // "Hello World 2025"

// Replace only the first match
String text = "cat and cat";
String result = text.replaceFirst("cat", "dog"); // "dog and cat"

Samuti on asendamisel võimalik kasutada gruppeerimist, näiteks:

// Reformat a date from DD.MM.YYYY to YYYY-MM-DD
String date = "31.12.2025";
String reformatted = date.replaceAll("(\\d{2})\\.(\\d{2})\\.(\\d{4})", "$3-$2-$1");
// "2025-12-31"

Pattern ja Matcher

Keerukamate operatsioonide jaoks saab kasutada java.util.regex paki Pattern ja Matcher klasse.

import java.util.regex.Pattern;
import java.util.regex.Matcher;

Milleks neid kasutada

String-i meetodid (matches(), split(), replaceAll()) kompileerivad musti iga kord uuesti. Kui kasutad üht ja sama mustrit korduvalt, on efektiivsem seda ainult kompileerida ühel korral ning edaspidi taaskasutada.

// Compiles the pattern every time — fine for a single check
input.matches("\\d+");

// Compiles once, reuses many times — better for repeated use
Pattern pattern = Pattern.compile("\\d+");
pattern.matcher(input1).matches();
pattern.matcher(input2).matches();
pattern.matcher(input3).matches();

Lisaks paremale jõudlusele võimaldavad Pattern ja Matcher ka:

  • leida mitu vastet ühes sõnes
  • kasutada gruppe
  • saada vaste täpne asukoht
  • kasutada lippe, nagu näiteks tõstutundetu sobitamine (ehk mustri konfigureerimine)

Põhiline töövoog

Tüüpiline töövoog nende klassidega näeb välja järgmine:

  • Esmalt kompileeri muster
  • Seejärel loo Matcher sõne jaoks läbi mustri
  • Viimaseks küsi matcher'ilt tulemust

Näiteks:

Pattern pattern = Pattern.compile("\\d+");
Matcher matcher = pattern.matcher("Order 42, Item 7");

while (matcher.find()) {
System.out.println(matcher.group()); // "42", then "7"
}

find() vs matches() vs lookingAt()

MeetodMida kontrollib
matches()Kas terve sisend on vastavuses regulaaravaldisele
find()Nullib eelnevad tulemused ning üritab leida sisendist järgmist alamosa, mis on vastavuses regulaaravaldisega. Alustab kas algusest või eelmise otsingu lõpust.
lookingAt()Üritab leida terve sisendi raames mingit osa, mis on vastavuses regulaaravaldisega. Alustab algusest.
Pattern pattern = Pattern.compile("\\d+");
Matcher matcher = pattern.matcher("123 abc");

matcher.matches(); // false — the whole string is not just digits
matcher.lookingAt(); // true — the string starts with digits
matcher.find(); // true — digits are found somewhere in the string

start() ja end()

Kui find() meetod leiab edukalt järgmise vaste, siis on võimalik kätte saada selle täpset asukohta sõnes läbi start() ja end meetodite:

Pattern pattern = Pattern.compile("\\w+@\\w+\\.\\w+");
Matcher matcher = pattern.matcher("Contact info@taltech.ee for help");

if (matcher.find()) {
System.out.println(matcher.group()); // "info@taltech.ee"
System.out.println(matcher.start()); // 8
System.out.println(matcher.end()); // 23
}

Grupeerimine

Mustri sulgudega ümbritsemine ((...)) võimaldab tulemusi gruppideks jagada. Positiivse vaste korral on võimalik tulemusi pärida vastavalt grupi indeksile:

Pattern pattern = Pattern.compile("(\\d{2})\\.(\\d{2})\\.(\\d{4})");
Matcher matcher = pattern.matcher("Date: 31.12.2025");

if (matcher.find()) {
String full = matcher.group(0); // "31.12.2025" (entire match)
String day = matcher.group(1); // "31"
String month = matcher.group(2); // "12"
String year = matcher.group(3); // "2025"
}

Parema loetavuse nimel on võimalik gruppidele ka nimesid määrata:

Pattern pattern = Pattern.compile("(?<day>\\d{2})\\.(?<month>\\d{2})\\.(?<year>\\d{4})");
Matcher matcher = pattern.matcher("Date: 31.12.2025");

if (matcher.find()) {
String day = matcher.group("day"); // "31"
String month = matcher.group("month"); // "12"
String year = matcher.group("year"); // "2025"
}

Lipud

Lisaks mustrile endale võtab Pattern.compile() vastu lippe, mis täpsustavad, kuidas muster käitub:

// Case-insensitive matching
Pattern pattern = Pattern.compile("hello", Pattern.CASE_INSENSITIVE);
pattern.matcher("Hello World").find(); // true
pattern.matcher("HELLO WORLD").find(); // true

// Combine multiple flags with |
Pattern pattern = Pattern.compile("hello world",
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);

Enim-levinud lipud:

LippMõju
Pattern.CASE_INSENSITIVEa sobitub nii a kui ka A-ga
Pattern.MULTILINE^ ja $ sobituvad iga rea alguse ja lõpuga, mitte aiult kogu sõne alguse ja lõpuga
Pattern.DOTALL. sobitub samuti reavahetuse märkidega