사실 이 이야기는 Effective Java 6장 - 불필요한 객체 생성을 피하라 에 나오는 아주 기초적인 이야기 이다. 그러나 습관이 되지 않아 생각없이 개발하다보면 어느새 이 규칙을 어겨서 어김없이 리팩토링 대상이 되곤 하는데다가, 막상 개발하면서 쓰려다 보면 사용 방법이 잘 생각나지 않아 메모할 겸 짧은 글을 작성 해본다.
(요즘 좀 정신이 나가 있어서 공부는 꾸준히 하기는 한다만, 공부하는 양은 좀 줄었고, 진도는 좀처럼 나가지가 않는다. 에라이)

일반적으로 Java에서 흔히 사용할 수 있는 정규 표현식의 예를 보자. (Junit5)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Utils {
public static boolean hasOnlyNumbers(String s) {
return s.matches("[\\d]*");
}
}

@DisplayName("Utils 테스트")
class UtilsTest {
@Test
@DisplayName("숫자 외의 문자가 있을 경우 false를 return 해야 한다")
void should_return_false_when_has_other_characters() {
String numbersWithCharacter = "1a2s3d4f5g6";
boolean expected = false;

boolean result = Utils.hasOnlyNumbers(numbersWithCharacter);

assertEquals(expected, result);
}
@Test
@DisplayName("숫자외의 문자가 없을 경우 true를 return 해야 한다")
void should_return_true_when_has_only_numbers() {
String numbersWithCharacter = "123456789";
boolean expected = true;

boolean result = Utils.hasOnlyNumbers(numbersWithCharacter);

assertEquals(expected, result);
}
}

오케이, 원하는 바를 손쉽게 얻었다. 그러나 지금 얻은 결과는 성능상 좋지 못하다. 사실 일반적인 상황에서 String의 matches 메소드를 사용하는게 엄청나게 성능에 문제를 주는 경우는 별로 없으나, 습관이 중요한데다가, 실제로 이런 코드들이 쌓여 문제를 발생하는 경우도 있으니 좋은 습관을 들이는 게 좋다.
이펙티브 자바는 String.matches를 사용하는 것보다 더 고급지고어렵고 성능이 좋은 방법을 알고 있다.

Pattern 클래스를 사용해 보자

바로 Pattern클래스를 사용해 같은 일을 더 성능 좋게 사용할 수 있다.
사실 위 예제의 코드는 메소드가 실행 될때마다 내부적으로 Pattern 인스턴스를 만들고 사용 후 버리는 작업을 수행하게 되는데, 이 Pattern을 final static 객체로 미리 선언해두고 사용하게 되면 메소드가 호출될 때마다 새로 생성하고 버리는 작업이 없어지게 된다.
아래 코드를 보자

1
2
3
4
5
6
public class Utils {
private static final Pattern ONLY_NUMBERS_PATTERN = Pattern.compile("[\\d]*");
public static boolean hasOnlyNumbers(String s) {
return ONLY_NUMBERS_PATTERN.matcher(s).matches();
}
}

테스트를 잘 통과하는 것을 볼 수 있다. 자, 그럼 얼마만큼의 성능 차이가 날까?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@DisplayName("Utils 테스트")
class UtilsTest {

final int times = 10000000;

@Test
@DisplayName("리팩토링 후")
void test_refactor() {
final String numbersWithCharacter = "1a2s3d4f5g6";
for(int i=0; i < times; i++){
Utils.hasOnlyNumbersRefactor(numbersWithCharacter);
}

}
@Test
@DisplayName("리팩토링 전")
void test_before_refactor() {
final String numbersWithCharacter = "1a2s3d4f5g6";
for(int i=0; i < times; i++){
Utils.hasOnlyNumbersOriginal(numbersWithCharacter);
}
}
}

컴퓨팅 성능마다, JVM 셋팅마다 다르겠지만, 메소드 수행을 1000만회 진행 했을때 꽤나 차이가 나는 것을 눈으로 볼 수 있었다.
(2.35초 -> 0.83초)

이펙티브 자바의 아이템6 에는 이 외에도 다른 팁들도 있으나 일단 오늘은 정규 표현식 부분만 정리했다.

정규 표현식을 사용할때는 기억하자.

  1. static final Pattern 으로 불변객체를 생성해서 사용하자
  2. Pattern p = Pattern.compile(“정규식”);
  3. p.matcher(string).matches();