객체 생성과 파괴(Item 6)
Item 6. 불필요한 객체 생성을 피하라
말 그대로 ‘불필요한’ 객체 생성을 피해야 한다. 필요한 객체는 생성해서 사용하면 된다.
그렇다면 불필요한 객체 생성에는 어떤 경우가 있을까?
메모리가 낭비되는 경우
첫 번째로 나오는 예시는 String
사용 방법이다.
String
클래스는 자바에서 기본적으로 사용하는 문자열 클래스이며 불변으로 설계되어 있다.
문자열은 가장 많이 쓰는 데이터 중 하나이기 때문에 자바에서도 특별하게 취급되는데 그 중 하나가 바로 문자열 리터럴이다.
String
클래스도 생성자가 있지만 생성자를 사용하게 되면 당연히 객체가 새로 생성된다.
문자열 리터럴을 사용할 경우 JVM이 내부적으로 String
객체를 재사용한다.
즉, 다음과 같은 코드는 절대 사용해서는 안 되는 코드다.
String s = new String("java");
위 코드를 개선한 코드를 보자.
String s = "java";
비슷한 예시로 Boolean
클래스가 있다.
Boolean(String)
생성자 대신에 Boolean.valueOf(String)
메서드를 사용하게 되면 객체를 재사용하는 이점을 누릴 수 있다.
객체 생성 비용이 비싸 시간이 오래 걸리는 경우
객체 생성 비용이 비싼 경우에는 해당 객체를 재사용하는 것을 고려해야 한다. 무심코 지나갈 수 있는 코드를 살펴보자.
public class RomanNumerals {
static boolean isRomanNumeralSlow(String s) {
return s.matches("^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
}
}
위 메서드는 문자열이 유효한 로마 숫자인지 확인하는 메서드이다. 너무 간단하게 작성해서 성능을 생각할 겨를이 있나 싶지만 메서드 내부를 따라가 보면 이런 코드를 마주하게 된다.
public static Pattern compile(String regex) {
return new Pattern(regex, 0);
}
문자열 검증을 하는 데 있어 Pattern
객체를 생성함을 볼 수 있다.
이 객체는 RomanNumerals.isRomanNumeralSlow()
메서드가 실행될 때마다 새로 생성되고 곧바로 버려진다.
가볍게 생성할 수 있는 객체라면 문제 없다고 생각할 수 있겠으나 Pattern
객체의 생성 비용은 꽤 크기 때문에 문제가 발생한다.
만약에 위 메서드가 자주 사용된다고 하면 시간적인 비용이 아주 많이 들 것이다.
대안은 Pattern
객체를 재사용하는 것!
어차피 로마 숫자인지 확인할 때의 Pattern
객체는 늘 하는 일이 같은 객체가 생성되기 때문에 충분히 재사용할 여지가 있다.
다음 개선된 코드는 메서드 반복 시 걸리는 시간을 획기적으로 줄여준다.
public class RomanNumerals {
private static final Pattern ROMAN = Pattern.compile(
"^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
static boolean isRomanNumeralFast(String s) {
return ROMAN.matcher(s).matches();
}
}
Pattern
객체를 상수 필드로 초기화하여 재사용한다.
RomanNumerals.isRomanNumeralFast()
메서드는 반복 실행되더라도 계속해서 Pattern
객체를 만들지 않고 최초 초기화 된 객체를 가져다가 사용한다.
오토박싱으로 인해 불필요한 객체를 생성하는 경우
오토박싱은 자칫 넘어갈 수 있는 문제 중 하나이다. 오토박싱은 기본 타입을 자동으로 참조 타입으로 바꿔주어 개발자들을 도와주지만 이러한 점이 무심코 성능을 떨어뜨리는 원인이 될 수 있다.
public class Sum {
private static long sum() {
Long sum = 0L;
for (long i = 0; i < Integer.MAX_VALUE; i++)
sum += i;
return sum;
}
}
성능이 떨어지는 지점이 보이는가? 오토박싱은 직관적으로 느껴지지 않아 세심히 살펴봐야 한다. 문제의 코드는 바로 여기다.
sum += i;
long
타입의 변수 i
가 Long
타입의 변수 sum
에 더해질 때, i
는 오토박싱 되어 Long
객체가 생성된다.
이 작업을 반복이 끝날 때까지 반복한다. 즉, 객체 생성이 2^31번 이루어진다.
sum
의 타입을 long
으로 바꾼다면 시간이 획기적으로 단축되는 것을 쉽게 확인할 수 있다.
이것은 오토언박싱에도 동일하게 적용되며 직접 확인해 본 결과 sum
의 타입을 long
, i
의 타입을 Long
으로 지정해도 비슷한 문제가 발생했다.
되도록이면 기본 타입을 사용하자. 기본 타입도 형변환이 발생하긴 하지만 (int
타입을 long
타입으로 변환하는 등) 그것은 큰 비용이 발생하지는 않는다.
실제로 sum
의 타입을 long
, i
의 타입을 int
로 지정해 보았지만 시간 차이는 크게 발생하지 않았다.
[참고]
백기선, inflearn 강의 이펙티브 자바 완벽 공략 1부
Joshua Bloch, 이펙티브 자바