티스토리 뷰

이펙티브 자바 Effective Java 2/E
국내도서
저자 : 조슈아 블로크(Joshua Bloch) / 이병준역
출판 : 인사이트 2014.09.01
상세보기



JDK 1.5 이전에는 싱글턴을 구현하는 방법이 두 가지 였다.

1. 정적 멤버는 final로 선언

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

  public void leaveTheBuilding() { ... }
}
  • private 생성자는 pulic static final 필드인 Elivs.INSTANCE를 초기화 할 때 한 번만 호출된다.

2. public으로 선언된 정적 팩터리

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

  public void leaveTheBuilding() { ... }
}
  • API를 변경하지 않고도 싱글턴 패턴을 포기할 수 있다.
  • 제네릭 타입을 수용하기 쉽다.

위 방법들의 단점

  • AccessibleObject.setAccessible 메서드의 도움을 받아 권한을 획득한 클라이언트는 리플렉션(reflection) 기능을 통해 private 생성자를 호출할 수 있다.
import java.lang.reflect.Constructor;

public class PrivateInvoker {
  public static void main(String[] args) throws Exception {
    // 리플렉션과 setAccessible 메서드를 통해 private로 
    // 생성자의 호출 권한을 획득한다.
    Constructor<?> con = Private.class.getDeclaredConstructors()[0];
    con.setAccessible(true);
    Private p = (Private) con.newInstance();
  }
}

class Private {
  private Private() { ... }
}
  • 직렬화 기능(Serializable) 클래스로 만들려면 클래스 선언에 implements Serializable을 추가하는 것으로 부족.
  • 모든 필드를 transient로 선언하고 readResolve 메서드를 추가. (참고)
    (그렇지 않으면 serialize된 객체가 역직렬화(deserialize)될 때마다 새로운 객체가 생기게 된다.)
// 싱글턴 상태를 유지하기 위한 readResolve 구현
private Object readResolve() {
  // 동일한 Elvis 객체가 반환되도록 하는 동시에, 가짜 Elivs 객체는 GC가 처리하도록 만든다.
  return INSTANCE;
}

3. 원소가 하나뿐인 enum 자료형을 이용.

public enum Evlis {
  INSTANCE;

  public void leaveTheBuilding() { ... }
}
  • 기능적으로는 public 필드를 사용하는 구현법과 동등.
  • 좀 더 간결하고 직렬화가 자동으로 처리된다.
  • 리플렉션(reflection)을 통한 공격에도 안전하다.

요약

원소가 하나뿐인 enum 자료형이야말로 싱글턴을 구현하는 가장 좋은 방법이다.

댓글