자바에서 함수를 사용할 때, 정규 표현식을 이용하여 특정 패턴을 추출, 치환, 삭제하는 경우가 많았습니다. String.matches()
, String.replaceAll()
, String.split()
정규 표현식에 대해 익히고 함수들을 유연하고 깔끔하게 쓰기를 희망합니다.
문자열의 일정한 패턴을 표현하는 방식입니다.
예를 들어, "ABC DEF GHI" 는 대문자 알파벳 3개와 공백으로 이루어져 있습니다.
이러한 대문자 알파벳 3개와 공백 있을 수도 있고 없을 수도 있는 정규표현식으로 표현할 수 있습니다.
패턴구분자 시작 | 작성할 패턴 | 패턴 구분자 끝 | flag |
---|---|---|---|
/ | 패턴 | / | gm |
정규 표현식은 이러한 구조를 띄인다고 합니다.
이 글에서는 구조 중 패턴에 대해 알아볼 것이기 때문에 인지만 하고 넘어가겠습니다.
패턴은 메타문자로 표현됩니다.
메타문자는 정규식에서 특별한 의미를 지닌 문자입니다. 메타 문자를 이용하여 여러 형태의 패턴을 표현할 수 있습니다.
(문자가 메타문자인 경우, default 메타문자로 인식합니다. 메타문자를 문자로 쓰고 싶은 경우 \
를 사용하여 변환할 수 있습니다)
메타문자 뒤에 수량자를 표현할 수도 있습니다. 일반적으로 1개일 때는 생략을 하고 최소, 최대, 범위를 지정할 수 있습니다.
문자 | 설명 |
---|---|
| | 또는 |
() | 그룹 |
[] | 문자셋, 괄호 안의 어떤 문자일 경우 |
[^] | 부정 문자셋, 괄호 안의 어떤 문자도 아닌 경우 |
?: | 찾지만 기억하지 않음 |
문자 | 설명 |
---|---|
? | 1개 이하 (zero or one) |
* | 0개 이상 (zero or more) |
+ | 1개 이상 (one or more) |
{n} | n번 |
{min,} | 최소 |
{min,max} | 최소 그리고 최대 |
문자 | 설명 |
---|---|
\b | 단어 경계 |
\B | 단어 경계가 아닌 |
^ | 문장의 시작 |
$ | 문장의 끝 |
문자 | 설명 |
---|---|
\ | 특수 문자가 아닌 문자로 표기 |
. | 어떤 글자 (줄바꿈 문자 제외) |
\d | digit 숫자 |
\D | digit 숫자가 아님 |
\w | word 문자 |
\W | word 문자 아님 |
\s | space 공백 |
\S | space 공백 아님 |
이외에도 다양한 메타 문자와 백슬래시 시퀀스가 존재합니다. 메타 문자 설명 IBM
class RegexTest {
String PHONE_NUMBER_REGEX = "\\d{2,3}[- .]\\d{3}[- .]\\d{4}";
String EMAIL_REGEX = "[a-zA-Z0-9._+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9.]+";
String YOUTUBE_ADDRESS_REGEX = "(https?:\\/\\/)?(www\\.)?youtu.be\\/([a-zA-Z0-9-]{11})";
Pattern PHONE_NUMBER_PATTERN = Pattern.compile(PHONE_NUMBER_REGEX);
Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX);
Pattern YOUTUBE_ADDRESS_PATTERN = Pattern.compile(YOUTUBE_ADDRESS_REGEX);
@Test
void normalizeDots() {
String input = "This....is.....a......test....";
String output = input.replaceAll("\\.{2,}", ".");
assertThat(output).isEqualTo("This.is.a.test.");
}
@Test
void matchText() {
String text = "This is a test string.";
Pattern pattern = Pattern.compile("^[a-zA-Z]+$");
Matcher matcher = pattern.matcher(text);
assertThat(matcher.matches()).isFalse();
}
@ParameterizedTest
@ValueSource(strings = {
"010-898-0893",
"010 898 0893",
"010.898.0893",
"010-405-3412",
"02-878-8888"
})
void matchPhoneNumber(String phoneNumber) {
assertThat(PHONE_NUMBER_PATTERN.matcher(phoneNumber).matches()).isTrue();
}
@ParameterizedTest
@ValueSource(strings = {
"dream.coder.ellie@gmail.com",
"hello@daum.net",
"hello@daum.co.kr"
})
void matchEMAIL(String email) {
assertThat(EMAIL_PATTERN.matcher(email).matches()).isTrue();
}
@ParameterizedTest
@ValueSource(strings = {
"http://www.youtu.be/-ZClicWm0zM",
"https://www.youtu.be/-ZClicWm0zM",
"https://youtu.be/-ZClicWm0zM",
"youtu.be/-ZClicWm0zM"
})
void matchYouTubeAddress(String youtubeAddress) {
assertThat(YOUTUBE_ADDRESS_PATTERN.matcher(youtubeAddress).matches()).isTrue();
}
}
String.replaceAll()
시 Pattern.compile()
를 이용하여 새로운 패턴을 만듭니다. 반복하여 수행할 경우, 정적 Pattern
을 만든 다음 사용하면 성능적 이점을 누릴 수 있습니다.
Pattern.compile()
을 통해 Pattern
을 인스턴스화하고 Pattern.matcher()
를 통해 패턴 검사를 하면 됩니다.
Catastrophic backtracking
이 발생할 수 있습니다. 패턴을 명확하게 표현하여야 합니다. Catastrophic backtracking 발생과 해결방안