싱글턴이란 오직 하나의 인스턴스를 보장하는 방식을 이야기한다.
싱글턴 클래스의 예이다.
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis() {
}
// ....
}
위 코드는 Elvis INSTANCE를 초기화할때 한번만 인스턴스를 초기화한다.
다만 private 생성자를 reflection을 활용해 호출할 수 있기는 하다.
public class Elvis {
private static final Elvis INSTANCE = new Elvis();
public static Elvis getInstance() { return INSTANCE;}
private Elvis() {
}
// ....
}
정적 팩토리 메서드를 활용하는 방법도 있다. 이 방법의 장점은 유연하게 API를 바꾸지 않고 인스턴스를 싱글턴이 아닌 다른 방식으로 변경이 가능하다는 점, Supplier를 활용하여 Elvis::getInstance 와 같은 형태로 사용할 수 있다.
https://madplay.github.io/post/what-is-readresolve-method-and-writereplace-method
https://madplay.github.io/post/what-is-readobject-method-and-writeobject-method
이 두 주소에 싱글턴 직렬화와 역직렬화에 대해 잘 정리되어 있다.
싱글턴의 경우 단순 serializable만 해주어서는 역직렬화시 동일 객체가 생성되지 않는다. 한 번 살펴보자
public class Elvis implements Serializable {
private static final Elvis INSTANCE = new Elvis();
private final String name = "Elvis";
public static Elvis getInstance() {
return INSTANCE;
}
private Elvis() {
}
public String getName() {
return name;
}
}
class SerializationTester {
public static byte[] serialize(Object instance) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try (bos; ObjectOutputStream oos = new ObjectOutputStream(bos)) {
oos.writeObject(instance);
} catch (Exception e) {
// ... 구현 생략
}
return bos.toByteArray();
}
public static Object deserialize(byte[] serializedData) {
ByteArrayInputStream bis = new ByteArrayInputStream(serializedData);
try (bis; ObjectInputStream ois = new ObjectInputStream(bis)) {
return ois.readObject();
} catch (Exception e) {
// ... 구현 생략
}
return null;
}
}
class ElvisTest {
@Test
@DisplayName("직렬, 역직렬화시 객체가 일치 테스트")
void singletonSerializeAndDeSerializeInstanceTest() {
byte[] serialize = SerializationTester.serialize(Elvis.getInstance());
Elvis deserialize = (Elvis) SerializationTester.deserialize(serialize);
assertThat(deserialize).isEqualTo(Elvis.getInstance());
}
@Test
@DisplayName("직렬, 역직렬화시 값 일치 테스트")
void singletonValueEqualTest() {
byte[] serialize = SerializationTester.serialize(Elvis.getInstance());
Elvis deserialize = (Elvis) SerializationTester.deserialize(serialize);
assertThat(deserialize.getName()).isEqualTo(Elvis.getInstance().getName());
}
}
public class Elvis implements Serializable {
private static final Elvis INSTANCE = new Elvis();
private final String name = "Elvis";
public static Elvis getInstance() {
return INSTANCE;
}
private Elvis() {
}
public String getName() {
return name;
}
// 역직렬화 싱글턴 보장하기 위해 추가
private Object readResolve() {
return INSTANCE;
}
}
이론상 가장 완벽한 싱글턴이다. 위의 싱글턴과 다르게 직렬화 역직렬화 과정에서도 새로운 인스턴스를 생성하는 과정을 막아준다. 그러나 enum 외에 다른 클래스를 상속해야 하는 경우 사용할 수 없다.