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.;

 

 

Kwantyfikatory (określają liczbę wystąpień danego literału):
  • 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

 

Zakresy literałów (określające zbiór, do którego może należeć określony znak łańcucha):
  • 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)

 

Grupy literałów (grupa literałów to struktura składająca się z wielu literałów - traktowana jak pojedynczy literał)
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

 

Znaki specjalne w wyrażeniach regularnych
Wyrażenie Opis
. dowolny znak
\ ten znak oznacza, że następny znak ma być traktowany w specjalny sposób - jako zwykły znak - np.:
  • \. (back-slash i kropka) - traktuje kropkę jako zwykły znak kropki, a nie dowolny znak;
  • \\ - traktuje back-slash jako zwykły znak back-slash - przydatne np. przy analizowaniu nazw ścieżek w systemach Windows.
  • \[ - traktuje znak [ jako zwykły znak, a nie oznaczenie początku zakresu literałów.
  • \+, \- - są traktowane jako zwykłe znaki + oraz -, a nie operatory logiczne;
  • itp.

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:

  1. 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})?" );
  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" );
  3. 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( "_____________________________________________" );
	}
}