Serializable을 구현할지는 신중히 결정하라
Serializable 구현의 문제점
Java에서 어떤 클래스의 인스턴스를 직렬화하는 방법은 클래스 선언에 implements Serializable를 명시하는 것이다. 적용이 쉬워 신경쓸 것이 없다는 오해가 생길 수 있지만, 훨씬 복잡하다.
하지만 Serializable을 구현하면 release한 뒤에는 수정하기 어렵다.
Serializable을 구현하면 직렬화된 바이트 스트림 인코딩(직렬화 형태)도 하나의 공개 API가 되어, 이 클래스가 널리 퍼지게된다면 직렬화 형태도 영원히 지원해야 한다. 직렬화 형태를 커스텀으로 설계하지 않고 Java의 기본 방식을 사용한다면 직렬화 형태는 최소 적용 당시 클래스의 내부 구현 방식에 영원히 묶이게 된다.
이는 기본 직렬화 형태에서 클래스의 private과 package-private 인스턴스 필드들이 API로 공개되어 캡슐화가 깨지는 것이며, 필드로의 접근을 최대한 막아 정보를 은닉하라는 조언도 무력화된다.
이 후 뒤늦게 클래스 내부 구현을 수정한다면 원래의 직렬화 형태와 달라지게 된다. 따라서 장기적으로 바라보고 고품질의 직렬화 형태도 주의해서 함께 설계해야한다.
또한 버그와 보안 문제가 생길 위험이 높아진다. 객체는 생성자를 통해만드는 것이 기본인데, 직렬화는 언어의 기본 메커니즘을 우회하는 객체 생성 기법이다.
기본 방식을 사용하건, 재정의를 하건 역직렬화는 일반 생성자의 문제가 그대로 적용되는 '숨은 생성자'로 '생성자에서 구축한 불변식을 모두 보장해야 하며, 생성 도중 공격자가 객체 내부를 들여다볼 수 없도록 해야한다'는 사실을 떠올리기 어렵다. 즉 기본 역직렬화 사용 시, 불변식이 깨지며 허가되지 않은 접근에 쉽게 노출된다.
추가적인 문제점은 해당 클래스의 새로운 버전을 release할 때 테스트할 사항이 늘어나게 된다.
직렬화 가능 클래스가 수정되면 새로운 버전의 인스턴스를 직렬화한 뒤 이전 버전으로 역직렬화할 수 있는지 검사해야한다. 양방향 직렬화/역직렬화가 모두 성공하고, 원래 객체를 충실하게 복제해내는지를 확인해야하는 것이다. 설계 시 처음 커스텀 직렬화 형태를 잘 설계해놨다면 이러한 테스트 부담을 줄일 수 있다.
주의점
Serializable 구현은 쉽게 결정할 사안이 아니다.
단, 객체를 전송하거나 저장할 때 Java 직렬화를 이용하는 프레임워크용으로 만든 클래스라면 선택의 여지가 없다. 이는 Serializable을 반드시 구현해야하는 다른 클래스의 컴포넌트로 쓰일 클래스도 마찬가지다.
구현에 따르는 비용이 적지않기때문에 이득과 비용을 확실히 고려해야한다.
또한 상속용으로 설계된 클래스는 대부분 Serializable을 구현하면 안되며, 인터페이스도 확장해선 안된다. 이를 따르지 않으면 이를 확장한 클래스거나 인터페이스를 구현하는 쪽에서 커다란 부담을 지운다. 단, Serializable을 구현한 클래스만 지원하는 프레임워크를 사용하는 상황은 방도가 없다.
예를 들어, 상속용으로 설계된 클래스 중 Serializable을 구현한 예로 Throwable과 Component가 있다.
Throwable
은 서버가 RMI(Java Remote Method Invocation: Java 원격 함수 호출)를 통해 클라이언트로 예외를 보내기 위해 Serializable을 구현했으며, `Component'는 GUI를 전송, 저장, 복원하기 위해 Serializable을 구현했다.
작성하는 클래스가 직렬화와 확장이 모두 가능하다면
- 인스턴스 필드 값 중 불변식을 보장해야 하는 경우, 하위 클래스에서 finalize 메서드를 재정의하게 못해야 해야한다.
- 이는 finalize 메서드를 자신이 재정의하면서 final로 선언하면 된다.
- 인스턴스 필드 중 기본 값으로 초기화되면 위배되는 불변식이 있는경우
- 클래스에 다음 readObjectNoData 메서드를 반드시 추가해야한다.
private void readObjectNoDate() throws InvalidObjectException {
throw new InvalidObjectException("need stream data");
}
또한 inner class(내부 클래스)는 직렬화를 구현하면 안된다. 내부 클래스에는 바깥 인스턴스의 참조와 유효 범위 안의 지역변수 값들을 저장하기 위해 컴파일러가 생성한 필드들이 자동으로 추가되기 때문이다. 단, 정적 멤버 클래스는 Serializable을 구현해도 된다.
참고자료
- 이펙티브자바 3판