Java-Serialisierung: readObject () vs. readResolve ()


127

Das Buch Effective Java und andere Quellen bieten eine ziemlich gute Erklärung, wie und wann die readObject () -Methode bei der Arbeit mit serialisierbaren Java-Klassen verwendet werden soll. Die readResolve () -Methode bleibt dagegen ein Rätsel. Grundsätzlich erwähnen alle Dokumente, die ich gefunden habe, entweder nur eines der beiden oder beide nur einzeln.

Fragen, die unbeantwortet bleiben, sind:

  • Was ist der Unterschied zwischen den beiden Methoden?
  • Wann soll welche Methode implementiert werden?
  • Wie sollte readResolve () verwendet werden, insbesondere im Hinblick auf die Rückgabe von was?

Ich hoffe, Sie können etwas Licht in diese Angelegenheit bringen.


Beispiel aus dem JDK von Oracle:String.CaseInsensitiveComparator.readResolve()
Kevinarpe

Antworten:


137

readResolvewird zum Ersetzen des aus dem Stream gelesenen Objekts verwendet. Die einzige Verwendung, die ich jemals dafür gesehen habe, ist das Erzwingen von Singletons. Wenn ein Objekt gelesen wird, ersetzen Sie es durch die Singleton-Instanz. Dies stellt sicher, dass niemand eine andere Instanz erstellen kann, indem der Singleton serialisiert und deserialisiert wird.


3
Es gibt eine Reihe von Möglichkeiten, wie bösartiger Code (oder sogar Daten) dies umgehen kann.
Tom Hawtin - Tackline

6
Josh Bloch spricht über die Bedingungen, unter denen dies in Java 2nd Ed bricht. Punkt 77. Er erwähnt dies in diesem Vortrag, den er vor ein paar Jahren in Google IO gehalten hat (einige Male gegen Ende des Vortrags): youtube.com/watch?v=pi_I7oD_uGI
calvinkrishy

17
Ich finde diese Antwort etwas unzureichend, da sie keine transientFelder erwähnt. readResolvewird zum Auflösen des Objekts nach dem Lesen verwendet. Eine beispielhafte Verwendung ist möglicherweise, dass ein Objekt einen Cache enthält, der aus vorhandenen Daten neu erstellt werden kann und nicht serialisiert werden muss. Die zwischengespeicherten Daten können deklariert transientund readResolve()nach der Deserialisierung neu erstellt werden. Für solche Dinge ist diese Methode gedacht.
Jason C

2
@JasonC Ihr Kommentar, dass "Dinge wie diese [vorübergehende Behandlung], wofür diese Methode ist ", ist irreführend. Im Java-Dokument Serializableheißt es: "Klassen, die einen Ersatz angeben müssen, wenn eine Instanz davon aus dem Stream gelesen wird, sollten diese [ readResolve] spezielle Methode implementieren ...".
Opher

2
Die readResolve-Methode kann auch in einem Eckfall verwendet werden, in dem Sie viele Objekte serialisiert und in einer Datenbank gespeichert haben. Wenn Sie diese Daten zu einem späteren Zeitpunkt in ein neues Format migrieren möchten, können Sie dies problemlos mit der readResolve-Methode erreichen.
Nilesh Rajani

29

Punkt 90, Effective Java, 3rd Ed Cover readResolveund writeReplacefür serielle Proxys - deren Hauptverwendung. Die Beispiele schreiben nicht aus readObjectundwriteObject Methoden , da sie die Standardserialisierung zum Lesen und Schreiben von Feldern verwenden.

readResolvewird aufgerufen, nachdem readObjectzurückgekehrt ist (umgekehrt writeReplacewird vorher writeObjectund wahrscheinlich für ein anderes Objekt aufgerufen ). Das Objekt, das die Methode zurückgibt, ersetzt das thisan den Benutzer ObjectInputStream.readObjectzurückgegebene Objekt und alle weiteren Verweise auf das Objekt im Stream. Beide readResolveund writeReplacekönnen Objekte desselben oder unterschiedlichen Typs zurückgeben. Die Rückgabe des gleichen Typs ist in einigen Fällen hilfreich, in denen Felder vorhanden sein müssenfinal und entweder Abwärtskompatibilität erforderlich ist oder Werte kopiert und / oder validiert werden müssen.

Die Verwendung von readResolveerzwingt die Singleton-Eigenschaft nicht.


9

readResolve kann verwendet werden, um die Daten zu ändern, die über die readObject-Methode serialisiert werden. Beispielsweise verwendet die xstream-API diese Funktion, um einige Attribute zu initialisieren, die nicht im zu deserialisierenden XML enthalten waren.

http://x-stream.github.io/faq.html#Serialization


1
XML und Xstream sind für eine Frage zur Java-Serialisierung nicht relevant, und die Frage wurde vor Jahren richtig beantwortet. -1
Marquis von Lorne

5
Die akzeptierte Antwort besagt, dass readResolve verwendet wird, um ein Objekt zu ersetzen. Diese Antwort enthält die nützlichen zusätzlichen Informationen, mit denen ein Objekt während der Deserialisierung geändert werden kann. XStream wurde als Beispiel angegeben, nicht als die einzig mögliche Bibliothek, in der dies geschieht.
Enwired

5

readResolve ist für den Fall gedacht, dass Sie möglicherweise ein vorhandenes Objekt zurückgeben müssen, z. B. weil Sie nach doppelten Eingaben suchen, die zusammengeführt werden sollen, oder (z. B. in eventuell konsistenten verteilten Systemen), weil es sich um ein Update handelt, das möglicherweise eintrifft, bevor Sie es bemerken ältere Versionen.


readResolve () war mir klar, aber ich habe immer noch einige unerklärliche Fragen im Kopf, aber Ihre Antwort las nur meine Gedanken, danke
Rajni Gangwar

5

readObject () ist eine in ObjectInputStream vorhandene Methode Klasse. das Lesen des Objekts zum Zeitpunkt der Deserialisierung die readObject-Methode intern prüft, ob das zu deserialisierende Klassenobjekt die readResolve-Methode hat oder nicht, wenn die readResolve-Methode vorhanden ist, ruft es die readResolve-Methode auf und gibt dieselbe zurück Beispiel.

Die Absicht, die readResolve-Methode zu schreiben, ist daher eine gute Vorgehensweise, um ein reines Singleton-Entwurfsmuster zu erzielen, bei dem niemand durch Serialisierung / Deserialisierung eine andere Instanz erhalten kann.



2

Wenn die Serialisierung verwendet wird, um ein Objekt so zu konvertieren, dass es in einer Datei gespeichert werden kann, können wir die Methode readResolve () auslösen. Die Methode ist privat und wird in derselben Klasse gespeichert, deren Objekt während der Deserialisierung abgerufen wird. Es stellt sicher, dass nach der Deserialisierung das zurückgegebene Objekt das gleiche ist, das serialisiert wurde. Das ist,instanceSer.hashCode() == instanceDeSer.hashCode()

Die Methode readResolve () ist keine statische Methode. Nachdem in.readObject()während der Deserialisierung aufgerufen wird, wird nur sichergestellt, dass das zurückgegebene Objekt mit dem Objekt identisch ist, das wie folgt serialisiert wurdeout.writeObject(instanceSer)

..
    ObjectOutput out = new ObjectOutputStream(new FileOutputStream("file1.ser"));
    out.writeObject(instanceSer);
    out.close();

Auf diese Weise hilft es auch bei der Implementierung von Singleton-Entwurfsmustern , da jedes Mal dieselbe Instanz zurückgegeben wird.

public static ABCSingleton getInstance(){
    return ABCSingleton.instance; //instance is static 
}

1

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.

  1. 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/readObjectnicht 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 headFeld jedes Links rekursiv geschrieben wird, gefolgt von einem nullWert. Das Deserialisieren eines solchen Formats wird jedoch unmöglich: readObjectDie Werte der Mitgliedsfelder können nicht geändert werden (jetzt festgelegt auf null). Hier kommt das writeReplace/ readResolvePaar:

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.

  1. 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.ListListenimplementierung, 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 ArrayListund ihn direkt zurückgeben, anstatt ihn in unsere Ansichtsklasse einzuschließen.

  2. 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.


0

Die readResolve-Methode

Bei serialisierbaren und externisierbaren Klassen ermöglicht die readResolve-Methode einer Klasse, das aus dem Stream gelesene Objekt zu ersetzen / aufzulösen, bevor es an den Aufrufer zurückgegeben wird. Durch die Implementierung der readResolve-Methode kann eine Klasse die Typen und Instanzen ihrer eigenen zu deserialisierenden Instanzen direkt steuern. Die Methode ist wie folgt definiert:

ANY-ACCESS-MODIFIER Das Objekt readResolve () löst eine ObjectStreamException aus.

Die readResolve- Methode wird aufgerufen, wenn ObjectInputStream ein Objekt aus dem Stream gelesen hat und sich darauf vorbereitet, es an den Aufrufer zurückzugeben. ObjectInputStream prüft, ob die Klasse des Objekts die readResolve-Methode definiert. Wenn die Methode definiert ist, wird die readResolve-Methode aufgerufen, damit das Objekt im Stream das zurückzugebende Objekt festlegen kann. Das zurückgegebene Objekt sollte von einem Typ sein, der mit allen Verwendungszwecken kompatibel ist. Wenn es nicht kompatibel ist, wird eine ClassCastException ausgelöst, wenn die Typinkongruenz festgestellt wird.

Zum Beispiel kann ein Symbol kann Klasse , für die geschaffen werden , um nur eine einzige Instanz jeden Symbol Bindung innerhalb einer virtuellen Maschine vorhanden ist. Die readResolve- Methode würde implementiert, um festzustellen, ob dieses Symbol bereits definiert wurde, und das bereits vorhandene äquivalente Symbolobjekt zu ersetzen, um die Identitätsbeschränkung beizubehalten. Auf diese Weise kann die Eindeutigkeit von Symbolobjekten über die Serialisierung hinweg beibehalten werden.


0

Wie bereits beantwortet, readResolvehandelt es sich um eine private Methode, die in ObjectInputStream beim Deserialisieren eines Objekts verwendet wird. Dies wird unmittelbar vor der Rückgabe der tatsächlichen Instanz aufgerufen. Im Fall von Singleton können wir hier die Rückgabe einer bereits vorhandenen Singleton-Instanzreferenz anstelle einer deserialisierten Instanzreferenz erzwingen. Ähnlich haben wir writeReplacefür ObjectOutputStream.

Beispiel für readResolve:

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class SingletonWithSerializable implements Serializable {
private static final long serialVersionUID = 1L;

public static final SingletonWithSerializable INSTANCE = new SingletonWithSerializable();

private SingletonWithSerializable() {
    if (INSTANCE != null)
        throw new RuntimeException("Singleton instance already exists!");
}

private Object readResolve() {
    return INSTANCE;
}

public void leaveTheBuilding() {
    System.out.println("SingletonWithPublicFinalField.leaveTheBuilding() called...");
}

public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
    SingletonWithSerializable instance = SingletonWithSerializable.INSTANCE;

    System.out.println("Before serialization: " + instance);

    try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("file1.ser"))) {
        out.writeObject(instance);
    }

    try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("file1.ser"))) {
        SingletonWithSerializable readObject = (SingletonWithSerializable) in.readObject();
        System.out.println("After deserialization: " + readObject);
    }

}

}}

Ausgabe:

Before serialization: com.ej.item3.SingletonWithSerializable@7852e922
After deserialization: com.ej.item3.SingletonWithSerializable@7852e922
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.