[JAVA] Effective Java - 3. private 생성자나 열거 타입으로 싱글턴 보증
싱글턴이란?
인스턴스를 하나만 생성할 수 있는 클래스를 말한다.
상태를 가지지않는 객체나 설계상 유일한 시스템 컴포넌트에 적합하다.
싱글턴을 만드는 방법은 3가지 이다
1. 유일한 인스턴스에 접근 가능한 public static final
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis() {
}
public void print() {
System.out.println("Elvis");
}
}
public static final 필드인 Elvis.INSTANCE를 초기화 할 때 생성자가 한번만 호출된다.
public 이나 protected 생성자가 없으므로 Elvis의 클래스가 초기화할 때 만들어진 인스턴스가 하나임을 보장한다.(Item 4)
장점 1. 싱글턴인게 명백하게 보임
장점 2. 간결함
단 리플렉션인 AccessibleObject.setAccessible을 사용해 생성자를 호출 가능하다.
Class cl = Class.forName("Elvis");
AccessibleObject declaredConstructor = cl.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Constructor conn = (Constructor) declaredConstructor;
Elvis o = (Elvis) conn.newInstance();
o.print();
이러한 공격을 방어하려면 생성자를 수정하여 두번째 객체가 생성되려하면 예외를 던지게 하면 된다.
2. 정적 팩터리 메서드를 public static 멤버로 제공한다.
public class Elvis {
private static final Elvis INSTANCE = new Elvis();
private Elvis() {}
public static Elvis getInstance() {
return INSTANCE;
}
public void print() {
System.out.println("Elvis");
}
}
Elvis.getInstance는 항상 같은 객체의 참조를 반환하므로 제 2의 인스턴스가 만들어지지 않는다.
(위의 예외와 똑같이 적용되기는함)
장점 1. 수정 하지 않아도 싱글턴이 아니게 확장할 수 있다.
장점 2. 제네릭 싱글턴 팩터리로 만들 수 있다.
* 제네릭 싱글턴 팩터리란
class Temp {
private static final List colorList = new ArrayList();
public static <T> List<T> getColorList() {
return (List<T>) colorList;
}
}
List<String> colorList = Temp.<String>getColorList();
List<Integer> colorList1 = Temp.<Integer>getColorList();
colorList.add("RED");
colorList1.add(111111);
System.out.println("colorList = " + colorList.toString()); // colorList = [RED, 111111]
제네릭 타입을 받을 수 있는 객체를 싱글턴으로 만들고,
해당 객체의 정적 팩터리 메서드 호출 시 받은 타입으로 반환 해준다.
장점3. 정적 팩터리의 메서드 참조를 공급자로 사용할 수 있다.
정적팩터리 메소드를 Supplier<T> 공급자로 사용할 수 있다.
class Service {
public static <T> void execute(Supplier<T> supplier) {
T t = supplier.get();
// ..
}
}
Service.execute(Elvis::getInstance);
3. 원소가 하나인 열거타입을 선언
public enum Elvis {
INSTANCE;
public void print() {
//...
}
}
public 필드 방식과 유사하지만, 더 간결하고, 직렬화에 작업이 필요없고, 리플렉션 공격에도 완전히 막아준다.
책에선 가장 좋은방법이라고 소개 되어있다.
클래스 상속을 못하는 단점이 있다.
* 싱글턴 객체 직렬화
직렬화란 객체를 다른 시스템에서 사용하기 위해 객체를 바이트화 하는것을 말한다.
이때, 클래스는 Serializable 인터페이스를 구현한다.
싱클턴 클래스에 단순히 Serializable만 선언시 직렬화 후 다시 역직렬화 하면 새로운 인스턴스가 만들어진다.
추가로
public Object readResolve() {
return INSTANCE;
}
readResolve 메서드를 추가해주면 역직렬화 시에도 싱글턴임이 보장된다.