Search

Item 89. 인스턴스 수를 통제해야 한다면 readResolve보다는 열거 타입을 사용하라

생성일
2023/08/24 12:13
챕터
12장 - 직렬화

요약

싱글톤 객체를 만들고 싶다면 readResolve 메서드를

직렬화와 싱글톤

public class Elvis { public static final Elvis INSTANCE = new Elvis(); private Elvis() { ... } ... }
Java
복사
위 코드는 클래스 바깥에서 생성자를 호출하지 못하도록 막는 방식으로 싱글턴 패턴을 구현한 것이다.
하지만 위 코드에 Serializable을 적용하면 더 이상 싱글턴 패턴이 아니게 된다.
기본 직렬화 형태를 사용하든 어떤 readObject를 사용하든, 역직렬화를 통해 생성된 인스턴스는 이 클래스가 초기화될 때 만들어진 인스턴스와는 별개의 인스턴스가 반환된다.
readResolve 기능으로 readObject가 만들어낸 인스턴스를 다른 것으로 대체하고, 새로 생성된 인스턴스는 가비지 컬렉터가 수거해가도록 할 수 있다.
private Object readResolve() { return INSTANCE; }
Java
복사
역직렬화 후 새로 생성된 객체를 인수로 readResolve 메서드가 호출되고, 이 메서드에서 반환된 객체 참조가 새로 생성된 객체를 대신해 반환된다.
이를 통해 Serializable을 구현하면서 싱글톤 패턴을 유지할 수 있다.
readResolve 메서드는 역직렬화한 객체를 무시하고 지정된 인스턴스를 반환하므로, 해당 클래스의 직렬화 형태는 아무런 데이터를 가질 이유가 없으니 모든 인스턴스 필드를 trasient로 선언해야 한다.
transient가 아닌 인스턴스 필드를 가지고 있으면 간단하게 역직렬화된 인스턴스를 훔쳐낼 수 있다.
final 클래스에서는 readResolve 메서드는 private로 선언되어있어야 한다.
이렇듯 readResolve 메서드를 사용해 만들어진 역직렬화된 인스턴스에 접근하지 못하게 하는 방법은 깨지기 쉽고 신경을 많이 써야 한다.
직렬화 가능한 인스턴스 클래스를 열거 타입으로 구현하면 선언한 상수 외의 다른 객체는 존재하지 않는게 보장되기 때문에, readResolve 메서드보다 열거 타입을 사용하자.
위 예시에서는 Elvis를 원소 하나짜리 열거 타입으로 바꾸는 것이 더 나은 선택이다.