요약
•
기본 직렬화 형태와 커스텀 직렬화 형태를 상황에 맞게 잘 고려해서 사용하자.
직렬화 형태 선택
•
고민해보고 괜찮다고 판단될 때만 기본 직렬화 형태를 사용하자.
◦
유연성, 성능, 정확성 측면에서 신중히 고민 후 합당할 때만 사용해야 한다.
◦
직접 설계하더라도 기본 직렬화 형태와 거의 같은 결과가 나올땜ㄴ 기본 형태를 쓰자.
◦
기본 직렬화 형태가 적합하다고 판단했더라도, 불변성과 보안을 위해 readObject 메서드를 제공해야 할 때가 많다.
•
객체의 물리적 표현과 논리적 내용이 같다면, 기본 직렬화 형태를 사용해도 된다.
public final class StringList implements Serializable {
private int size = 0;
private Entry head = null;
private static class Entry implements Serializable {
String data;
Entry next;
Entry previous;
}
...
}
Java
복사
•
이와 같이 물리적 표현과 논리적 내용이 차이가 클 때 기본 직렬화를 사용하면 4가지 측면에서 문제가 생긴다.
◦
현재의 내부 표현 방식이 직렬화로 인한 공개 API에 의해 영구히 묶이게 된다.
▪
다음 릴리즈에서 내부 표현 방식을 바꾸더라도, 연결 리스트 표현된 입력에 대해서 여전히 처리할 수 있어야 한다.
▪
그로 인해 연결 리스트는 더 이상 사용하지 않아도 관련 코드를 제거할 수 없다.
◦
불필요한 요소까지 포함하여, 너무 많은 공간을 차지할 수 있다.
▪
기본 형태를 사용하면 연결 리스트의 모든 엔트리와 관련된 모든 정보를 기록하게 되는데, 연결 정보는 내부 구현에 해당하기 때문에 직렬화 형태에 포함할 필요가 없다.
◦
시간이 너무 많이 걸릴 수 있다.
▪
객체 그래프의 위상에 관한 정보가 없으니 그래프를 직접 순회해야한다.
◦
스택 오버플로우를 발생시킬 수 있다.
▪
기본 직렬화 과정은 객체 그래프를 재귀 순회하는데, 이로인해 스택 오버플로우가 발생할 수 있다.
•
위 예시 코드에서 직렬화를 올바르게 사용하려면, 아래와 같이 커스텀 직렬화 형태를 사용하면 된다.
public final class StringList implements Serializable {
private transient int size = 0;
private transient Entry head = null;
private static class Entry {
String data;
Entry next;
Entry previous;
}
private void writeObject(ObjectOutputStream s) throws IOException {
s.defaultWriteObject();
s.writeInt(size);
// 올바른 순서로 원소들을 직렬화하기
for (Entry e = head; e != null; e = e.next)
s.writeObject(e.data);
}
private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
s.defaultReadObject();
int numElements = s.readInt();
for (int i = 0; i < numElements; i++)
add((String) s.readObject());
}
...
}
Java
복사
•
transient 키워드를 사용하자
◦
만약 필드가 모두 transient 이어도, writeObject와 readObject에서 defaultWriteObject와 defaultReadObject는 꼭 호출하자. 그래야 차후 릴리즈에서 transient가 아닌 필드가 생겼을 때 호환이 가능하다.
◦
transient는 해당 객체의 논리적 상태와 무관한 필드라고 확신할 때만 생략하고, 그 외에는 대부분의 인스턴스 필드를 transient를 선언해야 한다.
◦
기본 직렬화를 사용하면 transient 필드들은 기본값으로 초기화 된다.
•
어떤 직렬화 형태를 사용하든, 모든 메서드에 synchronized로 선언하여 스레드 안전한 객체에서는 아래처럼 writeObject도 동기화 해야한다.
private synchronized void writeObject(ObjectOutputStream s)
throws IOException {
s.defaultWriteObject();
}
Java
복사
•
어떤 직렬화 형태를 사용하든, 직렬화 가능 클래스 모두 SerialVersionUID를 명시적으로 부여하자.
◦
SUID가 발생시킬 수 있는 잠재적 호환성 문제가 전부 사라진다.
◦
또한 런타임에 해당 값을 생성하는 복잡한 연산이 생략되어 성능도 개선된다.
◦
구버전으로 직렬화된 인스턴스들과의 호환성을 끊으려는 경우를 제외하고는 SUID를 절대 수정하지 말자.