Ich weiß, dass diese Frage sehr alt ist und eine akzeptierte Antwort hat, aber da sie in der Google-Suche sehr häufig auftaucht, dachte ich, ich würde abwägen, da keine Antwort die drei Fälle abdeckt, die ich für wichtig halte - meiner Meinung nach die primäre Verwendung für diese Methoden. Natürlich gehen alle davon aus, dass tatsächlich ein benutzerdefiniertes Serialisierungsformat erforderlich ist.
Nehmen Sie zum Beispiel Sammlungsklassen. Die Standard-Serialisierung einer verknüpften Liste oder einer BST würde zu einem enormen Platzverlust mit sehr geringem Leistungsgewinn führen, verglichen mit der einfachen Serialisierung der Elemente in der angegebenen Reihenfolge. Dies gilt umso mehr, wenn es sich bei einer Sammlung um eine Projektion oder eine Ansicht handelt. Dabei wird auf eine größere Struktur verwiesen, als durch die öffentliche API verfügbar gemacht wird.
Wenn das serialisierte Objekt unveränderliche Felder enthält, für die eine benutzerdefinierte Serialisierung erforderlich ist, reicht die ursprüngliche Lösung von writeObject/readObject
nicht aus, da das deserialisierte Objekt vor dem Lesen des eingeschriebenen Teils des Streams erstellt wird writeObject
. Nehmen Sie diese minimale Implementierung einer verknüpften Liste:
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...
}
Diese Struktur kann serialisiert werden, indem das head
Feld jedes Links rekursiv geschrieben wird, gefolgt von einem null
Wert. Das Deserialisieren eines solchen Formats wird jedoch unmöglich: readObject
Die Werte der Mitgliedsfelder können nicht geändert werden (jetzt festgelegt auf null
). Hier kommt das writeReplace
/ readResolve
Paar:
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;
}
}
}
Es tut mir leid, wenn das obige Beispiel nicht kompiliert wird (oder funktioniert), aber hoffentlich reicht es aus, um meinen Standpunkt zu veranschaulichen. Wenn Sie der Meinung sind, dass dies ein sehr weit hergeholtes Beispiel ist, denken Sie bitte daran, dass viele funktionale Sprachen auf der JVM ausgeführt werden und dieser Ansatz in ihrem Fall von wesentlicher Bedeutung ist.
Wir möchten vielleicht tatsächlich ein Objekt einer anderen Klasse deserialisieren, als wir an das geschrieben haben ObjectOutputStream
. Dies wäre der Fall bei Ansichten wie einer java.util.List
Listenimplementierung, die ein Slice von einem längeren verfügbar macht ArrayList
. Offensichtlich ist es eine schlechte Idee, die gesamte Hintergrundliste zu serialisieren, und wir sollten nur die Elemente aus dem angezeigten Slice schreiben. Warum jedoch damit aufhören und nach der Deserialisierung ein nutzloses Maß an Indirektion haben? Wir könnten einfach die Elemente aus dem Stream in einen lesen ArrayList
und ihn direkt zurückgeben, anstatt ihn in unsere Ansichtsklasse einzuschließen.
Alternativ kann eine ähnliche Delegatenklasse, die der Serialisierung gewidmet ist, eine Entwurfswahl sein. Ein gutes Beispiel wäre die Wiederverwendung unseres Serialisierungscodes. Wenn wir beispielsweise eine Builder-Klasse haben (ähnlich dem StringBuilder für String), können wir einen Serialisierungsdelegierten schreiben, der jede Sammlung serialisiert, indem wir einen leeren Builder in den Stream schreiben, gefolgt von der Sammlungsgröße und den vom Iterator der Spalte zurückgegebenen Elementen. Bei der Deserialisierung wird der Builder gelesen, alle anschließend gelesenen Elemente angehängt und das Ergebnis des Finales build()
von den Delegierten zurückgegeben readResolve
. In diesem Fall müssten wir die Serialisierung nur in der Stammklasse der Auflistungshierarchie implementieren, und es wäre kein zusätzlicher Code für aktuelle oder zukünftige Implementierungen erforderlich, sofern sie abstrakt implementiereniterator()
undbuilder()
Methode (letztere zur Neuerstellung der Sammlung des gleichen Typs - was an sich eine sehr nützliche Funktion wäre). Ein anderes Beispiel wäre eine Klassenhierarchie, deren Code wir nicht vollständig steuern. Unsere Basisklasse (n) aus einer Drittanbieter-Bibliothek können eine beliebige Anzahl von privaten Feldern enthalten, von denen wir nichts wissen und die sich von einer Version zur anderen ändern können unsere serialisierten Objekte. In diesem Fall wäre es sicherer, die Daten zu schreiben und das Objekt bei der Deserialisierung manuell neu zu erstellen.
String.CaseInsensitiveComparator.readResolve()