[아이템 86] Serializable을 구현할지는 신중히 결정하라.

Serializable을 구현하면 릴리스 한 뒤에는 수정하기 어렵습니다. 직렬화 당시 내부 구현 방식에 묶이고, 캡슐화가 깨지는 위험이 있습니다. 직렬화된 클래스는 고유 식별 번호를 부여받는데, serialVersionUID를 명시하지 않으면 런타임에 SHA-1을 적용하여 자동으로 클래스 안에 생성해 넣는데, 클래스의 이름, 멤버 등이 포함됩니다. 그래서 나중에 이들 중 하나를 수정한다면 UID 값도 변하여 호환성이 깨져버립니다.



그 다음 문제는 역직렬화는 숨은 생성자 입니다. 기본 역직렬화를 사용하면 불변식이 깨지고 허가되지 은 접근에 쉽게 노출됩니다.

세 번째 문제는 해당 클래스의 신버전을 릴리즈 할 때마다 직렬화 호환성을 검사해야하기 때문에 테스트해야 할 양이 직렬화 가능 클래스의 수와 릴리스 횟수에 비례하여 증가합니다.
그래서 Serializable 구현 여부는 신중하게 결정해야합니다. 단 객체를 전송 / 저장할 때 자바 직렬화를 이용하는 프레임워크용으로 만든 클래스라면 선택의 여지가 없습니다.

상속용으로 설계된 클래스나 인터페이스는 대부분 `Serializable`을 구현하면 안 됩니다. 그렇지 않으면 이 클래스를 확장할 때마다 커다란 부담감을 안겨주게됩니다. 예외적으로 `Throwable`은 서버가 RMI를 통해 클라이언트로 예외를 보내기 위해 Serializable을 구현했습니다.

작성하는 클래스의 인스턴스 필드가 직렬화와 확장이 가능하다면 주의해야 할 것이 있습니다. finalize 메서드를 재정의하지 못하게 해야합니다. 그렇게 하지 않으면 finalizer 공격을 당할 수 있습니다. 마지막으로 인스턴스 필드 중 기본값으로 초기화하면 안 된다면 다음의 readObjectNoData 메서드를 반드시 추가해야합니다.
1
2
3
4
// 상태가 있고, 확장 가능하고, 직렬화 가능한 클래스용 readObjectNoData 메서드
private void readObjectNoData() throws InvalidObjectException() {
    throw new InvalidObjectException("스트림 데이터가 필요합니다");
}

Serializable를 구현하지 않을 때도 주의할 점이 있습니다. 상속용 클래스에서 직렬화를 지원하지 않고 하위 클래스에서 지원하는 상황에서 역직렬화를 하려면 상위 클래스는 매개변수 없는 생성자를 제공해야 하는데, 제공하지 않는다면 하위클래스에서 직렬화 프록시 패턴을 사용해야 합니다.
내부 클래스는 이 필드들이 클래스 정의에 어떻게 추가되는지도 정의되지 않았기 때문에 직렬화를 구현하면 안 됩니다. 단 정적 멤버 클래스는 Serializable를 구현해도 됩니다.