[Effective Java] item6. 불필요한 객체 생성을 피하라
1. 문자열 객체의 재사용
똑같은 기능의 객체를 매번 생성하기보다는 객체 하나를 재사용하는 편이 나을 때가 많다.
재사용은 빠르고 세련되다.
불변 객체는 언제든 재사용할 수 있다.
이 문장이 반복문이나 빈번히 호출되는 메서드 안에 있다면 쓸데없는 String 인스턴스가 만들어질 수도 있다.
2. static 팩터리 메서드를 제공하는 불변 클래스 사용하기
정적 팩터리 메서드를 사용해 불필요한 객체 생성을 피할 수 있다.
- 생성자는 호출할 때마다 새로운 객체를 생성, 팩터리 메서드는 전혀 그렇지 않다.
- 불변 객체만이 아니라 가변 객체라 해도 사용 중에 변경되지 않을 것임을 안다면 재사용할 수 있다.
3. 무거운 객체는 캐싱하여 성능을 끌어올리자!
String.matches 메서드의 문제점
[성능]
- String.matches는 정규표현식으로 문자열 형태를 확인하는 가장 쉬운 방법이지만, 성능이 중요한 상황에서 반복해 사용하기엔 적합하지 않다.
[정규표현식용 Pattern 인스턴스]
- String.matches 메서드가 내부에서 만드는 정규표현식용 Pattern 인스턴스는, 한번 쓰고 버려져서 곧바로 GC대상이 된다.
- Pattern은 입력받은 정규표현식에 해당하는 유한 상태 머신(finite state machine)을 만들기 때문에 인스턴스 생성 비용이 높다.
[성능 개선]
- Pattern 인스턴스를 클래스 초기화(정적 초기화) 과정에서 직접 생성해 캐싱해 두고, 나중에 isRomanNumeralAfter 메서드가 호출될 때마다 이 인스턴스를 재사용한다.
- Pattern 인스턴스를 static final 필드로 꺼내고 이름을 지어주어 코드의 의미가 잘 드러난다. 성능이 좋아진다.
- isRomanNumeralBefore의 소요 시간: 475.758833 ms
- isRomanNumeralAfter의 소요 시간: 27.310667 ms
지연 초기화(Lazy initialization, 아이템83)
개선된 isRomanNumeralAfter 클래스가 초기화된 후 이 메서드를 호출하지 않는다면 ROMAN 필드는 쓸데없이 초기화된 것이다.
//코드 83-3 정적 필드용 지연 초기화 홀더 클래스 관용구
private static class FieldHolder {
static final FieldType field = computeFieldValue();
}
private static FieldType getField() {
return FieldHolder.field;
}
- 불필요한 초기화를 없앨 수는 있지만, 권하지 않는다.
- 성능이 크게 개선되지 않을 때가 많다(아이템 67)
4. 어댑터(View)
객체가 불변이라면 재사용해도 안전함이 명백하지만 덜 명확하거나 직관에 반대되는 상황도 있다.
어댑터는 실제 작업은 뒷단 객체에 위임하여 뒷단 객체 외에는 관리할 상태가 없으므로 뒷단 객체 하나당 어댑터 하나씩만 만들어지면 충분하다.
Map 인터페이스의 keySet 메서드는 Map 객체 안의 키 전부를 담은 Set뷰를 반환한다.
keySet을 호출할 때마다 새로운 Set 인스턴스가 만들어질 것 같지만, 매번 같은 Set 인스턴스를 반환할지도 모른다.
때문에 반환한 객체 중 하나를 수정하면 다른 모든 객체가 바뀐다. -> Set 타입 객체를 변경하면 Map 타입 객체가 바뀐다.
5. 오토박싱(auto boxing)
오토박싱은 기본 타입과 그에 대응하는 박싱 된 기본 타입의 구분을 흐려주지만, 완전히 없애주는 것은 아니다.
성능의 차이가 난다(아이템 61)
- 기본 타입과 박싱 된 기본 타입의 차이점 3가지
- 첫째, 기본 타입은 값만 가지고 있으나, 박싱 된 기본 타입은 값에 더해 식별성(identity)이란 속성을 갖는다.
- 둘째, 기본 타입의 값은 언제나 유효하나, 박싱 된 기본 타입은 유효하지 않은 값 (null)을 가질 수 있다.
- 셋째, 기본 타입이 박싱된 기본 타입보다 시간과 메모리 사용면에서 더 효율적이다.
박싱된 기본 타입보다는 기본 타입을 사용하고, 의도치 않은 오토박싱이 숨어들지 않도록 주의하자
- Time taken with Long object: 0.005237 ms.
- Time taken with primitive long: 0.001104 ms.
6. 단순히 객체 생성을 피하고자 여러분만의 객체 풀(pool)을 만들지는 말자
일반적으로 자체 객체 풀은 코드를 헷갈리게 만들고 메모리 사용량을 늘리고 성능을 떨어뜨린다.
JVM의 가비지 컬렉터는 잘 최적화되어 가벼운 객체용을 다룰 때는 직접 만든 객체 풀보다 훨씬 빠르다
객체 풀을 만드는 게 나은 예) 데이터베이스 연결은 생성 비용이 비싸 재사용하는 편이 낫다.
방어적 복사(defensive copy) 아이템 50 : 방어적인 복사를 해야 하는 경우에도 객체를 재사용하면 심각한 버그와 보안성에 문제가 생긴다