똑같은 기능의 객체를 매번 생성하기보다는 객체 하나를 재사용하는 편이 나을 때가 많다.
특히 불변 객체는 언제든 재사용할 수 있다.
그렇기에 생성자 대신 정적 팩터리 매서드를 제공하는 불변 클래스에서는 정적 팩터리 메서드를 사용해 불필요한 객체 생성을 피할 수 있다.
생성 비용이 아주 비싼 객체도 더러 있다.
이런 '비싼 객체'가 반복해서 필요하다면 캐싱하여 재사용하길 권한다.
예시를 통해 불필요한 객체가 어떤 것인지 파악해보자.
static boolean isRomanNumeral(String s) {
return s.matches("로마자 파악 regex");
}
위의 예제 코드의 String.matches는 정규표현식으로 문자열 형태를 확인하는 가장 쉬운 방법이지만, 성능이 중요한 상황에서 반복해서 사용하기엔 적합하지 않다.
이 메서드가 내부에서 만드는 Pattern 인스턴스는, 한 번 쓰고 버려져서 곧바로 가비지 컬렉션 대상이 된다.
Pattern은 입력받은 정규표현식에 해당하는 유한 상태 머신(finite state machine)을 만들기 때문에 인스턴스 생성 비용이 높다.
성능을 개선하려면 정규 표현식을 표현하는 (불변인) Pattern 인스턴스를 클래스 초기화(정적 초기화) 과정에서 직접 캐싱해두고, 나중에 isRomanNumeral 메서드가 호출될 때마다 이 인스턴스를 재사용하는 방법이 있다.
어댑터는 실제 작업은 뒷단 객체에 위임하고, 자신은 제 2의 인터페이스 역할을 해주는 객체다.
어댑터는 뒷단 객체만 불필요한 객체를 생성하지 않도록 관리하면 된다.
위는 불필요한 객체 생성을 피하는 어댑터 구현에 대하여 설명한 것이다. 자세한 어댑터에 대한 설명은 링크를 참고하면 된다.
예를 들어 Map 인터페이스의 keySet 메서드는 Map 객체 안의 키를 전부 담은 Set 뷰를 반환한다.
keySet을 호출할 때마다 새로운 Set 인스턴스가 만들어지는 것이 아니다.
// AbstractMap 클래스
public abstract class AbstractMap<K,V> implements Map<K,V> {
transient Set<K> keySet;
...
}
// HashMap 클래스
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
public Set<K> keySet() {
Set<K> ks = keySet; // AbstractMap의 맴버변수 keySet을 사용
if (ks == null) {
ks = new KeySet(); // 없는 경우에만 새로운 KeySet 생성
keySet = ks;
}
return ks;
}
...
}