cleanUrl: /posts/consider-using-a-custom-serialized-form-effective-java-item-87
Serializable 을 implement 하기만 하면 직렬화를 맞출 수 있다. 하지만, 기본 직렬화가 커버하지 못하는 것이 있어 직접 private readObject, private writeObject 를 구현해야 할 경우가 있다. 언제 이러한 custom 직렬화를 선택해야 하는지 알아본다.
개발 일정에 쫓기다보면 동작만 하게 하고 릴리즈 이후 리팩토링을 고려하게 된다. 하지만 직렬화를 구현했다면 다음 릴리즈에서 버리려했던 구현에 영원히 묶일 수 있게 된다.
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
이 아님을 보장해야 한다.
<aside>
💡 세 필드 모두 private
임에도 주석이 달려있다. 직렬화시 모두 공개되기 때문에 공개 API에 속하는 것이다. 이러한 private
필드를 javadoc 에 포함하라고 알려주는 역할은 @serial
태그가 수행한다.
</aside>
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
의 모든 정보를 철두철미하게 기록한다.