[Effective Java] 2 - item3 private 생성자나 열거 타입으로 싱글턴임을 보장하라.
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