Serializzazione Java: readObject () vs. readResolve ()


127

Il libro Effective Java e altre fonti forniscono una spiegazione abbastanza valida su come e quando utilizzare il metodo readObject () quando si lavora con classi Java serializzabili. Il metodo readResolve (), d'altra parte, rimane un po 'un mistero. Fondamentalmente tutti i documenti che ho trovato menzionano solo uno dei due o menzionano entrambi solo individualmente.

Le domande che rimangono senza risposta sono:

  • Qual è la differenza tra i due metodi?
  • Quando dovrebbe essere implementato questo metodo?
  • Come dovrebbe essere usato readResolve (), specialmente in termini di restituzione di cosa?

Spero che tu possa far luce su questo argomento.


Esempio dal JDK di Oracle:String.CaseInsensitiveComparator.readResolve()
kevinarpe,

Risposte:


138

readResolveè usato per sostituire l'oggetto letto dallo stream. L'unico uso che io abbia mai visto per questo è far rispettare i singoli; quando viene letto un oggetto, sostituirlo con l'istanza singleton. Ciò garantisce che nessuno possa creare un'altra istanza serializzando e deserializzando il singleton.


3
Esistono diversi modi per aggirare il codice dannoso (o anche i dati).
Tom Hawtin - tackline il

6
Josh Bloch parla delle condizioni in cui ciò si rompe nell'efficace 2a edizione di Java. Punto 77. Ne parla in questo discorso che ha tenuto in Google IO un paio di anni fa (alcune volte verso la fine del discorso): youtube.com/watch?v=pi_I7oD_uGI
calvinkrishy,

17
Trovo questa risposta leggermente inadeguata, in quanto non menziona i transientcampi. readResolveè usato per risolvere l'oggetto dopo che è stato letto. Un esempio di utilizzo è forse un oggetto che contiene della cache che può essere ricreata dai dati esistenti e non ha bisogno di essere serializzato; i dati memorizzati nella cache possono essere dichiarati transiente readResolve()possono essere ricostruiti dopo la deserializzazione. Cose del genere servono a questo metodo.
Jason C,

2
@JasonC il commento che "le cose del genere [la gestione transitoria] sono ciò che questo metodo è per i " è fuorviante. Vedi il documento Java per Serializable: dice "Le classi che devono designare una sostituzione quando un'istanza di essa viene letta dal flusso dovrebbe implementare questo [ readResolve] metodo speciale ...".
Opher,

2
Il metodo readResolve può essere utilizzato anche in un caso angolare in cui si supponga di aver serializzato molti oggetti e di averli memorizzati nel database. Se in un secondo momento si desidera migrare quei dati in un nuovo formato, è possibile ottenerli facilmente nel metodo readResolve.
Nilesh Rajani,

29

Articolo 90, Java efficace, copertine della 3a edizione readResolvee writeReplaceper proxy seriali - il loro uso principale. Gli esempi non scrivono readObjecte writeObjectmetodi perché usano la serializzazione predefinita per leggere e scrivere i campi.

readResolveviene chiamato dopo che readObjectè tornato (al contrario writeReplaceviene chiamato prima writeObjecte probabilmente su un oggetto diverso). L'oggetto restituito dal metodo sostituisce l' thisoggetto restituito all'utente ObjectInputStream.readObjecte qualsiasi ulteriore riferimento all'oggetto nel flusso. Entrambi readResolvee writeReplacepossono restituire oggetti dello stesso tipo o di tipo diverso. Restituire lo stesso tipo è utile in alcuni casi in cui i campi devono essere finaled è richiesta la compatibilità con le versioni precedenti o i valori devono essere copiati e / o convalidati.

L'uso di readResolvenon impone la proprietà singleton.


9

readResolve può essere utilizzato per modificare i dati serializzati tramite il metodo readObject. Ad esempio, l'API xstream utilizza questa funzione per inizializzare alcuni attributi che non erano nell'XML da deserializzare.

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


1
XML e Xstream non sono rilevanti per una domanda sulla serializzazione Java e alla domanda è stata data risposta correttamente anni fa. -1
Marchese di Lorne,

5
La risposta accettata afferma che readResolve viene utilizzato per sostituire un oggetto. Questa risposta fornisce utili informazioni aggiuntive che possono essere utilizzate per modificare un oggetto durante la deserializzazione. XStream è stato fornito come esempio, non come l'unica libreria possibile in cui ciò accade.
Enwired il

5

readResolve è per quando potrebbe essere necessario restituire un oggetto esistente, ad esempio perché si stanno verificando input duplicati che devono essere uniti, oppure (ad esempio in sistemi distribuiti eventualmente coerenti) perché è un aggiornamento che potrebbe arrivare prima che l'utente sia a conoscenza eventuali versioni precedenti.


readResolve () mi è stato chiaro, ma ho ancora in mente alcune domande inspiegabili, ma la tua risposta mi ha appena letto nella mente, grazie
Rajni Gangwar,

5

readObject () è un metodo esistente nella classe ObjectInputStream . durante la lettura dell'oggetto al momento della deserializzazione il metodo readObject verifica internamente se l'oggetto di classe che viene deserializzato con il metodo readResolve o meno se esiste il metodo readResolve, invocherà il metodo readResolve e restituirà lo stesso esempio.

Quindi l'intento di scrivere il metodo readResolve è una buona pratica per ottenere un modello di progettazione singleton in cui nessuno può ottenere un'altra istanza serializzando / deserializzando.



2

Quando la serializzazione viene utilizzata per convertire un oggetto in modo che possa essere salvato in un file, possiamo attivare un metodo, readResolve (). Il metodo è privato ed è mantenuto nella stessa classe il cui oggetto viene recuperato durante la deserializzazione. Assicura che dopo la deserializzazione l'oggetto restituito sia lo stesso di quello serializzato. Questo è,instanceSer.hashCode() == instanceDeSer.hashCode()

Il metodo readResolve () non è un metodo statico. Dopo in.readObject()viene chiamato durante la deserializzazione, si assicura solo che l'oggetto restituito sia lo stesso di quello serializzato come di seguito mentreout.writeObject(instanceSer)

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

In questo modo, aiuta anche nell'implementazione del modello di progettazione singleton , perché ogni volta che viene restituita la stessa istanza.

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

1

So che questa domanda è molto vecchia e ha una risposta accettata, ma siccome appare molto in alto nella ricerca di Google ho pensato di pesare perché nessuna risposta fornita copre i tre casi che ritengo importanti - nella mia mente l'uso primario per questi metodi. Naturalmente, tutti presumono che sia effettivamente necessario un formato di serializzazione personalizzato.

Prendi, ad esempio, le classi di raccolta. La serializzazione predefinita di un elenco collegato o di un BST comporterebbe un'enorme perdita di spazio con un bassissimo guadagno in termini di prestazioni rispetto alla semplice serializzazione degli elementi in ordine. Ciò è ancora più vero se una raccolta è una proiezione o una vista: mantiene un riferimento a una struttura più ampia di quella che espone dalla sua API pubblica.

  1. Se l'oggetto serializzato ha campi immutabili che richiedono una serializzazione personalizzata, la soluzione originale di writeObject/readObjectè insufficiente, poiché l'oggetto deserializzato viene creato prima di leggere la parte del flusso scritta writeObject. Prendi questa implementazione minima di un elenco collegato:

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

Questa struttura può essere serializzata scrivendo ricorsivamente il headcampo di ogni collegamento, seguito da un nullvalore. La deserializzazione di un tale formato diventa tuttavia impossibile: readObjectnon è possibile modificare i valori dei campi membro (ora risolti in null). Ecco la coppia 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;
        }
    }
}

Mi dispiace se l'esempio sopra non viene compilato (o non funziona), ma spero che sia sufficiente per illustrare il mio punto. Se pensate che questo sia un esempio molto inverosimile, ricordate che molti linguaggi funzionali funzionano sulla JVM e questo approccio diventa essenziale nel loro caso.

  1. Potremmo voler effettivamente deserializzare un oggetto di una classe diversa da quella che abbiamo scritto su ObjectOutputStream. Questo sarebbe il caso di viste come java.util.Listun'implementazione dell'elenco che espone una sezione da una più lunga ArrayList. Ovviamente, serializzare l'intera lista di supporto è una cattiva idea e dovremmo solo scrivere gli elementi dalla sezione visualizzata. Perché fermarsi ad esso e avere un livello inutile di indiretta dopo la deserializzazione? Potremmo semplicemente leggere gli elementi dallo stream in un ArrayListe restituirlo direttamente invece di racchiuderlo nella nostra classe di visualizzazione.

  2. In alternativa, avere una classe delegata simile dedicata alla serializzazione può essere una scelta progettuale. Un buon esempio potrebbe essere il riutilizzo del nostro codice di serializzazione. Ad esempio, se disponiamo di una classe builder (simile a StringBuilder for String), possiamo scrivere un delegato di serializzazione che serializza qualsiasi raccolta scrivendo un generatore vuoto sullo stream, seguito dalle dimensioni della raccolta e dagli elementi restituiti dall'iteratore della raccolta. La deserializzazione implicherebbe la lettura del builder, l'aggiunta di tutti gli elementi successivamente letti e la restituzione del risultato del finale build()dai delegati readResolve. In tal caso avremmo bisogno di implementare la serializzazione solo nella classe radice della gerarchia di raccolta e non sarebbe necessario alcun codice aggiuntivo dalle implementazioni attuali o future, a condizione che implementino abstract iterator()ebuilder()metodo (quest'ultimo per ricreare la raccolta dello stesso tipo - che sarebbe una caratteristica molto utile in sé). Un altro esempio potrebbe essere avere una gerarchia di classi su quale codice non controlliamo completamente: le nostre classi di base da una libreria di terze parti potrebbero avere un numero qualsiasi di campi privati ​​di cui non sappiamo nulla e che potrebbero cambiare da una versione all'altra, interrompendo i nostri oggetti serializzati. In tal caso sarebbe più sicuro scrivere i dati e ricostruire manualmente l'oggetto sulla deserializzazione.


0

Il metodo readResolve

Per le classi serializzabili ed esternalizzabili, il metodo readResolve consente a una classe di sostituire / risolvere l'oggetto letto dallo stream prima che venga restituito al chiamante. Implementando il metodo readResolve, una classe può controllare direttamente i tipi e le istanze delle proprie istanze da deserializzare. Il metodo è definito come segue:

ANY-ACCESS-MODIFIER L'oggetto readResolve () genera ObjectStreamException;

Il metodo readResolve viene chiamato quando ObjectInputStream ha letto un oggetto dallo stream e si sta preparando a restituirlo al chiamante. ObjectInputStream verifica se la classe dell'oggetto definisce il metodo readResolve. Se il metodo è definito, viene chiamato il metodo readResolve per consentire all'oggetto nel flusso di designare l'oggetto da restituire. L'oggetto restituito deve essere di un tipo compatibile con tutti gli usi. Se non è compatibile, verrà generata una ClassCastException quando viene rilevata la mancata corrispondenza del tipo.

Ad esempio, è possibile creare una classe Symbol per la quale esiste un'unica istanza di ciascun binding di simboli all'interno di una macchina virtuale. Il metodo readResolve verrebbe implementato per determinare se quel simbolo era già definito e sostituire l'oggetto Symbol equivalente preesistente per mantenere il vincolo di identità. In questo modo l'unicità degli oggetti Symbol può essere mantenuta attraverso la serializzazione.


0

Come già risposto, readResolveè un metodo privato utilizzato in ObjectInputStream durante la deserializzazione di un oggetto. Viene chiamato appena prima che venga restituita l'istanza effettiva. Nel caso di Singleton, qui possiamo forzare il ritorno del riferimento di istanza singleton già esistente invece del riferimento di istanza deserializzato. Simile abbiamo writeReplaceper ObjectOutputStream.

Esempio per 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);
    }

}

}

Produzione:

Before serialization: com.ej.item3.SingletonWithSerializable@7852e922
After deserialization: com.ej.item3.SingletonWithSerializable@7852e922
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.