정규표현식이란?
정규표현식은 메타문자를 활용해서 특정 조건에 맞는 문자열을 포현하는 식입니다.
이것이 왜 필요하냐?
간단한 예시로 간단한 이메일형식의 문자열을 나타낸다면 정규표현식으로 \\\\w+@\\\\w+\\\\.\\\\w+
와 같이 나타낼수 있습니다.
그리고 이를 조건으로 활용해서 문자열 내에 이메일만 존재하는지도 체크할수도 있죠.
정규표현식이 없다면 꽤나 복잡한 과정을 거쳐 구했을 것입니다.
문자 클래스
문자 클래스는 []
안에 문자를 넣어 사용합니다. 정확한 의미는 “대괄호 안에 있는 문자 중 하나”를 의미합니다. 예시는 다음과 같습니다.
String regex = "[hi]" //h, i 중 하나
String regex = "[^hi]" //h, i 를 제외한 문자 중 하나
String regex = "[a-z]" //a ~ z 중 하나
String regex = "[a-zA-Z]" //a ~ z 또는 A ~ Z 중 하나
String regex = "[.]" //마침표
특히 마침표 .
는 특수한 기능을 하는 이스케이프 문자입니다. 하지만 문자 클래스에선 단순 문자로 사용됩니다.
와일드 카드와 이스케이프 문자
정규표현식에서 모든 문자를 나타내는 와일드 카드는 . 입니다. 그리고 \\
는 특별한 문자를 표현하기 위한 이스케이프 문자입니다.
이스케이프 문자를 사용하면 다음과 깉이 미리 정의되어 있는 문자 클래스들을 얻어낼 수 있습니다.
String regex = "." //모든 문자
String regex = "\\." //마침표
String regex = "\\d" //숫자
String regex = "\\w" //일반문자(영어) or 숫자 or _
String regex = "\\s" //공백
String regex = "\\D" //숫자를 제외한 문자
String regex = "\\W" //일반문자(영어) or 숫자 or _를 제외한 문자
String regex = "\\S" //공백을 제외한 문자
이스케이프 문제는 \\
하나인데 왜 두개를 사용하냐면 숫자를 나타내는 문자열은 \\d
이기 때문입니다. 문자열에서 \\
를 표현하려면 \\\\
를 사용해야합니다. 따라서 \\\\d
와 같은 형태로 사용되는 것입니다.
반복을 나타내는 수량자
수량자는 앞의 요소가 얼마나 반복되는지를 의미합니다. 일반적으로 수량자는 탐욕적 수량자며 ?
를 붙이면 게으른 수량자로 만들 수 있습니다.
String regex = "h*" //h가 0번 이상 반복
String regex = "h+" //h가 1번 이상 반복
String regex = "h?" //h가 0 또는 1번 반복
String regex = "h{2}" //h가 2번 반복
String regex = "h{2,}" //h가 2번 이상 반복
String regex = "h{2,5}" //h가 2번 이상 5번 이하 반복
그렇다면 탐욕적 수량자와 게으른 수량자의 차이는 무엇일까요? 정규표현식을 사용하여 p태그 안에 내용들을 검색한다고 가정해봅시다.
탐욕적 수량자 : <p>Hello</p><p>World</p>
게으른 수량자 : <p>Hello</p><p>World</p>
둘의 차이가 느껴지지요?
같은 표현이 반복될 때 탐욕적 수량자는 최대한 큰 범위에 검색결과를, 게으른 수량자는 가장 최소한의 검색결과를 반환합니다. 일반적으로 수량자는 탐욕적 수량자이며 탐욕적 수량자 뒤에 ?
를 붙인다면 게으른 수량자로 만들 수 있습니다.
위 내용을 정규표현식으로 표현하면 다음과 같습니다.
String regex = "<p>.*</p>" //탐욕적 수량자
String regex = "<p>.*?</p>" //게으른 수량자
논리 연산자
그 동안 문자들을 다루었던 것과 달리 문자열들을 다룰 수 있는 정규표현식 내의 논리 연산자입니다.
문자열끼리 or 연산을 가능하게 해주는 |
, 문자열을 하나의 문자로 취급할 수 있도록 만들어 주는 ()
가 있습니다.
String regex = "Hello|World" //Hello나 World
String regex = "Hello(World)?" //Hello나 HelloWorld
String regex = "(Happy|Gloomy){1}Day" //HappyDay나 GloomyDay
경계를 나타내는 앵커 문자
앵커 문자는 문자열의 시작과 끝과 같은 경계 조건을 줄 수 있습니다.
다른 앵커 문자들도 있지만 문자열의 시작을 나타내는 ^
끝을 나타내는 $
가 자주 사용됩니다.
String regex = "^[0-9]" //문자열의 시작에 위치한 숫자
String regex = "[0-9]$" //문자열 끝에 위치한 숫자
String regex = "^[0-9]$" //모든 문자열이 숫자
정규표현식 활용
정규표현식을 지원하는 클래스인 Pattern과 Matcher 클래스입니다.
정규표현식을 Pattern 에 등록하고 Matcher 의 메서드 find 를 사용해 하위 문자열을 탐색합니다.
아주 간단한 예시로 gmail만을 사용하는 이메일 주소만 가져오도록 하겠습니다.
String emails = "Jax@gmail.com, Dalius@naver.com, Teemo@kakao.com";
Pattern pattern = Pattern.compile("\\w+@gmail.com");
Matcher matcher = pattern.matcher(emails);
while(matcher.find()){
System.out.println(matcher.group()); //Jax@gmail.com
}
문자열 내에서도 정규표현식을 지원하는 메서드 replaceAll과 matches가 존재합니다.
이들을 이용해 이메일 명단에서 닉네임만을 가져오고 닉네임들 중 특수문자가 없는지 체크해봅시다.
String emails = "Jax@gmail.com, Dalius@naver.com, ####@kakao.com";
String nickNames = emails.replaceAll("@\\w+\\.com", ""); //이메일 주소 공백으로 치환
String[] nickNameAll = nickNames.split(",");
Arrays.stream(nickNameAll)
.map(String::trim)
.filter(s -> s.matches("\\w+")) //닉네임 명단 중 특수문자 있으면 제거
.forEach(System.out::println); //Jax, Dalius
String의 matches는 내부적으로 Pattern.matches를 사용하고 Pattern.matches는 내부적으로 Matcher를 만들어 matches를 사용합니다. 즉 이 String, Pattern, Matcher의 matches는 모두 같은 동작을 합니다.
여기서 주의해야 할 점은 find와 matches입니다.
이 둘은 같아보이지만 기능적으론 전혀 다른 메서드입니다. matches는 문자열 전체에 대해 정규식이 일치하는지 판단합니다만 find의 경우 문자열의 일부에 대해 정규식에 일치하는지 판단합니다.
일치한 매쳐들은 group 메서드를 통해 매칭됬던 문자열들을 얻을 수 있습니다.
'Java > Grammer' 카테고리의 다른 글
Simple Data Structure (2) - List/Set/Map (0) | 2022.04.23 |
---|---|
Simple Data Structure (1) - 공통 메서드 (0) | 2022.04.23 |
String Class (2) - 주요 메서드 (0) | 2022.03.27 |
String Class (1) - Immutable과 StringBuilder/StringBuffer (0) | 2022.03.26 |
추상클래스와 인터페이스의 차이 (0) | 2022.01.16 |