<aside>
📚 클래스를 직렬화하기로 결정했다면 어떤 직렬화 형태를 사용할 지 신중하게 고려해야 한다.
자바 기본 직렬화 형태는 ****직렬화한 결과가 객체의 논리적 표현과 부합하는 경우에만 사용하고, 그 외에는 커스텀 직렬화 형태를 사용하자.
한번 직렬화 형태에 포함한 필드는 마음대로 제거할 수 없다. 즉 잘못된 직렬화 형태는 해당 클래스에 영구히 부정적인 영향을 남기므로,
커스텀 직렬화 형태를 설계할 때에는 많은 시간을 들여 신중히 설계하도록 하자.
</aside>
1. 기본 직렬화 형태를 사용하는 경우
-
객체의 기본 직렬화 형태는 그 객체가 포함한 데이터들과 그 객체에서 접근할 수 있는 모든 객체 등 다양한 데이터를 인코딩한다.
-
따라서 기본 직렬화 형태는 객체의 물리적 표현과 논리적 내용이 같은 경우에만 사용하도록 하자.

- 기본 직렬화 형태를 사용해도 무방한 케이스
- 논리적으로 성명은 성, 이름, 중간이름 3개의 문자열로 구성되며, 위 코드의 인스턴스 필드들은 이 논리적 구성요소를 정확히 반영했다.
- 세 필드 모두 private 임에도 문서화 주석이 달려 있다 : 클래스의 직렬화 형태에 포함되는 공개 API는 모두 문서화 해야 하기 때문
- @serial : private 필드의 설명을 API 문서에 포함하라고 알려주는 역할 수행
- @serial 태그로 기술한 내용은 API 문서에서 직렬화 형태를 설명하는 특별한 페이지에 기록된다.
-
또한 불변식 보장과 보안을 위해 readObject 메서드가 어떤 기능을 제공해야 하는지 확인하자.
-
위 Name 클래스의 경우 readObject 메서드가 lastName
과 firstName
필드가 null이 아님을 보장해 주어야 한다.
2. 기본 직렬화 형태를 사용하면 안되는 경우
- 객체의 물리적 표현과 논리적 표현의 갭이 클 때 기본 직렬화 형태를 사용하면 크게 네가지 면에서 문제가 발생한다.
- 공개 API가 현재의 내부 표현 방식에 영구히 종속되어 버린다.
- 너무 많은 메모리 공간을 차지할 수 있다.
- 시간이 너무 많이 소요될 수 있다.
- 스택 오버플로우를 일으킬 수 있다.
- 예시와 함께 살펴보자. 아래 코드는 기본 직렬화 형태에 적합하지 않은 예시이다.
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;
}
...
}
- 이 클래스는 논리적으로 일련의 문자열을 표현하고, 물리적으로 문자열들을 이중 연결 리스트로 저장한다.
- 객체의 물리적 표현과 논리적 내용의 차이가 크다
- 여기서 기본 직렬화 형태를 사용하면 위에서 살펴본 네가지 문제가 발생한다.
- 공개 API가 현재의 내부 표현 방식에 영구히 종속되어 버린다.
- private 클래스인
StringList.Entry
가 공개 API 가 되어 버리고, 다음 릴리즈에서 내부 표현 방식을 바꾸더라도
StringList
클래스는 여전히 연결 리스트로 표현된 입력도 처리할 수 있어야 한다.
- 즉 연결 리스트를 더이상 사용하지 않더라도 과거의 코드를 제거할 수 없게 된다.
- 너무 많은 메모리 공간을 차지할 수 있다.
- 위 코드에 직렬화를 적용하면 연결 리스트의 모든 엔트리와 연결 정보까지 기록하게 된다.
- 하지만 엔트리와 연결 정보는 내부 구현에 해당하므로 직렬화 형태에 포함시킬 필요가 없다.
- 그러나 이런 불필요한 부분 때문에 직렬화 형태가 너무 커져 디스크에 저장하거나 네트워크로 전송하는 속도가 느려진다.
- 시간이 너무 많이 소요될 수 있다.
- 문자열들의 길이가 평균 10 이라면, 이후 살펴볼 개선 버전의 StringList 직렬화 형태는 원래 버전의 절반 정도의 공간만 차지하며
속도 역시 두배 정도 빠르다.
- 스택 오버플로우를 일으킬 수 있다.
- 기본 직렬화 과정은 객체 그래프를 재귀 순회하는데 이 작업은 중간 정도 크기의 객체 그래프에서도 스택 오버플로우를 일으킬 수 있다.
StringList
에 원소를 1000~1800 개 정도 담으면 직렬화 과정에서 StackOverflowError 가 발생한다.
- 다만 이 경우는 유연성과 성능이 떨어졌더라도 객체를 직렬화한 후 역직렬화하면 원래 객체를 그 불변식까지 포함해 제대로 복원해낸다.
- 불변식이 세부 구현에 따라 달라지는 객체에서는 이 마저 깨질 수 있다.
- 예) 해시테이블 Hash Table
key - valueList
의 형태로 객체를 저장하는 자료구조
- 특정 객체가 어느
key
의 valueList
에 들어갈지는 보통 hash function
에 의해 결정된다.
- 그러나 이
hash function
의 연산 방식은 구현에 따라 달라질 수 있으며 계산할 때마다 달라지기도 한다.
- 여기에 기본 직렬화를 사용하면 심각한 버그로 이어질 수 있다 : 직렬화 할 때와 역직렬화 할 때의
hash function
동작 방식이 다르면 객체가 훼손됨
3. 기본 직렬화 형태 대신 커스텀 직렬화 형태를 사용하는 경우
- 위에서 살펴본
StringList
의 경우, 리스트에 포함된 문자열의 개수와 그 뒤로 문자열들을 나열하는 수준의 직렬화면 충분하다.