Java

Java Pattern 클래스 사용 시 반드시 알아야 할 주의점

제주니어 2025. 1. 13. 08:53

 

Java에서 정규 표현식을 다룰 때 Pattern 클래스는 매우 중요한 역할을 한다. 하지만 Pattern을 잘못 사용하면 성능 저하나 예상치 못한 동작이 발생할 수 있다.

나는 실제로 프로젝트에서 정규식을 사용할 때 이러한 문제로 지적을 받은 경험이 있었고, 이를 해결하기 위해 Pattern 사용 시의 몇 가지 주의사항을 정리하게 되었다. 

 

 

Pattern 클래스란 무엇인가?

Pattern 클래스는 Java에서 정규 표현식을 다루기 위한 클래스이다. 주로 Pattern.compile(String regex) 메서드를 통해 정규식을 컴파일하고, 이를 기반으로 생성된 Matcher 객체를 사용해 문자열 매칭 작업을 수행한다.

 

예를 들어, 다음과 같은 코드로 정규식을 사용할 수 있다.

Pattern pattern = Pattern.compile("[a-zA-Z]+");
Matcher matcher = pattern.matcher("example");
boolean isMatch = matcher.find(); // true

 

위 코드에서 Pattern.compile()은 정규식을 컴파일하여 반복적으로 사용할 수 있도록 객체를 생성한다. 하지만 정규식을 효율적으로 사용하려면 몇 가지 주의할 점이 있다.

 

 

Pattern 사용 시 주의해야 할 사항

1. Pattern 객체 재사용

Pattern.compile() 메서드는 정규식을 컴파일하는 과정에서 상당한 리소스를 소비한다. 따라서 동일한 정규식을 반복적으로 사용해야 한다면, Pattern 객체를 매번 새로 생성하지 말고 재사용하는 것이 좋다.

// 비효율적인 코드
Pattern pattern = Pattern.compile("[a-zA-Z]+");
Matcher matcher = pattern.matcher("example");

// 효율적인 코드
private static final Pattern ALPHABET_PATTERN = Pattern.compile("[a-zA-Z]+");
Matcher matcher = ALPHABET_PATTERN.matcher("example");

 

2. 정규식 복잡도와 성능 문제

정규식은 간결하고 강력한 도구지만, 복잡한 정규식은 성능 저하를 초래할 수 있다. 특히, 잘못된 정규식은 백트래킹(backtracking)을 과도하게 발생시켜 예상치 못한 지연을 유발할 수 있다.

* 백트래킹(backtracking) 이란?

알고리즘 기법 중 하나로 재귀적으로 문제를 해결하되 현재 재귀를 통해 확인 중인 상태가 제한 조건에 위배가 되는지 판단하고, 해당 상태가 위배되는 경우 해당 상태를 제외하고 다음 단계로 넘어간다.
즉, 해를 찾는 도중 해가 절대 될 수 없다고 판단되면, 되돌아가서 다시 해를 찾는다고 생각하면 된다. 모든 가능한 경우의 수 중에서 특정한 조건을 만족하는 경우만 살펴보는 것이라고 볼 수도 있다.

 

정규식에서 백트래킹(backtracking)이 발생하는 이유는, 정규식 엔진이 문자열을 매칭하는 과정에서 다양한 가능 경로를 시도하기 때문이다. 특히, 정규식이 복잡하거나 비효율적으로 작성된 경우 백트래킹이 과도하게 발생하여 성능 문제가 생길 수 있다. 

 

예를 들어, 다음과 같은 정규식은 입력 문자열이 길어질수록 성능 문제가 발생할 가능성이 있다.

String regex = "(a|b|c|d)*e";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher("aaaaaaa"); // 성능 문제 발생 가능

 

3. 특수 문자 이스케이프 처리

정규식에서 특수 문자를 사용할 때는 반드시 이스케이프 처리를 해야 한다. 그렇지 않으면 의도한 매칭이 이루어지지 않거나 예외가 발생할 수 있다.

// 잘못된 코드
String regex = "1.5"; // "."는 모든 문자를 의미
Pattern pattern = Pattern.compile(regex);

// 올바른 코드
String escapedRegex = "1\\.5";
Pattern pattern = Pattern.compile(escapedRegex);

 

4. 입력 데이터 유효성 검사

정규식을 사용하는 경우, 외부에서 들어오는 입력값이 정규식에 적합하지 않은 패턴일 가능성을 항상 염두에 두어야 한다. 예상치 못한 입력으로 인해 정규식이 오작동하거나 성능 문제가 발생할 수 있기 때문이다.

나는 한 번, 예상하지 못한 입력 데이터로 인해 정규식이 무한 루프에 가까운 상황을 유발한 경험이 있었다. 이를 방지하기 위해 입력 데이터 유효성을 추가로 검증하거나, 입력 길이를 제한하는 방식으로 문제를 해결했다.

 

5. Thread-Safe하지 않은 Matcher 사용

Matcher 객체는 Pattern 객체에서 생성되며, Thread-Safe하지 않다. 이를 여러 스레드에서 공유하면 동기화 문제로 인해 예기치 못한 동작이 발생할 수 있다.

// 잘못된 사용 (스레드 공유)
Pattern pattern = Pattern.compile("[a-zA-Z]+");
Matcher sharedMatcher = pattern.matcher("example");

// 올바른 사용 (스레드마다 새로 생성)
Pattern pattern = Pattern.compile("[a-zA-Z]+");
Matcher matcher = pattern.matcher("example");

 

6. Lookbehind와 Lookahead 사용 시 주의

Lookbehind와 Lookahead는 강력한 기능이지만, 잘못 사용하면 성능 문제를 유발할 수 있다. 꼭 필요한 경우에만 사용하고, 대체 가능한 간단한 정규식이 있는지 검토해야 한다.

// 비효율적인 Lookbehind 사용
Pattern pattern = Pattern.compile("(?<=abc)def");

// 효율적인 대체
Pattern pattern = Pattern.compile("abc(def)");

 

정규 표현식과 Pattern 클래스는 데이터를 다루는 데 있어 강력한 도구이다. 하지만 효율성과 안정성을 고려하지 않으면 성능 문제나 버그를 초래할 수 있다. 이 글을 바탕으로 여러분의 코드가 더욱 안전하고 효율적으로 작동하길 바란다.