싱글턴(singleton) 클래스는 인스턴스를 오직 하나만 생성할 수 있는 클래스이다.
싱글턴 클래스는 private 생성자, 혹은 열거타입으로 보증할 수 있다.
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
// final 필드인 Elvis.INSTANCE를 초기화할 때 단 한번 호출됨
private Elvis() {...}
public void leaveTheBuilding() {...}
}
위 코드의 Elvis
클래스는 public
, protected
생성자가 없으므로 싱글턴임이 보장된다.
단, 클라이언트는 AccessibleObject.setAccessble
을 사용해 private
생성자를 호출할 수 있다. 이는 아래와 같이 방어할 수 있다.
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
// 두 번째 객체가 생성되려 할 때 예외를 던짐
private Elvis() {
if(INSTANCE != null) {
throw new Exception("생성자를 호출할 수 없습니다.");
}
...
}
public void leaveTheBuilding() {...}
}
장점1. public static 필드가 final 이므로 절대 다른 객체를 참조할 수 없다.
장점2. 간결하다.
public class Elvis {
private static final Elvis INSTANCE = new Elvis();
private Elvis() {...}
// 항상 같은 객체의 참조를 반환
public static Elvis getInstance() { return INSTANCE; }
public void leaveTheBuilding() {...}
}
public static final 필드 | 정적 팩터리 |
---|---|
public static 필드가 final 이므로 절대 다른 객체를 참조할 수 없다. 간결하다 | API 수정 없이 싱글턴이 아니게 변경할 수 있다. 정적 팩터리를 제네릭 싱글턴 팩터리로 만들 수 있다. 팩터리의 메서드 참조를 공급자(supplier)로 사용할 수 있다. ex) Supplier<Elvis> |
정적 팩터리의 장점이 굳이 필요하지 않다면 public
필드 방식이 좋다.
싱글턴 클래스를 직렬화하려면 모든 인스턴스 필드를 일시적(transient)이라고 선언 readResolve 메서드를 제공해야 한다. (그렇지 않으면 역직렬화 시 새로운 인스턴스 생성됨)
// 싱글턴임을 보장해주는 readResolve 메서드
private Object readResolve() {
// '진짜' Elvis를 반환하고, 가짜 Elvis는 가비지 컬렉터에 맡김
return INSTANCE;
}
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() {...}
}
장점1 public 필드 방식과 비슷하나 더 간결함
장점2 직렬화 용이
장점3 복잡한 상황에서도 제 2의 인스턴스가 생기는 일을 완벽히 방어
원소가 하나뿐인 열거 타입이 싱글턴을 만드는 가장 좋은 방법이다.
(단, Enum
외 클래스 상속 시 사용 불가능하다.)
조슈아 블로크Joshua Bloch, 『이펙티브 자바 Effective Java 3/E』, 개앞맵시(이복연) 옮김, 인사이트(2018), p23-25.