#04 – wyrażenia regularne

By 4 września 2014Kurs Javy
4

Dzisiejsza lekcja poświęcona będzie w całości wyrażeniom regularnym. Wyrażenia regularne (zwane też regex – od angielskiego wyrażenia regular expressions) to wzorce, dzięki którym możemy sprawdzać czy jakiś ciąg znaków (np. taki, który odczytamy od użytkownika) ma określony przez nas format (np. czy może być datą).

Wyrażenia regularne to bardzo potężne narzędzie, jednocześnie dość proste do opanowania. To oczywiście kwestia gustu, ale ja miałem świetną zabawę poznając i zgłębiając tajniki wyrażeń regularnych :) Ich zastosowań jest mnóstwo – od sprawdzania wejścia użytkownika, przez wyszukiwanie wzorców w tekście, po automatyczne przetwarzanie i analizowanie np. logów systemowych.

Lekcja

Przede wszystkim nauczymy się o budowie samych zapytań – do tego wystarczy nam strona internetowa pozwalająca sprawdzić, jak będzie działało nasze zapytanie.

Miej proszę na uwadze, że wyrażenia regularne są może logiczne, ale przy bardziej skomplikowanych łatwo popełnić błąd / literówkę. Problem z wyrażeniami regularnymi polega na tym, że takie wyrażenie z błędem najczęściej będzie działało, program nie zgłosi problemów, ale nie będzie ono sprawdzało tego, co oczekujemy. Ostatnia podsekcja jest poświęcona najczęstszym błędom w wyrażeniach, które zdarza się popełnić.

Wyrażenia regularne – składnia i konstrukcja

Wyrażenia regularne w Javie są bardzo podobne do tych w języku Perl (dla zainteresowanych różnice można znaleźć w dokumentacji Oracle). Dlatego jeśli znasz składnie wyrażeń regularnych z innego języka (np. PHP) możesz pominąć całą tą sekcję. Z drugiej strony, dzięki temu, że różnice nie są bardzo istotne, możesz się wspierać materiałami które znajdziesz pod hasłem PCRE (perl compatible regular expressions).

Wyrażenia regularne składają się z sekwencji ‚atomów’ (niestety nie znalazłem sensownego tłumaczenia na język polski – jeśli znasz takowe, będę wdzięczny za informacje w komentarzu :) ; na potrzeby nauki zostańmy przy tej nazwie, ale zastrzegam że to raczej nie jest poprawna nazwa w języku polskim). Najprostszy atom to literał – tzn. np litera, cyfra, znak specjalny. Literały można grupować za pomocą nawiasów. Poza literałami mamy jeszcze kwantyfikatory, mówiące ile wystąpień danego atomu może być oraz operator alternatywy. Brzmi niefajnie, prawda? Zostawmy więc teorię i przejdźmy do praktyki :)

Najprostsze wyrażenia regularne

Najprostsze wyrażenie regularne to po prostu tekst – póki co pomińmy znaki specjalne, bo jak zobaczymy za chwilę, mogą mieć one szczególne znaczenie.

abcde

Powyższe wyrażenie regularne dopasuje tekst „abcde” i żaden inny. Dopasuje, tzn. że sprawdzając czy zadany tekst „spełnia” wyrażenie regularne, otrzymamy prawdę. Oczywiście takie wyrażenie regularne nie ma dużego sensu, ale czasami będzie używane (np. metoda split klasy String przyjmuje wyrażenie regularne – a czasem chcemy podzielić wg po prostu określonej literki, znaku specjalnego czy słowa).

Dodajmy więc kwantyfikator do litery a .

Kwantyfikatory

a+bcde

To wyrażenie jest już ciekawsze, bo dopasuje zarówno „abcde” jak i „aabcde”, „aaaaabcde” itp. Jednym słowem – dowolną ilość literek a na początku (ale co najmniej jedną) i później litery bcde. Poniżej znajdziesz tabelkę z podsumowaniem kwantyfikatorów.

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} Maksymalnie n wystąpień  a{,3}b  b, ab, aab, aaab
 {n} Dokładnie n wystąpień  a{3}b  aaab

Kwantyfikatory dotyczą atomu, który jest od razu po jego lewej stronie (uwaga: jeśli będzie to spacja, dotyczył on będzie spacji). W powyższym przykładzie była to pojedyncza litera – często jednak chcemy powtórzyć pewną sekwencję. Weźmy na przykład numer rachunku bankowego. Zaczyna się on od liter PL, 2 cyfr, a nastepnie 6 bloków po cztery cyfry które mogą być oddzielone spacją. Zobaczmy jak podejść do tego problemu.

Zakresy i grupy

Zakresy w wyrażeniach regularnych to w skrócie rzecz ujmując grupa znaków, coś jakby powiedzieć ‚w tym miejscu będzie jeden z tych znaków’. Taki zakres też jest atomem. Zakresy definiujemy w nawiasach kwadratowych, możemy to zrobić na dwa sposoby: wymienić wszystkie możliwe znaki (bez przecinków, jeden obok drugiego) lub wprowadzić przedział, możemy je oczywiście łączyć. Przedział definiujemy określając element początkowy oraz końcowy umieszczając między nimi myślnik; mogą to być zarówno cyfry (np. 1-3 ; 0-9; 1-5) jak i litery (np. a-z ; A-Z). Ale znowu brniemy w teorię, na przykładach na pewno od razu będzie łatwiej:

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
 [abc\[\]] Litera a lub b lub c lub nawias kwadratowy (dlaczego dodaliśmy też odwrócone ukośniki, czytaj dalej)
 [.] Dowolna litera (czytaj dalej)

Grupy z kolei służą nam do łączenia bardziej skomplikowanych struktur (np. mamy wyrażenie regularne które dopasowuje fragment, który powtarza się kilka razy). Do oznaczania grup używamy zwykłych nawiasów, grupa traktowana jest jako atom. Spójrzmy na poniższe przykłady:

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

Teraz już możemy zbudować zapytanie do weryfikacji naszego rachunku bankowego (oczywiście nie sprawdza ona cyfry kontrolnej itp, a jedynie format numeru rachunku):

PL[0-9]{2}( ?[0-9]{4}){6}

Uwaga: do reprezentowania pewnych elementów (np. litery czy cyfry) są odpowiednie metatagi, o których więcej informacji znajdziesz w dokumentacji. Należy z nich korzystać w miarę możliwości ponieważ poprawiają czytelność i utrudniają popełnienie błędu/niedopatrzenia (np. pominięcia jakiejś litery). Nie robiliśmy tego powyżej, bo dla osoby nieobeznanej z wyrażeniami regularnymi mogłyby być one mylące. Pamiętaj jednak w pracy zawodowej, że są one dobra praktyką.

Znaki specjalne

Ostatnim ważnym elementem są znaki specjalne: poznamy tutaj kropkę oraz backslash.

Kropka dopasowuje dowolny znak. Dlatego wcześniej wspominałem, że należy uważać na znaki specjalne. Bardzo łatwo jest napisać np. wyrażenie do walidacji adresu IP (to adres, który identyfikuje nasz komputer w sieci, po szczegóły zainteresowanych odsyłam np. na wikipedię). Przykładowy adres ip to np: 255.255.255.255 . Uproszczone wyrażenie regularne które napisalibyśmy na szybko wyglądałoby więc nastepująco:

([0-9]{1,3}.){3}[0-9]{1,3}

Niestety poza naszym adresem dopasuje ono także „255 255 255 255”, a także „255u255p255s255”.

Aby kropka była traktowana w wyrażeniu regularnym jako kropka, musimy ją poprzedzić backslashem: \ . Ten znak mówi o tym, że kolejny znak należy traktować na specjalnych warunkach – w przypadku kropki te specjalne warunki to właśnie ‚traktuj jako normalny znak’. Prawidłowe wyrażenie regularne powinno więc wyglądać następująco:

([0-9]{1,3}\.){3}[0-9]{1,3}

(osoby zaznajomione z tematyką zapewne zauważą, że to wyrażenie dopasuje także nieprawidłowe adresy – wiem, ale wpis ten jest poświęcony wyrażeniom regularnym a nie zagadnieniom ruchu sieciowego, więc odpuszczamy absolutną poprawność na rzecz nauki ;) )

I tutaj jeszcze słowo odnośnie Javy i backslashów – ponieważ backslash jest też używany w ciągach znaków w Javie (np. żeby wstawić nową linię używamy \n) to każdy backslash musimy poprzedzić kolejnym. Dlatego wyrażenie regularne:

a\.b

zapiszemy jako:

String regex = "a\\.b";

W drugiej linijce najpierw mówimy, że kolejny znak po pierwszym backslashu traktuj specjalnie (== jest to normalny backslash), a więc wyrażenie regularne otrzymuje tylko jeden backslash. Ciekawie robi się jeśli chcemy np. dopasować właśnie backslash (np. a\b – taka konstrukcja przydatna jest do weryfikowania nazw plików/ścieżek w systemach Windows), dajmy na to wyrażeniem:

a\\b

Musimy je zapisać w Javie jako:

String regex = "a\\\\b";

Każda para backslashy przekłada sie na jeden backslash w wyrażeniu regularnym. Taka drobna niedogodność.

Korzystanie z wyrażeń regularnych w języku Java

W języku Java większość operacji na wyrażeniach regularnych wykonujemy z użyciem klas Pattern oraz Matcher. Klasy te oferują o wiele bardziej zaawansowaną funkcjonalność niż opisana poniżej (np. możemy analizować dopasowanie do każdej z grup z osobna), sporo kompletnych informacji znajdziemy na stronie Oracle. Tutaj zapoznamy się tylko z najprostszym użyciem, czyli sprawdzeniem czy dany ciąg znaków pasuje do naszego wyrażenia regularnego (innymi słowy, czy nasze wyrażenie regularne dopasowuje dany ciąg znaków).

Zacznijmy od klasy Pattern – klasa ta reprezentuje nasze wyrażenie regularne, skompilowane –  tzn. wstępnie przetworzone przez komputer, dzięki czemu wielokrotne jego wywołanie będzie bardziej wydajne. Obiekt z reprezentacją naszego wyrażenia otrzymamy wywołując statyczną metodę compile:

Pattern pattern = Pattern.compile("a*b?cde");

Metoda ta rzuca wyjątek PatternSyntaxException, który jest jednak typu unchecked więc nie musimy dodawać try-catch (ale oczywiście możemy – jeśli chcesz odświeżyć sobie wiedzę o wyjątkach, zapraszam do ponownego odwiedzenia lekcji #3).

Otrzymany obiekt pattern ma metodę matcher, która zwraca nam obiekt typu Matcher.

Matcher matcher = pattern.matcher("jakis ciag znakow");

Mówiąc w uproszczeniu – Pattern to nasze wyrażenie regularne, a Matcher to jego dopasowanie (lub nie) do konkretnego ciągu znaków. Obiekt matcher posiada metodę matches, która mówi nam czy cały ciąg znaków użyty do utworzenia Matchera pasuje do naszego wyrażenia regularnego.

matcher.matches(); //zwraca true lub false

Jeśli chcemy wykonać sprawdzenie tylko jeden raz, możemy skorzystać z metody-skrótu klasy Pattern:

Pattern.matches("regex", "ciąg do sprawdzenia"); //zwraca true lub false

Jest to funkcjonalnie równoważne z poniższym fragmentem:

Pattern pattern = Pattern.compile("regex");
Matcher matcher = pattern.matcher("ciąg do sprawdzenia");
matcher.matches(); //zwraca true lub false

Prawda, że proste, logiczne i przyjemne? :) Zapraszam oczywiście do samodzielnych eksperymentów.

Zadanie

Zmodyfikuj program, który już napisałeś tak, aby używał wyrażeń regularnych w miejscach gdzie są klauzule try-catch.

Pseudokod powinien wyglądać następująco:

  1. Wczytaj informacje od użytkownika
  2. Dopóki to, co podał użytkownik nie pasuje do wyrażenia regularnego, proś go ponownie o podanie danych
  3. Jeśli to, co wpisał użytkownik pasuje do wyrażenia regularnego przejdź dalej i wykonaj właściwy kod (np. konwersja daty) – możliwe, że nadal będziesz musiał używać bloków try-catch w niektórych miejscach (przez checked exceptions)

zip Pobierz rozwiązanie tego zadania

Licencja Creative Commons

Jeśli uważasz powyższą lekcję za przydatną, mamy małą prośbę: polub nasz fanpage. Dzięki temu będziesz zawsze na bieżąco z nowymi treściami na blogu ( i oczywiście, z nowymi częściami kursu Javy). Dzięki!

  •  
  •  
  •  
  •  
  •  
  • Magda Lena

    REWELACYJNE dziękuję :)

  • PawełS

    Wkradł się mały błąd ;)
    a(b(cd)?)*
    – lit­era a, a następ­nie jedno lub więcej powtórzeń b lub bcd

    Aby opis pasował należałoby zamienić * na +.

    • Jak najbardziej masz rację, dziękujemy! Lekcja już poprawiona.

  • Leszek

    Czy takie rozwiązanie zadania dla daty byłoby dobre?:

    Pattern wzorzecDaty = Pattern.compile(„[0-9]{4}(\.[0-9]{2}){2}”);
    String dataUrodzeniaWczytana;
    do {
    System.out.print(„Podaj datę urodzenia kota w formacie RRRR.MM.DD: „);
    dataUrodzeniaWczytana = getUserInput();
    if (wzorzecDaty.matcher(dataUrodzeniaWczytana).matches()) {
    break;
    }
    } while (kot.getDataUrodzenia() == null);

    U mnie działa, a jest krócej i nie trzeba używać bloku try-catch :-)

    • Cześć,
      tak, to część rozwiązania o które pyta zadanie :) Dokładnie o to chodziło! Oczywiście zamiast instrukcji break ustawiamy datę w obiekcie kot :)
      Pozdrawiamy!

      • Leszek

        Witam,

        Tu mam mały problem. Możesz napisać co dokładnie wpisać zamiast instrukcji break, tak żeby nie trzeba było użyć try-catch, bo ja się głowię, ale nie mogę nic wymyślić :( W waszym rozwiązaniu jest to zrobione z try-catch.

        PS. Jestem zupełnie na początku nauki programowania (10 dzień :)), więc czy mógłbyś wyjaśnić dlaczego nie może być instrukcji break? Gdy ją stosuję program uruchamia się i wszystko działa w 100% poprawnie :)

        Pozdrawiam!

        • Cześć! break to instrukcja, która przerywa dane działanie, np. działania w pętli (więcej o niej było w naszej 3 lekcji http://kobietydokodu.pl/3-konwersja-typow-obsluga-wyjatkow/) w tym przypadku, po prostu po sprawdzeniu czy data jest zgodna ze wzorcem, nie wykona się nic.
          Aby przypisać wartość do daty urodzenia naszego kota musimy użyć settera, który ustawi nam wartość pola – więcej o tym było w tej lekcji, http://kobietydokodu.pl/1-wstep-do-obiektow/.

          Teraz rozwiązanie, zamiast break powinno być: kot.setDataUrodzenia(dataUrodzeniaWczytana);

          Zachęcamy Cię do uzupełniania wiedzy z naszego kursu o tą z oficjalnych tutoriali Javy, powinno to ułatwić zrozumienie, w szczególności na początku.

          Pozdrawiamy!

          • Leszek

            Cześć,
            Gdy zamiast break wpisuję podane przez Ciebie rozwiązanie to wyskakuje następujący błąd:
            „The method setDataUrodzenia(Date) in the type Kot is not applicable for the arguments (String)”

            Masz rację z tym uzupełnieniem wiedzy, ale żeby się nie zniechęcać suchą teorią, przyjąłem zasadę, że najpierw kilka lekcji praktyki, a potem gruntowniejsze uzupełnienie wiedzy teoretycznej :)
            Pozdrawiam,

          • Cześć,

            faktycznie w tym przykładzie potrzebna jest jeszcze zamiana daty – podobnie jak było wpoprzedniej lekcji. Zamiast tamtej linijki, możesz użyć poniższego fragmentu:

            SimpleDateFormat sdf = new SimpleDateFormat(„yyyy.MM.dd”);
            Date dataUrodzeniaPrzetworzona = sdf.parse(dataUrodzeniaWczytana);
            kot.setDataUrodzenia(dataUrodzeniaPrzetworzona);

            Przepraszamy za skrót myślowy wczesniej i niedopowiedzenie :)

          • Adrian

            Ale takie rozwiązanie nadal wymusi na nas użycie bloku try-catch, tym razem w celu wyłapania wyjątku ParseException, więc nie da się zrobić tego zadania w takiej formie, w żadnym momencie nie używając bloku try-catch.

          • Oczywiście – stąd adnotacja przy zadaniu „możliwe, że nadal będziesz musiał używać bloków try-catch w niektórych miejscach (przez checked exceptions)”. O walidacji danych bez użycia try-catch i walidatorów możesz poczytać w kolejnych lekcjach, np. w lekcji 10 (http://kobietydokodu.pl/10-spring-mvc-formularze-i-widoki/) pod kątem Spring MVC

    • Jak dobrze rozumiem, to warunek na końcu pętli:
      while (kot.getDataUrodzenia() == null);
      ZAWSZE da w tym kodzie true (data urodzenia kota nie została mu jeszcze przypisana, bo w swojej pętli nie masz zdania przypisującego kotu date urodzenia, czyli
      (kot.setDataUrodzenia(sdf.parse(dataUrodzeniaWczytana));
      W środku pętli nie masz NIC co mogłoby sprawić, że warunek kot.getDataUrodzenia() == null kiedykowiek stanie się false.

      Kod by się kręcił w kółko, ale gdy user wpisze poprawnie format daty, break wyzwoli kod z pętli.

      Czyli w sumie działa, ale chyba brakuje temu logiki, bo w warunku na końcu pętli równie dobrze mógłbyś wpisać COKOLWIEK, co zawsze będzie true, np 2 +2 == 4. :-)

      Tak to widzę, ale poczekajmy co Mistrz powie. :-)

  • //Otrzy­many obiekt pat­tern ma metodę matches, która zwraca nam obiekt typu Matcher.

    //Matcher matcher = pattern.matcher(„jakis ciag znakow”);

    W tym zdaniu chciałeś chyba napisać „obiekt pat­tern ma metodę matchER”, a nie matchES?!

    I jeszcze pytanie jak się ma do tego wszystkiego metoda matches( String someString) z klasy String?!

    Po co wołać Pattern metodę, jak jednorazowo można też chyba tak, prawda?

    • Cześć,

      faktycznie zakradła się literówka, dzięki za zwrócenie uwagi!

      Co do metody String.matches – masz rację, nawet dokumentacja (http://docs.oracle.com/javase/8/docs/api/java/lang/String.html#matches-java.lang.String-) mówi o tym, że metoda ta działa tak samo. W tej lekcji jednak było stricte o wyrażeniach regularnych, przez co przykłady opierają się o klasy Pattern i Matcher. Natomiast funkcjonalnie jak najbardziej można użyć String.matches()

      Pozdrawiam, Jakub Derda

  • Ponieważ dobrnąłem do rozwiązania, to jeszcze kilka spraw:
    1. W rozwiązaniu brakuje \ przed kropkami:

    Pattern wzorzecDaty = Pattern.compile(„[0-9]{4}.[0-1]?[0-9].[0-3]?[0-9]”);

    2.
    // Pattern wzorzecWagi = Pattern.compile(„[0-9]+(\.[0-9]+)?”);
    // wzorzecWagi.matcher(wagaWczytana).matches();

    Jaki sens ma w tym kodzie używanie Matcher obiektu jak to jest jednarazowe użcie tego regex’u?
    Nie prościej Pattern.matches(wagaRegex, wagaWczytana);?

    3. Dla imienia kota i opiekuna nie ma regexu, czyli właściwie może powstać Kot obiekt bez imienia jak user kliknie „enter”.
    Tak jest ok?

    4. Nie ma znaczenia (czas, wydajność), że każdorazowo wywołujemy ten Scanner poprzez osobną metodę,
    zamiast w main’ie kilka razy wpisać tę linijkę: input = sc.nextLine().trim();?

    • Cześć,

      1. Faktycznie, powinno tam być – inaczej dopasowywany jest dowolny znak (w tym także kropka)

      2. Ponownie odeślę do dokumentacji (http://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html#matches-java.lang.String-java.lang.CharSequence-) – z punktu widzenia Javy te wywołania są identyczne. W lekcji są one przedstawione w ten sposób, żeby było jasne co się dzieje po kolei.

      3. Oczywiście możemy używać wyrażeń regularnych do sprawdzania imienia, jednak możnaby się kłócić jaki format ma mieć ‚imię’, prawdopodobnie i tak chcemy sprawdzić tylko jego długość. Datego regexy pojawiają się przy przypadkach, gdzie ich zastosowanie nie budzi wątpliwości i nie jest kontrowersyjne :)

      4. W skompilowanej klasie Javy z dużym prawdopodobieństwem wywołanie to będzie wyglądało dokładnie tak samo, jakby wpisać tą linijkę w każdym miejscu (jeśli Cię to interesuje, poszukaj „hotspot jit inlining” – znajdziesz tam kilka informacji technicznych na ten temat. Co do głównego pytania, nie jest to duży koszt z punktu widzenia aplikacji, nawet jeśli Java nie dokonała by optymalizacji tego kodu. Cenniejsze w tym wypadku jest wydzielenie wspólnego kodu do osobnej metody i poprawienie czytelności kodu niż potencjalna strata wydajności (którą, jeśli by istniała, liczylibyśmy pewnie co najwyżej w nanosekundach)

      Pozdrawiamy

      • //W skompilowanej klasie Javy z dużym prawdopodobieństwem wywołanie to będzie wyglądało
        //dokładnie tak samo, jakby wpisać tą linijkę w każdym miejscu

        Masz na myśli to, że JIT compiler nie będzie wysyłał tej metody co chwila do stack frame, tylko inteligentnie wpisze ją do kodu metody-callera, aby zmniejszyć overhead? ( że się tak po
        polsku wyrażę :-))

        • Niezupełnie… natomiast to raczej nie zagadnienia na 4 lekcję Javy. To, co ważne żeby zapamiętać to fakt, że czytelność kodu jest ważniejsza od jego potencjalnej uber-wydajności. Jeśli będziemy pisać kod w sposób zgodny ze sztuką, to jest duża szansa że jeden z mechanizmów optymalizacji w Javie będzie w stanie przyspieszyć działanie naszego kodu.

  • Ola

    Hej,

    Dlaczego mi to działa nie poprawnie:

    Pattern patternData = Pattern.compile(„[0-9]{1,4}\.[(0?[1-9])(1[0-2])]\.[(0?[1-9])([12][0-9])(3[01])]”); ?

    • Problem jest w składni wyrażenia, w nieprawidłowy sposób są używane nawiasy kwadratowe – nie można zrobić konstrukcji w stylu [(…)(…)]. Jak zakładam, chodzi o alternatywę w tym wypadku (jeden lub drugi nawias), w wyrażeniach regularnych używamy do tego znaku | . nawiasy kwadratowe mogą zawierać w sobie tylko i wyłącznie przedział znaków, a nie grupy czy sekwencje. Prawidłowe wyrażenie regularne może wyglądać następująco:

      [0-9]{1,4}.((0?[1-9])|(1[0-2])).((0?[1-9])|([12][0-9])|(3[01]))

      W zapisie Javowym:

      String pattern = „[0-9]{1,4}\.((0?[1-9])|(1[0-2]))\.((0?[1-9])|([12][0-9])|(3[01]))”;

      Polecam jednocześnie stronę http://www.regexplanet.com/advanced/java/index.html, można na niej sprawdzić czy podane frazy pasują do podanego wyrażenia.

      Pozdrawiamy

      • Ola

        Dziękuję bardzo za odpowiedź :)

  • TomDom

    Czy można to zapisać w taki sposób?

    Pattern wzorzecDaty = Pattern.compile(„[0-9]{4}[.][0-1]?[0-9][.][0-3]?[0-9]”);

    • Taki zapis jest poprawny z punktu widzenia składni Regex, ale miej na uwadze, że tak sformułowany wzorzec pozwoli na wpisanie także nieprawidłowych dat (które jednak pasują do wzorca), np:

      – dzień może być określony jako 39

      – jako miesiąc i dzień możemy wpisać 0 lub 00

      – rok może być z przedziału 0000-9999

      Natomiast regexy nie powinny być jedyną walidacją więc jako wstępny filtr jak najbardziej można użyć takiego zapisu. W pełni poprawna walidacja za pomocą regexów jest albo uciążliwa (zerknij np na ten przykład z datą: http://www.regxlib.com/REDetails.aspx?regexp_id=798), albo wręcz praktycznie niemożliwa (np. adres email wg standardu), dlatego często praktycznym rozwiązaniem jest właśnie używanie uproszczonych wzorcy.

      W razie wątpliwości polecam korzystanie ze strony http://www.regexplanet.com/advanced/java/index.html – możesz tam sprawdzić swoje wyrażenie regularne pod kątem kilku przykładów :)

  • Bolus150

    Witam, a można wykorzystać to wyrażenie do sprawdzenia czy użytkownik wpisał cyfrę lub literę? Np if (jaki_warunek)

    • Oczywiście, o ile dobrze zrozumiałem pytanie (o rozróżnienie za pomocą wyrażeń regularnych) można np użyć takich warunków:
      if (jakisString.matches(„[0-9]*”)) {…} else if (jakisString.matches(„[a-zA-Z]*”)) {…}

      Można to także zapisać w postaci jednego wyrażenia, ale to byłoby lekkim nadużyciem, i nie promujemy takich postaw ;)

  • karol p

    a można to wykorzystać do sprawdzenia czy użytkownik wpisał liczbę [0-9]?

    • Oczywiście, w takim wypadku wyrażenie regularne może wyglądać tak: „[0-9]” jeśli chcemy jedną cyfrę. Jeśli chciałbyś sprawdzić czy jest to liczba np. o maksymalnie 5 cyfrach, to można użyć wyrażenia: „[0-9]{1,5}”

  • super robota:)

  • a jak zrobić wyrażenie regularne z polskimi znakami? czy coś takiego będzie poprawne „\b+ [a-zA-Zęóąśłżźćń]+\b”

    • To zależy od kodowania znaków w Stringu, który sprawdzasz. Ale w ogólnym przypadku – tak, wyjątkiem może być sytuacja, w której odbierasz dane z formularzy albo czytasz je z pliku z niewłaściwym kodowaniem.
      Więcej o obsłudze znaków unicode (i nie tylko polskich znakach specjalnych) znajdziesz tutaj: http://www.regular-expressions.info/unicode.html

  • brudek

    Czołem! Może lepiej będzie brzmiało (albo chociaż pasowało) określenie człon zamiast atom?

    • niestety, człon w przypadku wyrażeń regularnych wystepuje już przy częściach, które dopasowujemy poprzez nawiasy :( ale dzięki za pomysł!

  • Wiktor Kalinowski

    A jak bym chcial bardziej dokladna date aby nikt nie mogl mi wpisac np w miesiacu 13 albo w dniach 39?
    W rozwiazaniu jest („[0-9]{4}.[0-1]?[0-9].[0-3]?[0-9]”); wiec data 9999.19.39 pasuje.

    • Polecam http://www.regxlib.com, gdzie znajdziesz większość standardowych patternów do np.dat. MM/DD/YYYY nie pozwalający na zły dzień i zły miesiąc:
      ^((((0[13578])|([13578])|(1[02]))[/](([1-9])|([0-2][0-9])|(3[01])))|(((0[469])|([469])|(11))[/](([1-9])|([0-2][0-9])|(30)))|((2|02)[/](([1-9])|([0-2][0-9]))))[/]d{4}$|^d{4}$

  • Stefan

    Trzeci link nie działa :(

  • Dobry

    Moje rozwiązanie :)
    Bloki try-catch nie są potrzebne, a wszystko działa

    https://uploads.disquscdn.com/images/6577b8b7f10819b686794daf56b555a474ae53335b45d257f13b5843165945c9.png

  • Tomasz

    Cześć,
    Dlaczego można tworzyć obiekt klasy Pattern bez operatora „NEW”?
    Pattern dataPattern = Pattern.compile(„[0-9]”);