아이템 3. private 생성자나 열거 타입으로 싱글턴임을 보증하라 - KwangtaekJung/book-effective-java GitHub Wiki

아이템 3. private 생성자나 열거 타입으로 싱글턴임을 보증하라.

싱글턴(singleton) 이란

  • 인스턴스를 오직 하나만 생성할 수 있는 클래스
  • 전형적인 싱글턴의 예로는 Item24에 나오는 함수와 같은 무상태(stateles) 객체나 설계 상에 유일 해야하는 시스템 컴포넌트

싱글턴을 만드는 방식

  • 첫번재, public static final 필드 방식의 싱글턴
    • 리플렉션을 통해 생성자를 호출할 수는 있지만 생성자에서 예외를 던지도록 만들어서 방어할 수 있다.
    • 싱글턴임을 명확하게 파악할 수 있고 비교적 코드가 간결해 지는 장점
package book.effective.java.ch02.item03;

public class ElvisWithPublicStaticFinal {

    public static final ElvisWithPublicStaticFinal INSTANCE = new ElvisWithPublicStaticFinal();

    private ElvisWithPublicStaticFinal() {
        //...
    }

    public void leaveTheBuilding() {
        //...
    }
    @Test
    @Order(10)
    @DisplayName("public static final 필드 방식의 싱글턴")
    public void singleton_PublicStaticFinal() {
        ElvisWithPublicStaticFinal elvisWithPublicStaticFinal1 = ElvisWithPublicStaticFinal.INSTANCE;
        ElvisWithPublicStaticFinal elvisWithPublicStaticFinal2 = ElvisWithPublicStaticFinal.INSTANCE;

        System.out.println("elvisWithPublicStaticFinal1 = " + elvisWithPublicStaticFinal1);
        System.out.println("elvisWithPublicStaticFinal2 = " + elvisWithPublicStaticFinal2);

        assertThat(elvisWithPublicStaticFinal1).isEqualTo(elvisWithPublicStaticFinal2);
    }

    @Test
    @Order(11)
    @DisplayName("public static final 필드 방식의 싱글턴 - 예외: reflection")
    public void singleton_PublicStaticFinal_reflaction() throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
        ElvisWithPublicStaticFinal singletonInstance = ElvisWithPublicStaticFinal.INSTANCE;
        System.out.println("singletonInstance = " + singletonInstance);
        ElvisWithPublicStaticFinal singletonReflection;

        Constructor<ElvisWithPublicStaticFinal> constructor = ElvisWithPublicStaticFinal.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        singletonReflection = constructor.newInstance();
        System.out.println("singletonReflection = " + singletonReflection);

        assertThat(singletonInstance).isNotEqualTo(singletonReflection);
    }
  • 두번재, Item1 정적 팩터리 방식의 싱글턴
    • getInscance 메소드는 항상 같은 인스턴스를 반환하기 때문에 역시 인스턴스가 하나뿐임을 보증한다.
    • 리플렉션을 통한 생성은 첫번째 방법과 마찬가지로 막을 수 있다.
    • 이 방법은 싱글턴이 아닌 클래스로 변경할 때 이점을 가진다. 또 제네릭을 사용할 수 있다.
public class ElvisWithStaticFactory {

    private static final ElvisWithStaticFactory INSTANCE = new ElvisWithStaticFactory();
    private ElvisWithStaticFactory() {
        //...
    }
    public static ElvisWithStaticFactory getInstance() {
        return INSTANCE;
    }

    public void leaveTheBuilding() {
        //...
    }
}
  • 위 두 방법의 단점은 직렬화를 위해 Serializable 을 구현할 때 발생한다.

    • 직렬화된 인스턴스를 역직렬화할 때 마다 새로운 인스턴스가 만들어지기 때문이다.
    • 이를 방어하기 위해서는 모든 인스턴스필드를 transient 선언하고 readResolve 메서드를 제공해야한다.
  • 세번째, 열거 타입 방식의 싱글턴

    • 대부분의 상황에서 원소가 하나뿐인 열거 타입이 싱글턴을 만드는 가장 좋은 방법이다.??
    • 하지만 다른 클래스를 상속해야 한다면 이 방법은 사용할 수 없습니다.
public enum Elvis {
    INSTANCE;

    public void leaveTheBuilding() {
        //...
    }
}

참고