Wyrażenia regularne
Wyrażenia regularne (ang. regular expressions — regex) to wzorce łańcuchów znaków, umożliwiające sprawdzenie czy dany łańcuch znaków jest zgodny z określonym formatem. Możesz np. sprawdzić, czy podany przez użytkownika łańcuch znaków jest liczbą o określonej liczbie cyfr, numerem telefonu, adresem e-mail, adresem IP, datą, numerem konta bankowego, nazwą pliku razem z poprawnie zapisaną ścieżką itp. Korzystając z wyrażenia regularnego możesz także wykonać wiele różnych operacji - np. wyszukać określone wzorce w tekście, analizować dzienniki zdarzeń itp.
Składnia wyrażeń regularnych w Javie jest bardzo podobna do składni wyrażeń regularnych w innych językach.
Wyrażenie regularne składa się z sekwencji literałów (liter, cyfr i znaków specjalnych) pogrupowanych przy użyciu nawiasów i opatrzonych kwantyfikatorami (określającymi liczbę wystąpień literałów). W wyrażeniach regularnych mogą występować operatory logiczne oraz znaki specjalne o specjalnym przeznaczeniu (więcej informacji na ten temat znajdziesz niżej).
Przykłady i występujące niżej tabele są zapożyczone z serwisu https://kobietydokodu.pl/4-wyrazenia-regularne/, gdzie wyrażenia regularne są opisane w bardzo przystępny sposób:
- abcd - zwykły tekst - takie wyr. reg. dopasuje dokładnie taki sam tekst "abcd";
- a+bcd - kwantyfikator + powoduje, że to wyr. reg. dopasuje "abcd", "aabcd", "aaabcd", "aaaabcd" itd.;
- [ab][ab] - dwa zakresy literałów - to wyr. reg. dopasuje "aa", "ba", "ab", "bb";
- a(bc)* - dwie grupa literałów - to wyr. reg. dopasuje "a", "abc", "abcbc", "abcbcbc" itd.;
- . - kropka oznacza dowolny znak;
ale:
- [.] - kropka w zakresie literałów oznacza dowolną literę lub cyfrę;
- [0-9]\.[0-9] - zakresy literałów oraz znak specjalny "\"
(dzięki użyciu znaku specjalnego \ kropka jest tutaj traktowana jako znak
kropki) - to wyr. reg. dopasuje liczby z jedną cyfrą dziesiętną i jedną cyfrą
po przecinku (kropce);
ale:
- [0-9].[0-9] - zakresy literałów oraz znak specjalny "." (kropka to znak specjalny oznaczający dowolny znak) - to wyr. reg. dopasuje wszystkie łańcuchy, w których występuje cyfra, dowolny znak i cyfra - np. "0a0", "0 0", "0.0" itp.;
- dotyczą literału występującego przed kwantyfikatorem,
- jeśli przed kwantyfikatorem występuje spacja, kwantyfikator określa liczbę spacji.
| Kwantyfikator | Znaczenie | Przykład | Przykład dopasowuje |
|---|---|---|---|
| * | Zero lub więcej wystąpień | a*b | ab, b, aab, aaaaaab, aaab (i podobne) |
| + | Jedno lub więcej wystąpień | a+b | ab, aab, aaaaaaab, aab (i podobne) |
| ? | Zero lub jedno wystąpienie | a?b | ab, b |
| {n,m} | Co najmniej n i maksymalnie m wystąpień | a{1,4}b | ab, aab, aaab, aaaab |
| {n,} | Co najmniej n wystąpień | a{3,}b | aaab, aaaab aaaaab (i podobne) |
| {n} | Dokładnie n wystąpień | a{3}b | aaab |
- cały zakres jest traktowany jako jeden literał, przy czym może on przyjąć jedną z wartości z danego zbioru.
| Wyrażenie | Opis |
|---|---|
| [abcde] | Jedna z liter: a, b, c, d lub e |
| [a-zA-Z] | Jedna z liter od a do Z mała lub duża |
| [a-c3-5] | Litera od a do c lub cyfra od 3 do 5 |
| [a-c14-7] | Litera od a do c lub cyfra 1 lub cyfra od 4 do 7 |
| [a-z&&[^bc]] | Litery od a do z z wyjątkiem b i c == [ad-z] |
| [a-z&&[^d-f]] | Litery od a do c lub od g do z == [a-cg-z] |
| [abc\[\]] | Litera a lub b lub c lub nawias kwadratowy (dlaczego dodaliśmy też odwrócone ukośniki, czytaj dalej) |
| [.] | Dowolna litera lub cyfra (czytaj dalej na temat Znaki specjalne w wyrażeniach regularnych) |
| Wyrażenie | Opis |
|---|---|
| a(bcd)* | litera a oraz ciąg bcd zero lub więcej razy |
| a(b(cd)?)+ | litera a, a następnie jedno lub więcej powtórzeń b lub bcd |
| Wyrażenie | Opis |
|---|---|
| . | dowolny znak |
| \ | ten znak oznacza, że następny znak ma być traktowany w specjalny
sposób - jako zwykły znak - np.:
|
Tutaj należy zaznaczyć, że do zapisania znaku \ w łańcuchu znaków w Javie używamy zapisu "...\\...". Jeśli więc chcesz użyć wyrażenia regularnego opisującego nazwę folderu na dysku zakończoną znakiem końca wiersza - "[a-z]:\[a-zA-Z0-9_+-]{,255}\n", w łańcuchu definiującym to wyrażenie regularne muszisz użyć podwójnych back-slashy: "[a-z]:\\[a-zA-Z0-9_+-]{,255}\\n"
Wiecej informacji o dodatkowych specjalnych kwantyfikatorach i znakach specjalnych znajdziesz w dokumentacji, tutaj: https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html
Narzędzie do testowania wyrażeń regularnych online: https://regexr.com/
Przykłady:
Wyrażenie regularne określające format liczby rzeczywistej z przecinkiem:
[0-9]*,[0-9]*
Wyrażenie regularne określające format liczby rzeczywistej (float) bez wiodących zer, z dowolną liczbą cyfr dziesiętnych i jeśli w liczbie występuje przecinek, wówczas dozwolone są maksymalnie dwie cyfry po przecinku, gdzie część całkowita od ułamkowej jest rozdzielona przecinkiem lub kropką:
[1-9]*[0-9]([\.,][0-9]{1,2})?
...i zapis tego wyrażenia w kodzie Javy (zwróć uwagę na podwójny back-slash):
Pattern wyrażenieRegularne = Pattern.compile( "[1-9]*[0-9]([\\.,][0-9]{1,2})?" );
Użycie wyrażeń regularnych w kodzie w języku Java:
Z wyrażeń regularnych korzysatmy w Javie najczęściej przy użyciu klas Pattern oraz Matcher. Dostępnych jest w nich szereg metod pozwalających na analizowanie dopasowań łańcuchów znaków do danego wyrażenia regularnego.
Przykład prostego, jednokrotnego użycia wyrażenia regularnego:
Pattern.matches( "wyrażenie_regularne", "łańcuch_znaków_do_sprawdzenia" ); // zwraca true lub false
...w odniesieniu do pokazanego wyżej przykładu:
Pattern.matches( "[1-9]*[0-9]([\\.,][0-9]{1,2})?", "0.12" ); //zwraca true
Przykład prostego użycia wyrażenia regularnego, ale do wielokrotnego wykorzystania w kodzie:
- Tworzymy obiekt reprezentujący wyrażenie regularne - wzorzec. Metody
Pattern.compile używamy do wstępnego skompilowania wyrażenia dla
celów wydajnościowych:
Pattern wyrażenieRegularne = Pattern.compile( "[1-9]*[0-9]([\\.,][0-9]{1,2})?" ); - W obiekcie klasy Pattern dostępna jest metoda matcher( String str
) zwracająca obiekt typu Matcher reprezentujący dopasowanie
łańcucha str do danego wyrażenia regularnego (wzorca) lub brak
dopasowania:
Matcher matcher = wyrażenieRegularne.matcher( "jakiś łańcuch znakow" ); - Obiekt klasy Matcher udostępnia metodę matches(), która zwraca
true, gdy dany łańcuch znaków pasuje do wyrażenia regularnego
wyrażenieRegularne; w przeciwnym razie zwraca false:
matcher.matches(); // zwraca true lub false
Przykład z jednokrotnym użyciem obiektu klasy Pattern
import java.util.*;
import java.util.regex.*;
public class RegularExpressionTestProgram {
static private String correctValue;
public static void main( String... args ) {
Scanner userInputScanner = new Scanner( System.in );
System.out.println( "_____________________________________________" );
do {
System.out.print( "Enter a float value: " );
String userInput = userInputScanner.nextLine();
try { // ten try/catch jest wyłącznie na wypadek wprowadzenia nieprawidłowej definicji
// wyrażenia regularnego. Jeśli wyrażenie jest poprawne, nie ma potrzeby try/catch.
if( Pattern.matches( "[1-9]*[0-9]([\\.,][0-9]{1,2})?", userInput ) ) // zwraca true lub false
correctValue = userInput;
} catch( Exception e ) {
System.out.println( "EXCEPTION: " + e.toString() );
}
} while( correctValue == null );
System.out.println( "HURRA! Float value entered: " + correctValue );
System.out.println( "_____________________________________________" );
}
}
Przykład z wielokrotnym użyciem obiektu klasy Pattern
import java.util.*;
import java.util.regex.*;
public class RegularExpressionTestProgram2 {
static private String correctValue;
public static void main( String... args ) {
Scanner userInputScanner = new Scanner( System.in );
System.out.println( "_____________________________________________" );
Pattern wyrażenieRegularne = Pattern.compile( "[1-9]*[0-9]([\\.,][0-9]{1,2})?" );
do {
System.out.print( "Enter a float value: " );
String userInput = userInputScanner.nextLine();
Matcher matcher = wyrażenieRegularne.matcher( userInput );
if( matcher.matches() ) //zwraca true lub false
correctValue = userInput;
} while( correctValue == null );
System.out.println( "HURRA! Float value entered: " + correctValue );
System.out.println( "_____________________________________________" );
}
}