cleanUrl: /posts/consider-using-a-custom-serialized-form-effective-java-item-87

Serializable 을 implement 하기만 하면 직렬화를 맞출 수 있다. 하지만, 기본 직렬화가 커버하지 못하는 것이 있어 직접 private readObject, private writeObject 를 구현해야 할 경우가 있다. 언제 이러한 custom 직렬화를 선택해야 하는지 알아본다.

Consider using a custom serialized form

개발 일정에 쫓기다보면 동작만 하게 하고 릴리즈 이후 리팩토링을 고려하게 된다. 하지만 직렬화를 구현했다면 다음 릴리즈에서 버리려했던 구현에 영원히 묶일 수 있게 된다.

BigInteger 가 이 문제를 겪고 있다.

직렬화가 적합한 경우

public class Name implements Serializable {
    
    public Name(String lastName, String firstName, String middleName) {
        this.lastName = lastName;
        this.firstName = firstName;
        this.middleName = middleName;
    }
    
    /**
     * 성, null이 아니어야 한다
     * @serial 
     */
    private final String lastName;

    /**
     * 이름, null이 아니어야 한다.
     * @serial
     */
    private final String firstName;

    /**
     * 중간이름, 중간이륾이 없다면 null
     * @serial
     */
    private final String middleName;
}

이름인 성, 이름, 중간이름(미국) 3개 문자열로 구성되며, 위 클래스의 필드들은 이 논리적 구성요소를 정확히 반영하였다.

기본 직렬화 형태가 적합하더라도 불변식 보장과 보안을 위해 readObject 를 제공해야 할 때도 있다.

여기서는 lastName, firstName 필드가 null 이 아님을 보장해야 한다.

세 필드 모두 private 임에도 주석이 달려있다. 직렬화시 모두 공개되기 때문에 공개 API에 속하는 것이다. 이러한 private 필드를 javadoc 에 포함하라고 알려주는 역할은 @serial 태그가 수행한다.

직렬화가 적합하지 않은 경우

public class StringList implements Serializable {
    
    private final int size = 0;
    private Entry head = null
    
    private static class Entry implements Serializable {
        String data;
        Entry next;
        Entry previous;
    }
}

논리적으로 이 클래스는 일련의 문자열을 표현하는데 물리적으로는 double linked list 로 연결했다.

이 클래스에 default serialization 을 적용한다면 각 노드의 양방향 연결 정보를 포함해 Entry 의 모든 정보를 철두철미하게 기록한다.