첫번째 방법: private 생성자 + public static final 필드
// 코드 3-1 public static final 필드 방식의 싱글턴 (23쪽)
public class Elvis implements IElvis, Serializable {
/**
* 싱글톤 오브젝트
*/
public static final Elvis INSTANCE = new Elvis();
private static boolean created;
private Elvis() {
if (created) {
throw new UnsupportedOperationException("can't be created by constructor.");
}
created = true;
}
public void leaveTheBuilding() {
System.out.println("Whoa baby, I'm outta here!");
}
public void sing() {
System.out.println("I'll have a blue~ Christmas without you~");
}
// 이 메서드는 보통 클래스 바깥(다른 클래스)에 작성해야 한다!
public static void main(String[] args) {
Elvis elvis = Elvis.INSTANCE;
elvis.leaveTheBuilding();
}
private Object readResolve() {
return INSTANCE;
}
}
장점, 간결하고 싱글턴임을 API에 들어낼 수 있다
단점 1, 싱글톤을 사용하는 클라이언트 테스트하기 어려워진다
단점 2, 리플렉션으로 private 생성자를 호출할 수 있다.
// 해결 방법
private static boolean created;
private Elvis() {
if (created) {
throw new UnsupportedOperationException("can't be created by constructor.");
}
created = true;
}
// 생성자로 여러 인스턴스 만들기
public class ElvisReflection {
public static void main(String[] args) {
try {
Constructor<Elvis> defaultConstructor = Elvis.class.getDeclaredConstructor();
defaultConstructor.setAccessible(true);
Elvis elvis1 = defaultConstructor.newInstance();
Elvis elvis2 = defaultConstructor.newInstance();
Elvis.INSTANCE.sing();
} catch (InvocationTargetException | NoSuchMethodException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
단점 3, 역직렬화 할 때 새로운 인스턴스가 생길 수 있다
// 역직렬화로 여러 인스턴스 만들기
public class ElvisSerialization {
public static void main(String[] args) {
try (ObjectOutput out = new ObjectOutputStream(new FileOutputStream("elvis.obj"))) {
out.writeObject(Elvis.INSTANCE);
} catch (IOException e) {
e.printStackTrace();
}
try (ObjectInput in = new ObjectInputStream(new FileInputStream("elvis.obj"))) {
Elvis elvis3 = (Elvis) in.readObject();
System.out.println(elvis3 == Elvis.INSTANCE);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
===
// 역직렬화를 막는다.
// 원하는 object 를 막는다. (in Entity Class)
private Object readResolve() {
return INSTANCE;
}
두번째 방법: private 생성자 + 정적 팩터리 메서드
장점 1. API를 바꾸지 않고도 싱글턴이 아니게 변경할 수 있다
// 코드 3-2 정적 팩터리 방식의 싱글턴 (24쪽)
public class Elvis implements Singer {
private static final Elvis INSTANCE = new Elvis();
private Elvis() { }
public static Elvis getInstance() { return INSTANCE; } // 해당 부분을 반대로 new Elvis();
public void leaveTheBuilding() {
System.out.println("Whoa baby, I'm outta here!");
}
// 이 메서드는 보통 클래스 바깥(다른 클래스)에 작성해야 한다!
public static void main(String[] args) {
Elvis elvis = Elvis.getInstance();
elvis.leaveTheBuilding();
System.out.println(Elvis.getInstance());
System.out.println(Elvis.getInstance());
}
@Override
public void sing() {
System.out.println("my way~~~");
}
}
장점 2. 정적 팩터리를 제네릭 싱글턴 팩터리로 만들 수 있다
// 코드 3-2 제네릭 싱글톤 팩토리 (24쪽)
public class MetaElvis<T> {
private static final MetaElvis<Object> INSTANCE = new MetaElvis<>();
private MetaElvis() { }
// <E> or <T> 가 선언된 이유는 scope 를 나타낸다. 클래스의 <T> 와는 전혀 다른 것이다.
**@SuppressWarnings("unchecked")
public static <E> MetaElvis<E> getInstance() { return (MetaElvis<E>) INSTANCE; }**
public void say(T t) {
System.out.println(t);
}
public void leaveTheBuilding() {
System.out.println("Whoa baby, I'm outta here!");
}
public static void main(String[] args) {
MetaElvis<String> elvis1 = MetaElvis.getInstance();
MetaElvis<Integer> elvis2 = MetaElvis.getInstance();
System.out.println(elvis1);
System.out.println(elvis2);
elvis1.say("hello");
elvis2.say(100);
}
}
장점 3. 정적 팩터리의 메서드 참조를 공급자(Supplier)로 사용할 수 있다.
public class Concert {
public void start(Supplier<Singer> singerSupplier) {
Singer singer = singerSupplier.get();
singer.sing();
}
public static void main(String[] args) {
Concert concert = new Concert();
concert.start(Elvis::getInstance);
}
}
단점. 위와 동일하다.
세번째 방법: 열거 타입
가장 간결한 방법이며 직렬화와 리플렉션에도 안전하다
대부분의 상황에서는 원소가 하나뿐인 열거 타입이 싱글턴을 만드는 가장 좋은 방법이다
테스트 시에는 인터페이스를 생성하여 진행하자.
**// 열거 타입 방식의 싱글턴 - 바람직한 방법 (25쪽)**
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() {
System.out.println("기다려 자기야, 지금 나갈께!");
}
// 이 메서드는 보통 클래스 바깥(다른 클래스)에 작성해야 한다!
public static void main(String[] args) {
Elvis elvis = Elvis.INSTANCE;
elvis.leaveTheBuilding();
}
}
====
public class EnumElvisReflection {
public static void main(String[] args) {
try {
Constructor<Elvis> declaredConstructor = Elvis.class.getDeclaredConstructor();
System.out.println(declaredConstructor);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}