이펙티브 자바 - item 3. private 생성자나 열거 타입으로 싱글톤임을 보증하라
on JAVA
이펙티브 자바 - 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 이외의 다른 상위 클래스를 상속해야 한다면 이 방법은 사용할 수 없다.