[Effective Java] Primitive Type 과 Wrapper Class
자바에는 여러 기본 자료형(Primitive Type) 과 해당 자료형에 대한 Wrapper Class를 제공한다. 우리는 이 Wrapper Class를 이용해 기본형을 객체로 생성해 이용할 수가 있다.
각 기본형에 해당하는 Wrapper Class 와 사용 방법은 아래와 같다.
기본형 | Wrapper Class | 예시 |
boolean | Boolean | Boolean a = new Boolean(true); boolean b = new Boolean("false"); |
char | Character | Character a = new Character('a'); |
byte | Byte | Byte a = new Byte(3); Byte b = new Byte("100"); |
short | Short | Short a = new Short(10); Short b = new Short("1000"); |
int | Integer | Integer a = new Integer(10); Integer b = new Integer("1000"); |
long | Long | Long a = new Long(10); Long b = new Long("1000"); |
float | Float | Float a = new Float(12.12f); Float b = new float("12.12f"); |
double | Double | Double a = new Double(12.12); Double b = new Double("12.12"); |
Effective Java 에 따르면 Wrapper class 를 이용한 Boxing Type 을 이용하는 것 보다는 Primitive Type 사용을 권장한다.
아래 코드를 보자.
public static long sum() {
Long sum = 0L;
for(long i = 0 ; i<=Integer.MAX_VALUE; i++) {
sum+= i;
}
return sum;
}
public static long sum() {
long sum = 0L;
for(long i = 0 ; i<=Integer.MAX_VALUE; i++) {
sum+= i;
}
return sum;
}
0부터 Integer 의 최대 값까지 수를 더한 값을 리턴하는 간단한 메서드인데, 하나는 Boxing Type을 하나는 Primitive Type을 사용하고 있다. 실제로 해당 메서드를 수행해보면 Boxing Type 을 사용한 것 보다 Primitive Type 을 사용하는 것이 훨씬 빠르게 동작한다.
그 이유는 Long Type 의 sum 객체가 반복문이 한번 돌아갈 때마다 계속 생성되기 때문이다. 실제로 실무에서 리팩토링을 하다보면 레거시 코드에 이런 문제가 간혹 발견되곤 한다.
또한 Boxing Type을 남용하는 것을 주의하는 것도 중요하지만 , 위 코드처럼 의도하지 않은 Auto Boxing 도 주의해야한다.
하지만 무작정 기본형 타입만 고집하는 것도 답은 아니다. 간혹가다가 특정한 값을 null 로 처리해야할 경우가 있다. 예를 들어 특정 상품에 대한 가격을 산정하는데, 아직 정해지지 않았거나 무료인 상품에 대해서는 null로 DB에 저장하기로 한다면 , 어플리케이션 단에서 해당 상품에 대한 가격을 0이 아닌 null 로 넣어줘야하는데 이렬 경우 Wrapper Class 를 이용하여 null 로 처리할 수가 있다.
예제를 하나 더 살펴보자.
만약에 유효한 이메일 형식인지를 판별하는 메서드를 작성한다고 가정해보면 아래와 같이 작성할 수가 있다.
static boolean isEmailValid(String s) {
return s.matches("\\w+@\\w+\\.\\w+(\\.\\w+)?");
}
위 메서드를 뜯어보면 어떠한 문제가 있음을 우리는 알 수 있다.
String 클래스의 matches 메서드를 실제로 확인해보면 아래와 같이 Pattern 클래스의 matches 메서드를 호출하여 리턴하도록 되어 있다.
public boolean matches(String expr) {
return Pattern.matches(expr, this);
}
Pattern 클래스의 matches 메서드도 뜯어보자.
public static boolean matches(String regex, CharSequence input) {
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(input);
return m.matches();
}
문제가 무엇인지 알겠는가? 우리는 유효한 이메일인지를 체크할 때마다 한번만 쓰고 말 패턴 객체를 계속해서 생성해버리고 있는 것이다.
실제로 실무에서 특정 파일의 확장자를 확인하는 메서드가 이런식으로 구현되어서 라이브환경에서 OOM 이 지속적으로 발생한 적 있기에 조심해야하는 부분이다.
그렇다면 이러한 로직은 어떻게 최적하는 것이 좋을까? 아래 코드를 보자.
private static final Pattern EMAIL = Pattern.compile("\\w+@\\w+\\.\\w+(\\.\\w+)?");
static boolean isEmailValid(String s) {
return EMAIL.matcher(s).matches();
}
위와 같이 패턴 인스턴스가 매번 생성되지 않고 한번만 생성되도록 패턴 변수를 초기화 시켜놓는 방법을 사용할 수가 있다. 위와 같은 아주 간단한 방법으로 최적활 시킬 수가 있다.
여러분도 코딩할 때 이런부분도 고려해서 작업을 해보자.
Ref.
조슈아 블로크, 『이펙티브 자바』, 프로그래밍인사이트(2020), p31-p35