private 생성자 사용하기 - Effective java

우리는 보통 public 생성자를 사용해서 클래스의 인스턴스를 얻는다. 그러나 생성자를 private 으로 지정한다면, 해당 클래스 외부에서 생성자를 사용해 인스턴스를 생성할 수 없다. 

 

그렇다면 private 생성자를 어떤 경우에 사용하면 좋을지 알아보도록 하자

 

private 생성자나 열거 타입으로 싱글턴임을 보장하기

싱글톤 (Singleton) 클래스란 인스턴스가 오직 하나만 존재하는 클래스를 말한다. 그런데 클래스를 싱글톤으로 만들면 이를 사용하는 클라이언트를 테스트하기가 어려워진다. 타입을 인터페이스로 정의한 다음 그 인터페이스를 구현해서 만든 싱글톤이 아니라면 싱글톤 인스턴스를 가짜(mock) 구현으로 대체할 수 없다.

 

싱글톤을 만드는 방식은 보통 두 가지가 존재한다. 일단 두 방식 모두 생성자를 private으로 감싸서 외부에서 절대 인스턴스를 생성할 수 없게 만든다.

 

1. 유일한 인스턴스에 접근할 수단으로 public static 멤버 마련하기

public class Singleton {
    public static final Singleton instance = new Singleton();

    private Singleton() {

    }
}

private 생성자는 public static final 필드인 Singleton.instance 를 초기화할 때 딱 한 번 호출된다. public 이나 protected 생성자가 없으므로 Singleton 클래스의 인스턴스는 전체 시스템에서 단 하나임이 보장된다. 

 

2. 정적 팩토리 방식의 싱글톤

public class Singleton {
    private static final Singleton instance = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return instance;
    }
}

Singleton 클래스의 인스턴스를 얻기 위해 getInstance()를 호출해야 한다. 이 함수는 항상 같은 참조를 반환하므로 제 2의 Singleton 인스턴스는 절대 만들어지지 않는다.

 

정적 팩토리 방식의 장점은 API 를 바꾸지 않고도 싱글톤이 아니게 변경할 수 있다는 점이다. 유일한 인스턴스를 반환하던 팩토리 메소드가 호출하는 스레드별로 다른 인스턴스를 넘겨줄 수 있다. 

 

직렬화 시 문제점

둘 중 하나의 방식으로 만든 싱글톤 클래스를 직렬화하려면 단순히 Serializable 을 구현한다고 선언하는 것만으로는 부족하다. 모든 인스턴스에 transient 라고 선언하고 readResolve 메소드를 제공해야 한다. 이렇게 하지 않으면 직렬화된 인스턴스를 역직렬화할 때 마다 새로운 인스턴스가 만들어진다. 가짜 인스턴스의 탄생을 예방하고 싶다면 다음의 readResolve 메서드를 추가해야 한다.

 

private Object readResolve() {
    return instance;
}

 

3. 열거 타입을 선언해서 싱글톤 만들기

public enum Elvis {
    INSTANCE;

    public void leaveTheBuilding() {

    }
}

public 필드 방식과 비슷하지만, 더 간결하고, 추가 노력 없이 직렬화할 수 있다. 직렬화 상황이나 리플렉션 공격에서도 제 2의 인스턴스가 생기는 일을 완벽히 막아준다.

 

조금 부자연스러워 보이긴 하나 대부분의 상황에서는 원소가 하나뿐인 열거 타입의 싱글톤을 만드는 것이 가장 좋은 방법이다. 단 만들려는 싱글톤이 Enum 외의 클래스를 상속해야 한다면 이 방법은 사용할 수 없다. (열거 타입이 다른 인터페이스를 구현하도록 선언할 수는 있다)

 

 

Reflection API 를 사용해 새로운 인스턴스를 생성할 수 있다.

Reflection API 를 사용해 새로운 인스턴스를 생성할 수 있다.

이러한 공격을 방어하려면 생성자를 수정하여 두 번째 객체가 생성되려 할 때 예외를 던지게 하면 된다.