아이템 6 : 불필요한 객체 생성을 피하라
같은 기능의 객체를 매번 생성하는 것보다 만들어진 하나의 객체를 재사용하는 것이 더 효율적인 경우가 많다. 특히 불변 객체(아이템 17)는 언제던지 재사용이 가능하다
String s = new String("bikini");
이 코드를 통해 매 번 쓰이지 않는 String 인스턴스가 (극단적으로) 수백만 개 만들어질 수 있다. "bikini" 자체가 만드려는 객체와 기능이 완전히 똑같아 객체를 생성할 필요가 없지만, 생성자로 객체를 생성하게 되면 호출 시마다 새로운 객체가 만들어지는 것이다
String s = "bikini";
이 방식을 사용하면 String s에 새로운 String을 생성하는 것이 아니라 기존 객체를 계속 할당하므로, 같은 가상머신 안에서 똑같은 문자열을 사용하는 모든 코드가 같은 객체를 재사용하는 것이 보장된다
생성자 대신 정적 팩터리 메서드를 활용하면 호출 시 마다 새로운 객체를 만들지 않기 때문에 불필요한 객체 생성을 피할 수 있다
생성 비용이 비싼 객체도 있을 수 있다. 이런 객체는 캐싱해서 재사용하는 것이 바람직하며, 그 비용을 알아보는 것이 좋다
static boolean isRomanNumeral(String s) {
return s.matches("^(?=.)M*(C[MD]|D?C{0,3})" + "(X[CL]|L?X{0,3})(I[XY]|V?{0,3})$");
}
이 메서드는 매번 호출 시마다 String.matches()를 실행해 정규표현식을 체크하는 비효율이 생긴다(정규 표현식 vaildation조건 은 변화가 없는데 매번 유효성 검사를 다시하고, 이 인스턴스는 더이상 쓰이지 않기 때문에 곧바로 GC 처리대상이 된다)
이를 개선하는 방법은 클래스 초기화 시, 해당 표현식 인스턴스를 캐싱하고 재사용하면 된다
public class RomanNumerals {
private static final Pattern ROMAN = Pattern.complie(
"^(?=.)M*(C[MD]|D?C{0,3})" + "(X[CL]|L?X{0,3})(I[XY]|V?{0,3})$");
static boolean isRomanNumeral(String s) {
return ROMAN.matcher(s).matches();
}
}
성능도 개선되고 해당 유효성 검사 조건을 정적 필드로 할당했기 때문에 코드의 의미도 더욱 명확해진다. 그러나 이 방식으로 구현한 클래스가 초기화 된 이후 한번도 호출되지 않는다면
(메서드가 처음 호출될 때 필드를 초기화하는 지연 초기화(아이템83)로 불필요한 초기화를 없앨 수는 있으나 권장되지 않는다 ~ 코드가 복잡해지기만 하고 성능 개선 효과는 미미함)
불필요한 초기화가 일어나는 것이다
객체가 불변이라면 불안해하지 않고 재사용할 수 있다. 다만 재사용하는 게 덜 명확하고 직관적이지 않은 케이스도 있다. 어댑터[Gamma95]의 경우(뷰 view라고도 한다)의 경우 뒷단 객체가 실제 로직을 수행하고, 어댑터 객체 스스로는 인터페이스의 기능을 한다. 어댑터는 뒷단 객체만 관리하면 되기 때문에 뒷단 객체 하나당 어댑터가 하나만 있으면 된다
Map 인터페이스의 keySet 메서드는 map 객체 안의 키 전부를 담은 set 뷰를 반환하는 메서드로, 이 set 인스턴스는 재사용되는 인스턴스다. 그렇기 때문에 어느 하나의 set 인스턴스를 수정하면 다른 모든 인스턴스가 변경되므로, 이 keySet에 따라 반환되는 set에 대한 view 객체를 여러 개 만드는 것이 유의미한 일은 아니다(가능하긴 하다)
불필요한 객체를 만드는 다른 케이스로는, 오토 박싱이 있다. 오토 박싱은 기본 타입과 그에 대응하는 박싱된 기본 타입의 구분을 흐려주지만, 완전히 없애는 것이 아니다. 성능 상의 차이가 발생한다(아이템 61)
모든 양의 정수의 합을 구하는 메서드가 그 예시다
private static long sum() {
Long sum = 0L;
for(long i = 0; i <= Integer.MAX_VALUE; i++) {
sum += i;
}
return sum;
}
이 메서드의 경우, sum 변수를 long이 아닌 Long으로 선언해 불필요한 Long 인스턴스가 2^31만큼 더 만들어진 것이다. 필요한 경우가 아니면, 기본 타입을 사용하고 무분별한 오토박싱은 피하는 것이 좋다
이와 같은 비싼 객체 생성을 예방한다고, 자체 객체 풀을 활용하는 게 무조건 정답은 아니다. 데이터베이스 연결 등의 경우가 아니고선, 코드를 더 헷갈리게 만들고 메모리 사용량을 늘리고 성능이 저하된다
방어적 복사(아이템 50)과는 대조되는 주제인 아이템 6이다
기존 객체를 재사용해야 한다면 새로운 객체를 만들지 마라(아이템 6)
vs 새로운 객체를 만들어야 한다면 기존 객체를 재사용하지 마라(아이템 50)
아이템 50으로 인한 피해가 더 크다. 버그와 보안 구멍으로 이어질 수 있으며, 그에 반해 아이템 6은 코드 형태와 성능에만 영향을 준다
'자바☕ > 이펙티브 자바' 카테고리의 다른 글
이펙티브 자바 읽고 정리해보기 8. (1) | 2024.10.03 |
---|---|
이펙티브 자바 읽고 정리해보기 7. (0) | 2024.08.31 |
이펙티브 자바 읽고 정리해보기 4 & 5. (0) | 2024.08.15 |
이펙티브 자바 읽고 정리해보기 3. (0) | 2024.07.21 |
이펙티브 자바 읽고 정리해보기 2. (0) | 2024.07.12 |