1 minute read

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

싱글턴 클래스

  • 인스턴스를 오직 하나만 생성할 수 있는 클래스
  • 예 : 함수와 같은 무상태 객체, 설계상 유일한 시스템 컴포넌트, DBCP(DataBase Connection Pool), 로그기록 객체 등…
  • 객체 생성이 1회만 이루어져 메모리 낭비를 방지한다.
  • 다른 객체와 공유가 용이하다.
  • 클라이언트에서 테스트가 어렵다. (Mock 객체 생성 불가능)

public static final 필드 방식

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

	private Elvis(){
		...
	}

}
  • private 생성자는 public static final 필드 초기화시 최초 1회만 호출된다.
  • 예외적으로, 권한이 있는 클라이언트는 리플렉션 API를 사용해 private 생성자를 호출한다.
    • 이를 통한 공격을 방지하려면, 생성자에서 2번째 객체가 생성되려고 할때 예외를 던지게 한다.

정적 팩터리 방식

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

	private Elvis(){
		...
	}
	public static Elvis getInstance(){
		return INSTANCE;
	}

}
  • 해당 클래스가 싱글턴임이 API에 명백히 드러나고, 간결하다.
  • API 변경 없이 싱글턴이 아니게 수정이 가능하다.
    • 호출하는 thread별로 다른 인스턴스를 넘겨주도록..
  • 정적 팩토리를 제네릭 싱글턴 팩터리로 만들 수 있다. [item 30]
  • 메서드 참조를 공급자로 사용할 수 있다. item 43 , item44
    • Printer::getInstance -> Supplier<Printer>

역직렬화시 유의점

앞선 방식으로 만들어진 싱글턴 클래스를 직렬화하려면, 단순히 Serializable을 구현한다고 선언하는 것만으로는 부족하다.

  • 모든 인스턴스 필드에 transient를 선언하고, readResolve 메서드를 제공해야만 역직렬화시에 새로운 인스턴스가 만들어지는 것을 방지할 수 있다.
  • 이렇게 하지 않으면 초기화해둔 인스턴스가 아닌 다른 인스턴스가 반환된다.
// 싱글턴임을 보장해주는 readResolve 메서드
private Object readResolve() {
	// 진짜 싱글턴 객체를 반환하고, 가짜 싱글턴 객체는 가비지 컬렉터에 맡김
	return INSTANCE;
}

열거 타입 방식의 싱글턴

  • public static final 필드 방식과 비슷하지만 더 간결하다.
  • 추가 노력 없이 직렬화가 가능하다.
  • 대부분 상황에서 싱글턴 객체를 만드는 좋은 방법
    • 단, 만드려는 싱글턴이 Enum외의 클래스를 상속해야 한다면 사용 불가능하다.
public enum Elvis {
	INSTANCE;
}

Leave a comment