본문 바로가기

독후감/Effective Java

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

싱글턴(singleton)이란 인스턴스를 오직 하나만 생성할 수 있는 클래스이다.
싱글턴을 만드는 방식은 둘 중 하나다. 두 방식 모두 private으로 감추고, 인스턴스에 접근할 수 있는 수단으로 public static 멤버를 하나 만든다.

public class Elvis {
	public static final Elvis INSTANCE = new Elvis();
	private Elvis() {...}

	public void leaveTheBuilding() {...}
}

생성자는 INSTANCE 를 초기화할 때 한 번만 호출된다. 그렇기에 전체 시스템에서 해당 클래스는 하나만 생성됨을 보장할 수 있게된다.(단 향후 배울 리플랙션에서 private 생성자를 호출하는 법을 알게된다.)
해당 코드의 장점은 해당 클래스가 싱글턴임이 명백히 보이며, 코드가 되게 간결해진다는 것이다.

public class Elvis {
	priavte static final Elvis INSTANCE = new Elvis();
	private Elvis() {...}
	
	public static Elvis getInstance( return INSTANCE; }

	public void leaveTheBuilding() {...}
}

Elvis.getInstance 는 항상 같은 객체에 대한 참조를 반환하므로 해당 클래스가 하나만 생성된다.(이 또한 리플렉션에 뚫린다.)
이 코드의 장점은 바꾸지 않고도 싱글턴이 아니게 변경할 수 있으며 정적 팩터리를 제네릭 싱글 팩토리 패턴으로 바꿀 수 있다는 점이다.
또한 정적 팩토리 메서드 참조를 supplier로 사용할 수 있다는 점이다.(Supplier<Elvis>)

 

해당 방식으로 싱글턴 클래스를 직렬화하려면, Serializable 로는 부족하다.
모든 인스턴스 필드를 일시적(transient)로 선언하고 readResolve 메서드를 제공해야 한다.
만약 이렇게 하지 않으면 역직렬화할 시 새로운 인스턴스가 만들어진다.

 

사실 싱글턴을 만드는 추가적인 꿀 방법이 있는데, 이는 원소가 하나인 enum을 선언하는 것이다.

public enum Elvis {
	INSTANCE
	
	public void leaveTheBuilding() {...}
}

이 방법은 굉장히 깔끔하고 간결하며, 추가 노력 없이도 직렬화가 가능하며, 리플렉션에서도 인스턴스가 한 개 더 생성되는 것을 막아준다.
(단 만들려는 싱글턴이 Enum 이외의 클래스에 대한 상속이 필요할 시 사용불가(인터페이스를 구현하도록 선언할 수는 있다.))

 

만약 위와 같은 장점이 필요하지 않으면 public 필드 방식이 좋다.(사실 자바에서는 내부적으로 생성자를 기본적으로 제공하고 있다.)

 


결론

상속을 받을 필요가 없을 경우 Enum 싱글턴, 그 외 방법은 직렬화와 리플랙션에 대한 방어가 필요하다.