자바에서 함수를 사용할 때, 정규 표현식을 이용하여 특정 패턴을 추출, 치환, 삭제하는 경우가 많았습니다. 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 발생과 해결방안