나는이 질문이 실제로 오래되었고 받아 들여진 대답을 알고 있지만 Google 검색에서 매우 높게 나타나기 때문에 중요하다고 생각되는 세 가지 경우에 대한 답변이 없기 때문에 무게가 달릴 것이라고 생각했습니다. 행동 양식. 물론 모든 사용자 지정 직렬화 형식이 실제로 필요하다고 가정합니다.
컬렉션 클래스를 예로 들어 보겠습니다. 링크 된 목록 또는 BST의 기본 직렬화는 요소를 순서대로 직렬화하는 것과 비교하여 성능이 거의 떨어지지 않으면 서 공간이 크게 손실됩니다. 컬렉션이 프로젝션 또는 뷰인 경우 공개 API에서 제공하는 것보다 더 큰 구조에 대한 참조를 유지합니다.
직렬화 된 객체에 사용자 정의 직렬화가 필요한 불변 필드 writeObject/readObject
가있는 경우, 직렬화 된 객체가 작성된 스트림 부분을 읽기 전에 생성되므로 원래 솔루션 은 충분하지 않습니다 writeObject
. 다음과 같이 링크 된 목록을 최소한으로 구현하십시오.
public class List<E> extends Serializable {
public final E head;
public final List<E> tail;
public List(E head, List<E> tail) {
if (head==null)
throw new IllegalArgumentException("null as a list element");
this.head = head;
this.tail = tail;
}
//methods follow...
}
이 구조는 head
모든 링크 의 필드를 재귀 적으로 쓰고 그 뒤에 null
값 을 붙여서 직렬화 할 수 있습니다 . 그러나 이러한 형식을 역 직렬화하는 것은 불가능합니다. readObject
멤버 필드의 값을 변경할 수 없습니다 (이제 고정됨 null
). 여기에 writeReplace
/ readResolve
쌍이 있습니다 :
private Object writeReplace() {
return new Serializable() {
private transient List<E> contents = List.this;
private void writeObject(ObjectOutputStream oos) {
List<E> list = contents;
while (list!=null) {
oos.writeObject(list.head);
list = list.tail;
}
oos.writeObject(null);
}
private void readObject(ObjectInputStream ois) {
List<E> tail = null;
E head = ois.readObject();
if (head!=null) {
readObject(ois); //read the tail and assign it to this.contents
this.contents = new List<>(head, this.contents)
}
}
private Object readResolve() {
return this.contents;
}
}
}
위의 예제가 컴파일되지 않거나 작동하지 않으면 죄송하지만 내 요점을 설명하기에 충분합니다. 이것이 아주 많이 가져온 예라고 생각한다면 많은 기능적 언어가 JVM에서 실행되며 이러한 접근 방식은 필수적입니다.
실제로 우리에게 쓴 것과 다른 클래스의 객체를 역 직렬화하려고 할 수 있습니다 ObjectOutputStream
. 이것은 java.util.List
슬라이스를 더 길게 노출시키는 목록 구현 과 같은 뷰의 경우입니다 ArrayList
. 분명히, 전체 백업 목록을 직렬화하는 것은 좋지 않은 생각이며, 본 슬라이스에서 요소 만 작성해야합니다. 그러나 왜 멈추고 역 직렬화 후에 쓸모없는 수준의 간접 성이 있습니까? 우리는 단순히 스트림에서 요소를 읽고 ArrayList
뷰 클래스에 래핑하는 대신 직접 반환 할 수 있습니다.
대안 적으로, 직렬화 전용의 유사한 델리게이트 클래스를 갖는 것이 설계 선택 일 수있다. 좋은 예는 직렬화 코드를 재사용하는 것입니다. 예를 들어, StringBuilder for String과 유사한 빌더 클래스가있는 경우 빈 빌더를 스트림에 작성하고 컬렉션의 반복자에서 리턴 한 콜렉션 크기 및 요소를 사용하여 콜렉션을 직렬화하는 직렬화 대리자를 작성할 수 있습니다. 역 직렬화는 빌더를 읽고, 이후에 읽은 모든 요소를 추가하고 build()
, 델리게이트에서 final의 결과를 리턴하는 작업 readResolve
입니다. 이 경우 컬렉션 계층의 루트 클래스에서만 직렬화를 구현해야하며, 현재 또는 향후 구현에서 추가 코드가 필요하지 않습니다.iterator()
및builder()
메소드 (동일한 유형의 콜렉션을 재생성하는 후자-자체적으로 매우 유용한 기능). 또 다른 예는 우리가 완전히 제어하지 않는 코드를 가진 클래스 계층 구조를 갖는 것입니다. 타사 라이브러리의 기본 클래스에는 우리가 알지 못하는 여러 개인 필드가있을 수 있으며 버전에 따라 변경 될 수 있습니다. 직렬화 된 객체. 이 경우 역 직렬화시 데이터를 작성하고 수동으로 개체를 다시 작성하는 것이 더 안전합니다.
String.CaseInsensitiveComparator.readResolve()