<aside> 💡 필요할 때까지 지연 초기화를 하지 말것.
</aside>
지연 초기화 (lazy initialization)은 필드의 초기화 시점을 그 값이 처음 필요할 때까지 늦추는 기법이다.
대부분의 상황에서는 일반적인 초기화가 지연 초기화보다 낫다.
//일반적인 초기화
private final FieldType field = computeFieldValue();
//인스턴스 필드의 지연 초기화 - synchronized 접근자 방식
//지연 초기화가 초기화 순환성을 깨뜨릴 것 같다면 synchronized를 단 접근자 사용
// 이 방법이 가장 간단, 명확한 대안이다.
private FieldType field2;
private synchronized FieldType getField2() {
if (field2 == null)
field2 = computeFieldValue();
return field2;
}
//정적 필드용 지연 초기화 홀더 클래스 관용구
//성능 때문에 정적 필드를 지연 초기화해야 한다면 지연 초기화 홀더 클래스 관용구 사용
//클래스는 클래스가 처음 쓰일 때 비로소 초기화된다는 특성 이용한 관용구.
private static class FieldHolder {
static final FieldType field = computeFieldValue();
}
//getField가 처음 호출되는 순간 FieldHolder.field가 처음 읽히면서
//FieldHolder 클래스 초기화를 촉발함
//getField 메서드가 필드에 접근하면서 동기화를 전혀 하지 않아 성능이 느려질 거리가 없다.
private static FieldType getField() { return FieldHolder.field; }
// 인스턴스 필드 지연 초기화용 이중검사 관용구
// 성능 때문에 인스턴스 필드를 초기화해야 한다면 사용
// 초기화된 필드에 접근할 때의 동기화 비용을 없애준다.
//필드가 초기화된 후로는 동기화하지 않아서 해당 필드는 반드시 volatile로 선언해야함
private volatile FieldType field4;
private FieldType getField4() {
//필드가 이미 초기화된 상황에서 필드를 딱 한 번만 읽도록 보장함
FieldType result = field4;
if (result != null) // 첫 번째 검사 (락 사용 X)
return result;
synchronized(this) {
if (field4 == null) // 두 번째 검사 (락 사용)
field4 = computeFieldValue();
return field4;
}
}
// 단일검사 관용구 - 초기화가 중복해서 일어날 수 있다.
// 반복해서 초기화해도 상관없는 인스턴스 필드를 지연 초기화해야 할 때 사용
// 이중검사 관용구에서 두 번째 검사 생략
private volatile FieldType field5;
private FieldType getField5() {
FieldType result = field5;
if (result == null)
field5 = result = computeFieldValue();
return result;
}
private static FieldType computeFieldValue() {
return new FieldType();
}