Filtering a List With Regular Expressions in Java
1. 서론
이 튜토리얼에서는 Java에서 정규 표현식(regex)에 맞는 요소를 필터링하여 기존 리스트에서 새로운 리스트를 만드는 방법을 배웁니다. 또한 Java에서의 regex를 사용하여 리스트를 필터링하는 다양한 방법을 살펴보겠습니다.
2. 정규 표현식 개요
정규 표현식은 문자열 내에서 특정 문자 시퀀스를 일치시키기 위해 사용되는 패턴입니다. 이들은 텍스트를 필터링, 조작, 교체 및 검증할 수 있는 매우 유용한 도구입니다.
Java는 java.util.regex 패키지를 통해 풍부한 정규 표현식 기능을 제공합니다.
2.1. 일반 특수 문자
우리는 텍스트를 일치시키기 위해 패턴을 만드는 특수 문자 집합을 사용합니다. 이러한 문자를 문자열로 결합하여 정규 표현식을 만듭니다:
“.”: 줄바꿈을 제외한 모든 문자와 일치
“*”: 이전 문자와 동일한 문자(0개 이상)와 일치
“+”: 이전 문자와 동일한 문자(1개 이상)와 일치
“?”: 이전 문자와 일치하는 수가 0개 또는 1개
“^”: 문자열의 시작과 일치
“$”: 문자열의 끝과 일치
“[]”: 대괄호 안에 있는 문자 중 하나와 일치, 예: “[abc]”는 “a”, “b”, 또는 “c”와 일치
“|”: OR 연산, 예: “a|b”는 “a” 또는 “b”
“()”: 그룹화에 사용
2.2. 일반 정규 표현식 단축 표기법
Java에서는 특수 문자 구조를 이스케이프하기 위해 백슬래시 “\”를 사용합니다. 따라서, 하나의 백슬래시가 올바르게 해석되도록 두 개의 백슬래시를 사용합니다.
다음은 일반적으로 사용되는 패턴을 위한 단축 표기법입니다:
\d: 숫자([0-9])와 일치
\w: 단어 문자([a-zA-Z_0-9])와 일치
\s: 공백 문자(공백, 탭, 줄바꿈)와 일치
\D: 비숫자 문자와 일치
\W: 비단어 문자와 일치
\S: 비공백 문자와 일치
3. 정규 표현식을 사용하여 Java에서 리스트를 필터링하는 다양한 방법
정규 표현식은 문자열 형태로 내부에서 결정적 유한 자동자(DFA) 또는 비결정적 유한 자동자(NFA)로 컴파일됩니다. 매처는 이 상태 기계를 사용하여 입력 문자열을 탐색하고 일치시킵니다.
3.1. 패턴 및 프레디케이트와 함께 Stream API 사용하기
Java Stream API는 리스트를 필터링하는 편리한 방법을 제공하며, 이를 Pattern.compile() 클래스와 결합하여 regex 필터링을 적용할 수 있습니다:
필터는 “a”로 시작하는 문자열을 선택하고 출력: [apple, apricot, avocado]를 반환합니다.
3.2. String.matches() 메서드 사용하기
String.matches() 메서드를 사용하여 전체 문자열과 일치시키고 true를 반환하며, 그렇지 않으면 false를 반환합니다:
List<String> filterUsingStringMatches() {
List<String> list = List.of("123", "abc", "456def", "789", "xyz");
return list.stream()
.filter(str -> str.matches("\\d+")).toList();
}
이 코드는 하나 이상의 숫자를 가진 새로운 리스트를 생성합니다. 위 코드의 결과 리스트는: [123, 789]입니다.
3.3. 루프와 함께 Pattern.compile() 사용하기
Stream API를 사용하지 않으려면(JDK 버전 <8), 루프와 Pattern.matcher() 메서드를 사용할 수 있습니다:
List<String> filterUsingPatternCompile() {
List<String> numbers = List.of("one", "two", "three", "four", "five");
List<String> startWithTList = new ArrayList<>();
Pattern pattern = Pattern.compile("^t.*");
for (String item : numbers) {
Matcher matcher = pattern.matcher(item);
if (matcher.matches()) {
startWithTList.add(item);
}
}
return startWithTList;
}
위 코드는 “t”로 시작하는 문자열로 새로운 리스트를 생성합니다. 예상 결과는: [two, three]입니다.
3.4. 조건부 그룹화를 위한 Collectors.partitioningBy() 사용하기
Stream API를 사용하여 Pattern.compile() 메서드로 요소를 필터링하고 두 개의 리스트로 조건부 그룹화할 수도 있습니다:
Map<Boolean, List<String>> filterUsingCollectorsPartitioningBy() {
List<String> fruits = List.of("apple", "banana", "apricot", "berry");
Pattern pattern = Pattern.compile("^a.*");
return fruits.stream()
.collect(Collectors.partitioningBy(pattern.asPredicate()));
}
이 코드는 다시 “a”로 시작하는 요소를 필터링하며 예상 결과는:
Matches(key=true): [apple, apricot]
Non-Matches(key=false): [banana, berry]
4. 결론
이 기사에서는 정규 표현식을 사용하여 리스트를 필터링하는 몇 가지 기술을 살펴보았습니다. 여러 옵션 중에서, Stream API를 사용하는 것이 가독성과 간결한 문법으로 돋보입니다.
또한, 이를 Pattern 및 Predicate와 결합하면 대용량 데이터셋을 처리할 때 매우 효율적입니다. Pattern은 한 번만 컴파일되고 재사용되므로 처리 시간을 절약합니다.
더욱이 Stream API는 여러 작업을 원활하게 체인할 수 있어 뛰어난 성능을 발휘합니다. 물론, 특정 요구 사항에 따라 다른 방법들을 사용할 수 있지만, Stream API는 일반적으로 명확성과 성능 간의 완벽한 균형을 이룹니다.
매우 소스 코드는 GitHub에서 확인할 수 있습니다.