Java Pattern / Matcher 정리

2025. 12. 29. 22:19·Java

우테코 1주차 미션에서는 문자열을 분리해 계산기를 구현하는 과제가 있었다. 나는 처음에 while 반복문으로 문자열을 하나하나 순회하면서 문제를 해결했다. 일단 요구사항은 충족했고, 크게 문제는 없다고 생각했다.

그런데 미션을 마친 뒤 다른 사람들의 코드를 보면서 생각이 조금 달라졌다. 정규식을 사용하면 같은 문제를 훨씬 간단하게 풀 수 있다는 걸 알게 되었기 때문이다. 코드를 비교해보니 단순히 구현 방식의 차이가 아니라, 문자열을 바라보는 관점 자체가 다르다는 느낌을 받았다.

그 경험을 계기로 정규식을 한 번 제대로 정리해보기로 했다. 문법을 외우는 방식보다는, 직접 코드를 작성하고 결과를 확인하면서 Pattern과 Matcher가 각각 어떤 역할을 하는지, 그리고 어디까지를 정규식으로 해결할 수 있는지를 중심으로 정리해보려고 한다.


Pattern은 자료형이 아니라 Class

처음 헷갈렸던 부분 중 하나는 Pattern의 정체였다.

Pattern은 자료형이 아니라 정규식 규칙을 컴파일하는 클래스다.

Pattern pattern = Pattern.compile("\\\\d+");

 

위 코드는

숫자 모양의 문자(\\d)가 1개 이상 연속으로 등장하는 문자열 규칙을 만든다는 의미다.

이 시점에서는 아직 어떤 문자열에도 적용되지 않은 상태다.


Matcher는 왜 사용할까?

Pattern은 규칙만 정의한다.

그 규칙을 실제 문자열에 적용해 매칭 결과를 관리하는 역할은 Matcher가 담당한다.

Pattern pattern = Pattern.compile("정규식");
Matcher matcher = pattern.matcher("대상 문자열");

 

정리하면 다음과 같다.

  • Pattern : 정규식 규칙
  • Matcher : 규칙 + 대상 문자열의 조합

find() vs matches()

정규식을 쓰면서 가장 헷갈렸던 부분이 find()와 matches()의 차이였다.

 

matches()

"123".matches("\\\\d+");   // true
"123a".matches("\\\\d+");  // false

 

matches()는 문자열 전체가 정규식과 일치해야 true를 반환한다.

입력값 검증에 주로 사용된다.

find()

Pattern p = Pattern.compile("\\\\d+");
Matcher m = p.matcher("a12b34");

while (m.find()) {
    System.out.println(m.group());
}

 

출력 결과는 다음과 같다.

12
34

 

find()는 문자열 중간에 매칭되는 부분이 있으면 true를 반환하며, 여러 번 매칭될 수 있다.

문자열에서 특정 패턴을 추출하는 파싱 문제에 적합하다.


문자열 경계: ^ 와 $

기호 의미
^ 문자열 시작
$ 문자열 끝
"123abc".matches("\\\\d+");        // false
"123abc".matches("^\\\\d+");       // false
"123abc".matches("^\\\\d+.*");     // true

 

입력 검증을 할 때는 대부분 ^...$ 형태를 사용하게 된다.

 

직접 테스트해본 예시

System.out.println("123abc".matches("^\\\\d+"));
// 숫자로 시작하지만 뒤에 abc가 남아 전체 매칭 실패

System.out.println("123abc".matches("^\\\\d+$"));
// 시작부터 끝까지 숫자여야 하므로 실패

System.out.println("123abc".matches("^\\\\d+."));
// 123a까지만 매칭되고 bc가 남아 실패

System.out.println("123abc".matches("^\\\\d+.*"));
// 숫자로 시작하고 뒤에는 어떤 문자든 허용 → 전체 소비

 

처음 보면 ^만 있어도 문자열 끝까지 검사하는 것처럼 보일 수 있다.

하지만 그 이유는 정규식 때문이 아니라, matches() 메서드의 동작 방식 때문이다.

 

String.matches()는 내부적으로 정규식이 문자열 전체를 매칭해야만 true를 반환한다. 즉, 패턴이 문자열의 일부만 매칭하고 끝나면, 남은 문자열이 있는 순간 바로 실패한다.

 

이 때문에 ^\\\\d+처럼 끝을 명시하지 않은 패턴도, 결과적으로는 문자열 전체가 소비되지 않아 false가 된다. 반면 ^\\\\d+.*처럼 뒤에 오는 모든 문자를 허용하면, 문자열 전체가 매칭되면서 true가 된다.

 

여기서 $는 여전히 의미를 가진다. $는 “문자열의 끝”을 명시적으로 표현하는 앵커(anchor) 이고, matches()와는 별개로 정규식 자체의 의도를 더 분명하게 만들어준다.


수량자

정규식에서 수량자는 매우 자주 사용된다.

기호 의미

기호 의미
+ 1개 이상
* 0개 이상
? 0~1개
{n} 정확히 n개
{n,} n개 이상
{n,m} n~m개
\\\\d{3}     // 숫자 문자 3개
\\\\d{1,2}   // 숫자 문자 1~2개

테스트 예시

System.out.println("123abc".matches("^\\\\d{3}.{3}"));   // true
System.out.println("123abc".matches("^\\\\d{2}.*"));     // true
System.out.println("123abc".matches("^\\\\d{1,3}.{2}")); // false

 

첫 번째 예시는 \\d{3}으로 숫자 3개(123), .{3}으로 문자 3개(abc)를 정확히 매칭하므로 성공한다.

 

두 번째 예시가 통과되는 이유는 .* 때문이다. .*는 문자 개수에 제한이 없고, 남아 있는 모든 문자를 전부 소비할 수 있다. 따라서 앞의 두 자리 숫자(12)만 맞으면, 나머지 "3abc"는 .*가 모두 처리하면서 전체 매칭이 성립한다.

 

세 번째 예시는 실패한다. \\d{1,3}은 숫자 1~3개까지 허용되지만, 그 뒤에 오는 .{2}는 문자 2개만 소비한다. 최대로 매칭해도

  • 숫자 3개 (123)
  • 문자 2개 (ab)

까지만 가능하고, 마지막 문자 c가 남게 된다. matches()는 문자열 전체 매칭을 요구하기 때문에, 남은 문자가 하나라도 있으면 실패하게 된다.


문자 집합 [ ]

[abc]       // a, b, c 중 하나
[a-z]       // 소문자
[A-Z]       // 대문자
[0-9]       // 숫자 문자
[^0-9]      // 숫자가 아닌 문자
[A-Za-z0-9]+ // 영문자 + 숫자

테스트 예시

System.out.println("d".matches("[a-d]"));        // true
System.out.println("abc".matches("[a-z]"));      // false
System.out.println("abc".matches("[a-z]+"));     // true

System.out.println("Hello World".matches("[a-zA-Z]+"));
// 공백 때문에 false

System.out.println("Hello World".matches("[a-zA-Z ]+"));
// 공백 허용

 

문자 집합에 포함된 문자는 허용 문자일 뿐, 반드시 포함되어야 한다는 의미는 아니다.


캡처 그룹 ()

정규식은 문자열 전체를 검증하는 데만 쓰이는 게 아니라,

문자열에서 특정 값만 추출해야 할 때도 매우 유용하다.

Pattern p = Pattern.compile("userId=(\\\\d+)");
Matcher m = p.matcher("userId=42");

if (m.find()) {
    System.out.println(m.group(1)); // 42
}

 

메서드 의미
group() 전체 매칭
group(1) 첫 번째 그룹

 

정규식에서 괄호로 감싼 부분은 캡처 그룹(capturing group) 으로 취급된다. 캡처 그룹에 매칭된 문자열은 Matcher의 group(n) 메서드를 통해 꺼낼 수 있다. 여기서 group(0)은 전체 매칭 결과를 의미하고, 첫 번째 괄호로 감싼 그룹이 group(1), 두 번째가 group(2)와 같은 식으로 번호가 매겨진다.

 

이 방식 덕분에 문자열 전체를 직접 순회하지 않아도, 필요한 값만 정확하게 추출할 수 있다. 예를 들어 날짜, 숫자, 특정 포맷의 값처럼 구조가 명확한 문자열을 다룰 때 특히 유용하다.

 

반대로, 단순히 묶음 용도로 괄호를 사용하고 싶지만 캡처는 필요 없는 경우도 있다. 이때는 (?: ... ) 형태의 비캡처 그룹(non-capturing group) 을 사용하면 된다. 이렇게 하면 정규식 구조를 유지하면서도 group() 인덱스에는 영향을 주지 않는다.

 

또한 여러 값을 한 번에 추출하고 싶다면 캡처 그룹을 여러 개 두면 된다. 예를 들어 다섯 개의 그룹을 만들었다면, 각각 group(1)부터 group(5)까지 순서대로 접근할 수 있다.


정규식과 자바 문자열의 차이

 

의미 정규식 자바 문자열
숫자 \\d "\\\\d"
공백 \\s "\\\\s"
점 문자 \\. "\\\\."

 

정규식을 떠올리면 보통 \\t, \\s, \\d, \\n 같은 표현들을 먼저 생각하게 된다. 그런데 자바에서 정규식을 작성하다 보면 이런 패턴들을 두 번씩 써야 하는 상황을 자주 마주하게 된다

.

이유는 정규식의 문제가 아니라, 자바 문자열 처리 구조 때문이다. 자바에서는 문자열을 사용할 때 JVM이 먼저 문자열 리터럴을 해석하고, 그 다음에야 Pattern 클래스가 해당 문자열을 정규식으로 해석한다.

 

만약 코드에서 "\\d"처럼 한 번만 작성하면, JVM 단계에서 이미 문제가 발생한다. 자바 문자열에서 \\는 이스케이프 문자이기 때문에, JVM은 \\d를 유효한 이스케이프 시퀀스로 인식하지 못한다.

 

그래서 자바에서는 \\\\를 사용해 JVM에게 “이건 이스케이프가 아니라 실제 \\ 문자다”라고 알려줘야 한다. 이렇게 하면 JVM이 먼저 \\\\를 하나의 \\ 문자로 해석하고, 그 결과로 생성된 문자열 \\d가 이후 Pattern 클래스에 전달된다. 그제야 정규식 엔진은 이를 “숫자 모양 문자”를 의미하는 \\d로 올바르게 해석할 수 있다.


split은 정규식이다

String s = "a,  b,   c";
String[] arr = s.split("\\\\s*,\\\\s*");

 

이 코드는 공백 0개 이상 + 콤마 + 공백 0개 이상을 기준으로 문자열을 나눈다.

"a   b    c".split("\\\\s+");
"a12b34c".split("\\\\d+");

 

split()은 기준이 되는 패턴을 버리고 좌우를 나눈다.

 

무조건 콤마로 분리하는건가?

무조건 콤마로 분리하는 것은 아니다.

 

단지 앞에서 든 예시가 콤마(,)를 기준으로 분리하도록 작성된 것뿐이다.

 

예를 들어 \\s*,\\s* 같은 패턴은 “콤마로 분리한다”기보다는, 콤마를 기준으로 좌우에 어떤 문자가 올 수 있는지를 정의한 것이다. 여기서 \\s*는 공백 문자가 0개 이상 올 수 있음을 의미하므로, 결국 앞에 공백이 있든 없든 콤마를 기준으로 문자열을 나누겠다는 의미가 된다.

 

이런 관점에서 보면 정규식 분리는 항상 “어떤 문자로 나눈다”기보다는, 중간에 어떤 패턴이 등장하는지를 기준으로 문자열을 좌우로 나누는 작업에 가깝다. 그 기준이 콤마일 수도 있고, 개행 문자(\\n), 탭(\\t), 스페이스 같은 공백 문자일 수도 있다.

 

또한 분리 기준을 지정할 때 반드시 하나의 고정된 문자만 써야 하는 것도 아니다. \\s나 \\d처럼 특정 문자 타입을 의미하는 패턴을 사용할 수도 있고, 여러 조건을 조합해 더 유연한 분리 규칙을 만들 수도 있다.

 

결국 정규식으로 문자열을 나눈다는 것은, 단순히 구분자를 지정하는 것이 아니라 “이 패턴이 나타나는 지점을 기준으로 문자열을 쪼갠다”라고 이해하는 게 가장 정확하다.


자주 사용하는 패턴 정리

\\\\d+                // 숫자 문자
-?\\\\d+              // 마이너스 문자 선택 + 숫자 문자
\\\\s+                // 공백 정규화
^[A-Za-z]+$         // 문자만 허용
^(?:[01]\\\\d|2[0-3]):[0-5]\\\\d$  // 시간(HH:MM)

 

-?\\d+ 같은 정규식을 보면, 이게 숫자를 의미하는지 문자를 의미하는지 헷갈릴 수 있다. 결론부터 말하면 정규식은 ‘숫자’라는 개념을 전혀 모른다. 정규식은 오직 문자열 패턴만을 다룬다.

 

정규식에서 -는 단순한 문자 -일 뿐이고, \\d 역시 실제 숫자가 아니라 숫자 모양을 가진 문자(character)를 의미한다. 즉 \\d는 내부적으로 0부터 9까지의 문자 집합을 가리킨다. 그래서 -?\\d+는 - 문자가 있을 수도 있고 없을 수도 있으며, 그 뒤에 숫자 모양의 문자가 하나 이상 오는 문자열 패턴을 의미할 뿐이다.

 

이 패턴이 실제로 정수인지, 계산 가능한 숫자인지는 정규식의 관심사가 아니다. 정규식은 값의 의미를 판단하지 않고, 오직 형태만을 검사한다.

 

이 때문에 정규식은 보통 입력값 검증(validation)에 많이 사용된다. 사용자가 숫자를 입력하더라도 프로그램이 처음 받는 값은 항상 문자열이다. 정규식은 이 문자열이 숫자 형태인지, 음수를 허용하는 형식인지, 자리 수나 전체 구조가 원하는 조건을 만족하는지를 먼저 확인한다.

 

그 이후에 Integer.parseInt() 같은 변환 과정을 거쳐 실제 숫자로 다루게 된다. 정리하면 정규식은 숫자를 처리하는 도구가 아니라, 문자열이 숫자처럼 생겼는지를 판별하는 도구다. 이 관점으로 보면 -?\\d+ 같은 패턴도 훨씬 명확하게 이해할 수 있다.


정규식은 계산을 위한 도구가 아니라 문자열의 형태를 검사하고 파싱하기 위한 도구다.

처음에는 반복문으로 풀었던 문제였지만,

정규식을 이해하고 나니 문자열 문제를 바라보는 시야가 조금 넓어졌다.

다음에 비슷한 문제를 다시 만난다면, 아마 접근 방식부터 달라질 것 같다.

'Java' 카테고리의 다른 글

Java에서 Map/Set/Hash가 헷갈린다…(3)  (1) 2026.01.06
Java에서 Map/Set/Hash가 헷갈린다…(2)  (0) 2026.01.06
Java에서 Map/Set/Hash가 헷갈린다…(1)  (0) 2026.01.06
'Java' 카테고리의 다른 글
  • Java에서 Map/Set/Hash가 헷갈린다…(3)
  • Java에서 Map/Set/Hash가 헷갈린다…(2)
  • Java에서 Map/Set/Hash가 헷갈린다…(1)
baeminn
baeminn
새로운 기술을 익히는 데 그치지 않고, 실제 문제 해결에 적용하며 구조와 한계를 함께 고민해왔습니다. 서비스 설계, 공간 데이터 분석, 도구 개발 등 다양한 프로젝트를 통해 문제 해결에 필요한 기술을 직접 선택하고 적용해 왔으며, 하나의 역할에 머무르지 않고 필요한 영역까지 책임지는 개발자로 성장하고자 합니다!
  • baeminn
    BaeLog
    baeminn
  • 전체
    오늘
    어제
    • 분류 전체보기 N
      • Programming
      • Java
      • CS
        • DB
        • OS
      • Web
      • Spring
      • GeoInfo
      • Infra N
        • Kubernates
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 깃허브
  • 공지사항

  • 인기 글

  • 태그

    MAP
    AOP
    select
    replicaton
    matcher
    infra
    n+1
    I/O
    Datebase
    dbcp
    spring
    OS
    spring aop
    io
    sharding
    redis
    OOP
    데이터베이스
    쉬운코드
    db
    hash
    Java
    멀티플렉싱
    epoll
    set
    database
    우테코
    poll
    partitioning
    cs
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
baeminn
Java Pattern / Matcher 정리
상단으로

티스토리툴바