언어/JAVA

Java Matcher 클래스 완전정복

shaprimanDev 2026. 6. 20. 08:16
728x90
반응형

 

☕ Java · 정규식 · 중급

Java Matcher 클래스
완전정복 🎯

Pattern과 Matcher를 활용한 정규식 처리의 모든 것 — 기초 개념부터 실전 예제까지 한 번에 정리합니다.

1 Matcher란 무엇인가?

Java에서 정규식(Regular Expression)을 사용할 때 반드시 등장하는 두 클래스가 있습니다. 바로 PatternMatcher입니다.

Pattern은 정규식 패턴 자체를 컴파일한 객체이고, Matcher는 그 패턴을 실제 문자열에 적용해서 매칭을 수행하는 엔진입니다. 둘 다 java.util.regex 패키지에 속합니다.

📌 패키지 위치
java.util.regex.Pattern
java.util.regex.Matcher

별도 import 없이 JDK에 기본 포함되어 있습니다.
2 Pattern → Matcher 흐름

Matcher 객체는 직접 new로 생성할 수 없습니다. 반드시 Pattern을 통해서만 얻을 수 있습니다.

정규식 문자열
"[0-9]+"
Pattern.compile()
패턴 컴파일
pattern.matcher()
Matcher 생성
find() / matches()
매칭 수행
 
 
 
BasicMatcherExample.java
import java.util.regex.*;

public class BasicMatcherExample {
    public static void main(String[] args) {

        // 1단계: 정규식을 컴파일하여 Pattern 생성
        Pattern pattern = Pattern.compile("[0-9]+");

        // 2단계: 대상 문자열로 Matcher 생성
        Matcher matcher = pattern.matcher("주문번호 12345, 배송번호 67890");

        // 3단계: find()로 순서대로 매칭 탐색
        while (matcher.find()) {
            System.out.println("발견: " + matcher.group()
                + " (위치: " + matcher.start() + "~" + matcher.end() + ")");
        }
    }
}
▶ 실행 결과
발견: 12345 (위치: 5~10)
발견: 67890 (위치: 17~22)
3 핵심 메서드 총정리
메서드 반환 타입 설명
matches() boolean 전체 문자열이 패턴과 완전히 일치하는지 검사
find() boolean 현재 위치에서 다음 매칭을 탐색 (반복 호출 가능)
find(int start) boolean 지정한 인덱스부터 탐색 시작
lookingAt() boolean 문자열의 시작부분이 패턴과 일치하는지 검사
group() String 마지막으로 매칭된 전체 문자열 반환
group(int n) String n번째 캡처 그룹의 매칭 문자열 반환
group(String name) String 이름이 있는 캡처 그룹의 매칭 문자열 반환
start() int 마지막 매칭 시작 인덱스
end() int 마지막 매칭 종료 인덱스 (마지막 문자 다음)
groupCount() int 패턴 내 캡처 그룹의 총 개수
replaceAll(String) String 매칭된 모든 부분을 지정 문자열로 교체
replaceFirst(String) String 처음 매칭된 부분만 교체
reset() Matcher 탐색 위치를 처음으로 초기화
reset(CharSequence) Matcher 다른 입력 문자열로 교체하고 초기화
4 matches() vs find() vs lookingAt()

가장 많이 혼동되는 세 메서드의 차이를 코드로 명확히 비교해봅니다.

 
 
 
MatcherComparison.java
import java.util.regex.*;

public class MatcherComparison {
    public static void main(String[] args) {

        String text = "Hello World";
        Pattern p = Pattern.compile("Hello");

        // matches() — 전체 문자열이 패턴과 완전 일치해야 true
        Matcher m1 = p.matcher(text);
        System.out.println("matches()    : " + m1.matches());   // false

        // lookingAt() — 문자열의 시작이 패턴과 일치하면 true
        Matcher m2 = p.matcher(text);
        System.out.println("lookingAt()  : " + m2.lookingAt()); // true

        // find() — 문자열 어디서든 패턴이 발견되면 true
        Matcher m3 = p.matcher(text);
        System.out.println("find()       : " + m3.find());      // true

        // matches()를 true로 만들려면 전체를 포함하는 패턴 필요
        Pattern p2 = Pattern.compile("Hello.*");
        Matcher m4 = p2.matcher(text);
        System.out.println("matches(.*)  : " + m4.matches());   // true
    }
}
▶ 실행 결과
matches() : false
lookingAt() : true
find() : true
matches(.*) : true
💡 한 줄 정리
matches() = 전체 일치  |  lookingAt() = 앞에서 시작 일치  |  find() = 어디든 일치
5 그룹(Group) 캡처 활용

정규식에서 ()로 감싼 부분이 캡처 그룹입니다. Matcher의 group(n)으로 각 그룹을 개별 추출할 수 있습니다.

 
 
 
GroupCapture.java — 날짜 파싱 예제
import java.util.regex.*;

public class GroupCapture {
    public static void main(String[] args) {

        // 날짜 패턴: (연도)-(월)-(일)
        String regex = "(\\d{4})-(\\d{2})-(\\d{2})";
        String text  = "계약일: 2024-03-15, 만료일: 2025-12-31";

        Pattern p = Pattern.compile(regex);
        Matcher m = p.matcher(text);

        while (m.find()) {
            System.out.println("전체: "  + m.group(0));  // = group()
            System.out.println("  연도: " + m.group(1));
            System.out.println("  월:   " + m.group(2));
            System.out.println("  일:   " + m.group(3));
            System.out.println("---");
        }
    }
}
▶ 실행 결과
전체: 2024-03-15
  연도: 2024
  월: 03
  일: 15
---
전체: 2025-12-31
  연도: 2025
  월: 12
  일: 31

Java 7부터는 이름 있는 캡처 그룹을 사용할 수 있어 가독성이 훨씬 좋아집니다.

 
 
 
NamedGroup.java — 이름 있는 그룹 (?<name>...)
import java.util.regex.*;

public class NamedGroup {
    public static void main(String[] args) {

        // (?<이름>패턴) 형태로 그룹에 이름 부여
        String regex = "(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})";
        Matcher m = Pattern.compile(regex).matcher("출시일: 2024-07-04");

        if (m.find()) {
            System.out.println("연도: "  + m.group("year"));
            System.out.println("월:   "  + m.group("month"));
            System.out.println("일:   "  + m.group("day"));
        }
    }
}
▶ 실행 결과
연도: 2024
월: 07
일: 04
💡 팁
이름 있는 그룹 (?<name>...)은 숫자 인덱스 방식보다 코드 유지보수에 훨씬 유리합니다. 그룹 순서가 바뀌어도 코드를 수정할 필요가 없습니다.
6 replaceAll / replaceFirst

Matcher의 replace 메서드는 String.replaceAll()보다 강력합니다. 캡처 그룹의 역참조($1, $2...)를 치환 문자열에 활용할 수 있기 때문입니다.

 
 
 
ReplaceExample.java
import java.util.regex.*;

public class ReplaceExample {
    public static void main(String[] args) {

        // 예제 1: 모든 숫자를 [숫자]로 감싸기
        Pattern p1 = Pattern.compile("(\\d+)");
        Matcher m1 = p1.matcher("항목 3개, 가격 15000원");
        System.out.println(m1.replaceAll("[$1]"));

        // 예제 2: 날짜 형식 변환 YYYY-MM-DD → DD/MM/YYYY
        Pattern p2 = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})");
        Matcher m2 = p2.matcher("오늘: 2024-06-19");
        System.out.println(m2.replaceAll("$3/$2/$1"));

        // 예제 3: 처음 발견된 이메일만 마스킹
        Pattern p3 = Pattern.compile("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}");
        Matcher m3 = p3.matcher("연락처: admin@example.com, help@test.com");
        System.out.println(m3.replaceFirst("[이메일 숨김]"));
    }
}
▶ 실행 결과
항목 [3]개, 가격 [15000]원
오늘: 19/06/2024
연락처: [이메일 숨김], help@test.com
7 실전 예제 모음

예제 1: 전화번호 유효성 검사 & 포맷 통일

 
 
 
PhoneValidator.java
import java.util.regex.*;

public class PhoneValidator {

    static final Pattern PHONE_PATTERN =
        Pattern.compile("(01[016789])[- ]?(\\d{3,4})[- ]?(\\d{4})");

    public static boolean isValid(String phone) {
        return PHONE_PATTERN.matcher(phone).matches();
    }

    public static String format(String phone) {
        Matcher m = PHONE_PATTERN.matcher(phone);
        if (m.matches()) {
            return m.group(1) + "-" + m.group(2) + "-" + m.group(3);
        }
        return "유효하지 않은 번호";
    }

    public static void main(String[] args) {
        String[] phones = { "01012345678", "010-1234-5678",
                             "010 1234 5678", "02-123-4567" };
        for (String phone : phones) {
            System.out.printf("%-15s → 유효: %-5s 포맷: %s%n",
                phone, isValid(phone), format(phone));
        }
    }
}
▶ 실행 결과
01012345678 → 유효: true 포맷: 010-1234-5678
010-1234-5678 → 유효: true 포맷: 010-1234-5678
010 1234 5678 → 유효: true 포맷: 010-1234-5678
02-123-4567 → 유효: false 포맷: 유효하지 않은 번호

예제 2: HTML 태그 제거

 
 
 
HtmlStripper.java
import java.util.regex.*;

public class HtmlStripper {
    public static void main(String[] args) {

        String html = "<h1>제목</h1><p>내용 <b>강조</b> 텍스트</p>";

        Pattern tagPattern = Pattern.compile("<[^>]+>");
        String plain = tagPattern.matcher(html).replaceAll("");

        System.out.println("원본: " + html);
        System.out.println("결과: " + plain);
    }
}
▶ 실행 결과
원본: <h1>제목</h1><p>내용 <b>강조</b> 텍스트</p>
결과: 제목내용 강조 텍스트

예제 3: 로그에서 IP 주소 추출

 
 
 
IpExtractor.java
import java.util.*;
import java.util.regex.*;

public class IpExtractor {
    public static void main(String[] args) {

        String log = "[INFO] 192.168.0.1 접속\n[WARN] 10.0.0.255 차단\n[INFO] 172.16.100.5 접속";

        Pattern ip = Pattern.compile(
            "\\b(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})\\b");
        Matcher m = ip.matcher(log);

        List<String> ips = new ArrayList<>();
        while (m.find()) {
            ips.add(m.group());
        }
        System.out.println("발견된 IP: " + ips);
    }
}
▶ 실행 결과
발견된 IP: [192.168.0.1, 10.0.0.255, 172.16.100.5]
8 성능 팁 & 주의사항
⚠️ 주의: Pattern은 반드시 재사용하세요
Pattern.compile()은 정규식을 파싱·최적화하는 비용이 큽니다.
루프 안에서 매번 호출하면 심각한 성능 저하가 발생합니다.
 
 
 
PerformanceTip.java
import java.util.regex.*;

public class PerformanceTip {

    // ✅ 올바른 방법: static final로 한 번만 컴파일
    private static final Pattern EMAIL =
        Pattern.compile("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}");

    public static boolean isEmail(String input) {
        return EMAIL.matcher(input).matches();
    }

    // ❌ 잘못된 방법: 호출마다 컴파일 (성능 낭비)
    public static boolean isEmailSlow(String input) {
        return Pattern.compile("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}")
                      .matcher(input).matches();  // 매번 컴파일!
    }
}
💡 추가 팁
reset()으로 Matcher 재활용: 같은 패턴으로 다른 문자열을 검사할 때 새 Matcher를 생성하는 것보다 matcher.reset(새문자열)이 더 효율적입니다.

단순 포함 검사: 단순히 문자열에 특정 문자가 포함되어 있는지만 확인할 때는 String.contains()가 정규식보다 훨씬 빠릅니다. 정규식은 복잡한 패턴이 필요할 때만 사용하세요.
⚠️ 백슬래시 이중 이스케이프 주의
Java 문자열 리터럴에서는 백슬래시를 \\로 써야 정규식의 \가 됩니다.
예) 숫자 매칭: 정규식으로는 \d+, Java 코드에서는 "\\d+"
9 N번째 매칭만 가져오기

찾고자 하는 단어가 문자열에 여러 번 등장할 때, 특정 순서의 매칭만 골라서 가져오고 싶은 경우가 있습니다.

find()는 호출할 때마다 내부 커서가 이전 매칭 끝으로 이동하므로, N번 호출하면 N번째 매칭에 도달합니다.

 
 
 
NthMatch.java — 3번째 숫자 추출
import java.util.regex.*;

public class NthMatch {
    public static void main(String[] args) {

        String text = "사과 3개, 배 7개, 귤 12개";
        Pattern p = Pattern.compile("\\d+");
        Matcher m = p.matcher(text);

        // 방법 1: 카운터로 N번째 추출
        int count = 0;
        while (m.find()) {
            count++;
            if (count == 3) {
                System.out.println("방법1 - 3번째: " + m.group()); // 12
                break;
            }
        }

        // 방법 2: find()를 N번 연속 호출 (간결)
        m.reset(); // 커서 초기화
        if (m.find() && m.find() && m.find()) {
            System.out.println("방법2 - 3번째: " + m.group()); // 12
        }
    }
}
▶ 실행 결과
방법1 - 3번째: 12
방법2 - 3번째: 12
📌 커서 동작 원리
find() 호출 1회 → "3" 발견, 커서가 그 뒤로 이동
find() 호출 2회 → "7" 발견, 커서가 그 뒤로 이동
find() 호출 3회 → "12" 발견 ✅

reset()을 호출하면 커서가 문자열 처음으로 돌아갑니다.

N이 유동적이라면 유틸 메서드로 뽑아두면 편합니다.

 
 
 
NthMatchUtil.java — 재사용 가능한 유틸 메서드
import java.util.regex.*;

public class NthMatchUtil {

    /**
     * 문자열에서 패턴의 n번째(1-based) 매칭을 반환
     * 없으면 null 반환
     */
    public static String getNthMatch(Pattern pattern, String text, int n) {
        Matcher m = pattern.matcher(text);
        int count = 0;
        while (m.find()) {
            if (++count == n) return m.group();
        }
        return null; // n번째 매칭 없음
    }

    public static void main(String[] args) {
        Pattern p = Pattern.compile("\\d+");
        String text = "사과 3개, 배 7개, 귤 12개";

        System.out.println(getNthMatch(p, text, 1));  // 3
        System.out.println(getNthMatch(p, text, 3));  // 12
        System.out.println(getNthMatch(p, text, 5));  // null
    }
}
▶ 실행 결과
3
12
null
10 실전: 반복 키워드에서 괄호 안 코드 추출

자동 생성된 문자열처럼 같은 키워드가 여러 번 등장하고, 그 뒤에 (코드) 형태로 값이 붙어있을 때 캡처 그룹으로 깔끔하게 추출할 수 있습니다.

예) "동업종(C18)" 에서 C18만 추출하는 경우입니다.

 
 
 
CodeExtractor.java — 괄호 안 코드 추출
import java.util.regex.*;

public class CodeExtractor {
    public static void main(String[] args) {

        String text = "업종분류: 동업종(A01), 이업종(B05), 동업종(C18), 동업종(D22)";

        // 동업종\( : 리터럴 '동업종(' 매칭
        // ([^)]+)  : ')' 아닌 문자를 그룹으로 캡처 → 이게 코드값
        // \)       : 리터럴 ')' 매칭
        Pattern p = Pattern.compile("동업종\\(([^)]+)\\)");
        Matcher m = p.matcher(text);

        while (m.find()) {
            System.out.println("전체 매칭: " + m.group()  // 동업종(C18)
                + "  →  코드: "          + m.group(1)); // C18
        }
    }
}
▶ 실행 결과
전체 매칭: 동업종(A01) → 코드: A01
전체 매칭: 동업종(C18) → 코드: C18
전체 매칭: 동업종(D22) → 코드: D22
📌 패턴 해설
동업종\\(([^)]+)\\)

  동업종 — 고정 문자열 그대로 매칭
  \\( — 리터럴 ( (정규식에서 특수문자라 이스케이프 필요)
  ([^)]+))가 아닌 문자 1개 이상을 캡처 그룹으로 묶음 → group(1)
  \\) — 리터럴 )

특정 순서의 것만 필요하다면 N번째 매칭 기법과 조합합니다.

 
 
 
CodeExtractorNth.java — 3번째 동업종 코드만 추출
import java.util.regex.*;

public class CodeExtractorNth {
    public static void main(String[] args) {

        String text = "업종분류: 동업종(A01), 이업종(B05), 동업종(C18), 동업종(D22)";
        Pattern p = Pattern.compile("동업종\\(([^)]+)\\)");
        Matcher m = p.matcher(text);

        int target = 3; // 3번째 동업종의 코드를 원함
        int count = 0;

        while (m.find()) {
            if (++count == target) {
                System.out.println("3번째 동업종 코드: " + m.group(1));
                break;
            }
        }
    }
}
▶ 실행 결과
3번째 동업종 코드: D22
💡 [^)]+ 패턴이 강력한 이유
[^)]+"닫는 괄호가 나오기 전까지 모든 문자"를 의미합니다.
코드값이 C18처럼 짧든, FOOD_BEVERAGE_01처럼 길든 상관없이 전부 캡처합니다.
괄호 안의 내용이 무엇이든 안전하게 추출할 수 있는 범용 패턴입니다.

🎯 정리

Matcher는 Pattern의 힘을 실제 문자열에 적용하는 엔진입니다.
find()로 반복 탐색, 그룹 캡처로 부분 추출, replace로 변환까지
정규식의 모든 기능을 Matcher 하나로 완성할 수 있습니다.


Pattern은 static final로 재사용 → 성능까지 잡는 Matcher 활용법을 기억하세요! ☕

728x90
반응형