이펙티브 자바 - item 3. private 생성자나 열거 타입으로 싱글톤임을 보증하라

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

이펙티브 자바 - item 3. private 생성자나 열거 타입으로 싱글톤임을 보증하라

public static 멤버가 final 필드인 방식

  • 생성자는 private으로 감춰준다.
  • 접근할 수 있는 수단으로 public static 멤버를 사용한다
public class Elvis {
	public static final Elvis INSTANCE = new Elvis();
	private Elvis() { ... }

	public void leaveTheBuilding()
}

private 생성자는 public static final필드인 Elvis.INSTANCE를 초기화할 때 딱 한 번만 호출된다. public이나 protected 생성자가 없으므로 Elvis클래스가 초기화될 때 만들어진 인스턴스가 전체 시스템에서 하나뿐임이 보장된다.

@Test
public void singletonTest(){
    Elvis elvis1 = Elvis.INSTANCE;
	Elvis elvis2 = Elvis.INSTANCE;
	assertSame(elvis1, elvis2); // success
}

예외

예외로 권한이 있는 클라이언트는 리플랙션 API를 사용해 private 생성자를 호출 할 수 있다

Constructor<Elvis> constructor = (Constructor<Elvis>) elvis2.getClass().getDeclaredConstructor();
constructor.setAccessible(true);

Elvis elvis3 = constructor.newInstance();
assertSame(elvis2, elvis3); // success

생성자를 수정하여 두번 째 객체가 생성되려 할 때 예외를 던져 방어

private Elvis() {
	if (Objects.nonNull(INSTANCE)) {
		throws new RuntimeException("이미 생성된 객체가 존재합니다.");
	}
}

장점

  • 해당 클래스가 싱글톤임이 API에 명백히 드러남
  • public static 필드가 final이니 절대로 다른 객체를 참조할 수 없음
  • 간결함

정적 팩터리 메서드를 public static 멤버로 제공하는 방식

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

	public void leaveTheBuilding()
}

Elvis.getInstance()는 항상 같은 객체의 참조를 반환하므로 제2의 Elvis인스턴스는 만들어지지 않는다.(리플렉션을 통한 예외는 똑같이 적용)

@Test
public void getInstance() {
	Elvis elvis1 = Elvis.getInstance();
	Elvis elvis2 = Elvis.getInstance();

	assertSame(elvis1, elvis2); // success
}

원소가 하나인 열거 타입을 선언하는 방식

public enum Elvis {
	INSTANCE;

	public String getName() {
		return "Elvis";
	}

	public void leaveTheBuilding() { ... }
}

복잡한 직렬화 상황이나 리플렉션 공격에서도 제2의 인스턴스가 생기는 일을 완벽히 막아준다.

대부분의 상황에서는 최선의 방법이지만, 만들려는 싱글톤이 Enum 이외의 다른 상위 클래스를 상속해야 한다면  방법은 사용할  없다.