web-dev-qa-db-fra.com

Java sérialisation: readObject () vs. readResolve ()

Le livre Effective Java et d’autres sources expliquent assez bien comment et quand utiliser la méthode readObject () lorsque vous utilisez des classes serializable Java. La méthode readResolve () La méthode, en revanche, reste un peu mystérieuse: tous les documents que j'ai trouvés ne mentionnent que l'un des deux ou les deux individuellement.

Les questions qui restent sans réponse sont:

  • Quelle est la différence entre les deux méthodes?
  • Quand faut-il mettre en œuvre cette méthode?
  • Comment faut-il utiliser readResolve (), notamment pour renvoyer quoi?

J'espère que vous pourrez nous éclairer à ce sujet.

124
Forage

readResolve est utilisé pour remplacement l'objet lu dans le flux. La seule utilisation que j'ai jamais vue pour ceci est d'imposer des singletons; lorsqu'un objet est lu, remplacez-le par l'instance singleton. Cela garantit que personne ne peut créer une autre instance en sérialisant et en désérialisant le singleton.

130
Michael Myers

La méthode readResolve est appelée lorsque ObjectInputStream a lu un objet dans le flux et se prépare à le renvoyer à l'appelant. ObjectInputStream vérifie si la classe de l'objet définit la méthode readResolve. Si la méthode est définie, la méthode readResolve est appelée pour permettre à l'objet du flux de désigner l'objet à renvoyer. L'objet renvoyé doit être d'un type compatible avec toutes les utilisations. Si ce n'est pas compatible, un ClassCastException sera lancé lorsque l'incompatibilité de type est découverte.

40
AZ_

L'élément 90, Effective Java, 3rd Ed couvre readResolve et writeReplace pour les mandataires série - leur utilisation principale. Les exemples n'écrivent pas les méthodes readObject et writeObject car ils utilisent la sérialisation par défaut pour lire et écrire des champs.

readResolve est appelé après le retour de readObject (à l'inverse, writeReplace est appelé avant writeObject et probablement sur un autre objet). L'objet que la méthode retourne remplace this objet renvoyé à l'utilisateur de ObjectInputStream.readObject et toute référence ultérieure à l'objet dans le flux. readResolve et writeReplace peuvent tous deux renvoyer des objets du même type ou de types différents. Le retour du même type est utile dans certains cas où les champs doivent être final et que la compatibilité avec les versions antérieures est requise ou que les valeurs doivent être copiées et/ou validées.

L'utilisation de readResolve n'impose pas la propriété singleton.

28

readResolve peut être utilisé pour modifier les données sérialisées via la méthode readObject. Par exemple L'API xstream utilise cette fonctionnalité pour initialiser certains attributs qui n'étaient pas dans le XML à désérialiser.

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

9
endless

readResolve est pour quand vous pouvez avoir besoin de retourner un objet existant, par exemple. parce que vous vérifiez si des entrées en double doivent être fusionnées ou (par exemple, dans des systèmes distribués éventuellement cohérents) car il s'agit d'une mise à jour qui peut arriver avant que vous ne connaissiez des versions plus anciennes.

5
Pr0methean

readObject () est une méthode existante dans ObjectInputStream class.while en lisant un objet au moment de la désérialisation. La méthode readObject vérifie en interne si l'objet de la classe en cours de désérialisation ayant la méthode readResolve est présent ou non. invoquez la méthode readResolve et renvoyez la même instance.

Par conséquent, la méthode readResolve d'écriture intense est une bonne pratique pour obtenir un modèle de conception singleton pur où personne ne peut obtenir une autre instance en sérialisant/désérialisant.

3
arjun kumar mehta

readResolve () assurera le contrat singleton pendant la sérialisation.
S'il vous plaît voir

3
Kanagavelu Sugumar

Lorsque la sérialisation est utilisée pour convertir un objet afin qu'il puisse être enregistré dans un fichier, nous pouvons déclencher une méthode, readResolve (). La méthode est privée et est conservée dans la même classe dont l'objet est en cours de récupération lors de la désérialisation. Cela garantit qu'après la désérialisation, l'objet renvoyé est le même que celui qui a été sérialisé. C'est-à-dire instanceSer.hashCode() == instanceDeSer.hashCode()

la méthode readResolve () n'est pas une méthode statique. Après l'appel de in.readObject() pendant la désérialisation, il s'assure simplement que l'objet renvoyé est identique à celui qui a été sérialisé comme ci-dessous, tandis que out.writeObject(instanceSer)

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

De cette façon, cela aide également dans modèle de conception singleton, car chaque fois qu'une même instance est renvoyée.

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

Je sais que cette question est très ancienne et a une réponse acceptée, mais comme elle apparaît très haut dans la recherche Google, je me suis dit que j'y aurais ma part, car aucune réponse fournie ne couvre les trois cas que j'estime importants - à mon sens, leur principale utilisation méthodes. Bien entendu, tous supposent qu'il existe un besoin de format de sérialisation personnalisé.

Prenez, par exemple, les classes de collection. La sérialisation par défaut d'une liste chaînée ou d'un fichier BST entraînerait une perte d'espace considérable avec un gain de performances très faible par rapport à la sérialisation des éléments dans l'ordre. Cela est encore plus vrai si une collection est une projection ou une vue - conserve une référence à une structure plus grande que celle exposée par son API publique.

  1. Si l'objet sérialisé a des champs immuables nécessitant une sérialisation personnalisée, la solution originale de writeObject/readObject Est insuffisante, car l'objet désérialisé est créé avant en lisant la partie du flux écrite en writeObject. Prenez cette implémentation minimale d'une liste chaînée:

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

Cette structure peut être sérialisée en écrivant de manière récursive le champ head de chaque lien, suivi d'une valeur null. Désérialiser un tel format devient cependant impossible: readObject ne peut pas changer les valeurs des champs membres (maintenant fixé à null). Voici la paire 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;
        }
    }
}

Je suis désolé si l'exemple ci-dessus ne compile pas (ou ne fonctionne pas), mais j'espère que cela suffira à illustrer mon propos. Si vous pensez que cet exemple est très poussé, rappelez-vous que de nombreux langages fonctionnels s'exécutent sur la JVM et que cette approche devient essentielle dans leur cas.

  1. Nous souhaitons peut-être désérialiser un objet d'une classe différente de celle que nous avons écrite dans le fichier ObjectOutputStream. Ce serait le cas avec des vues telles qu'une implémentation de liste Java.util.List Qui expose une tranche d'un plus long ArrayList. Evidemment, la sérialisation de toute la liste de sauvegarde est une mauvaise idée et nous ne devrions écrire que les éléments de la tranche visualisée. Pourquoi s'arrêter là et avoir un niveau d'indirection inutile après la désérialisation? Nous pourrions simplement lire les éléments du flux dans un ArrayList et le retourner directement au lieu de le placer dans notre classe de vue.

  2. Une autre classe de délégué dédiée à la sérialisation peut constituer un choix de conception. Un bon exemple consisterait à réutiliser notre code de sérialisation. Par exemple, si nous avons une classe de générateur (similaire au StringBuilder for String), nous pouvons écrire un délégué de sérialisation qui sérialise toute collection en écrivant un générateur vide dans le flux, suivi de la taille de la collection et des éléments renvoyés par l'itérateur de la collection. La désérialisation impliquerait de lire le générateur, d'ajouter tous les éléments lus par la suite et de renvoyer le résultat final de build() à partir des délégués readResolve. Dans ce cas, nous aurions besoin d'implémenter la sérialisation uniquement dans la classe racine de la hiérarchie de collection, et aucun code supplémentaire ne serait nécessaire à partir des implémentations actuelles ou futures, à condition qu'elles implémentent abstract iterator() et builder() méthode (cette dernière méthode permettant de recréer la collection du même type - ce qui constituerait une fonctionnalité très utile en elle-même). Un autre exemple serait d'avoir une hiérarchie de classes dont le code ne serait pas entièrement contrôlé - nos classes de base d'une bibliothèque tierce pourraient avoir un nombre quelconque de champs privés que nous ne connaissons pas et qui pourraient changer d'une version à l'autre nos objets sérialisés. Dans ce cas, il serait plus sûr d'écrire les données et de reconstruire l'objet manuellement lors de la désérialisation.

0
Turin

Comme déjà répondu, readResolve est une méthode privée utilisée dans ObjectInputStream lors de la désérialisation d'un objet. Ceci est appelé juste avant le retour de l'instance réelle. Dans le cas de Singleton, nous pouvons ici forcer le renvoi d'une référence d'instance de singleton déjà existante au lieu d'une référence d'instance désérialisée. De même, nous avons writeReplace pour ObjectOutputStream.

Exemple pour 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);
    }

}

}

Sortie:

Before serialization: com.ej.item3.SingletonWithSerializable@7852e922
After deserialization: com.ej.item3.SingletonWithSerializable@7852e922
0
Omkar

La méthode readResolve

Pour les classes Serializable et Externalizable, la méthode readResolve permet à une classe de remplacer/résoudre l'objet lu dans le flux avant qu'il ne soit renvoyé à l'appelant. En implémentant la méthode readResolve, une classe peut contrôler directement les types et instances de ses propres instances en cours de désérialisation. La méthode est définie comme suit:

ANY-ACCESS-MODIFIER Objet readResolve () lève ObjectStreamException;

La méthode readResolve est appelée lorsque ObjectInputStream a lu un objet du flux. et se prépare à le renvoyer à l'appelant. ObjectInputStream vérifie si la classe de l'objet définit la méthode readResolve. Si la méthode est définie, la méthode readResolve est appelée pour permettre à l'objet du flux de désigner l'objet à renvoyer. L'objet renvoyé doit être d'un type compatible avec toutes les utilisations. Si ce n'est pas compatible, une ClassCastException sera levée lorsque la non-concordance de type est découverte.

Par exemple, une classe Symbol peut être créée pour laquelle une seule instance de chaque liaison de symbole existe dans une machine virtuelle. La méthode readResolve serait implémentée pour déterminer si ce symbole était déjà défini et substituer l'objet Symbol équivalent préexistant pour conserver la contrainte d'identité. De cette manière, l'unicité des objets Symbol peut être conservée tout au long de la sérialisation.

0
Ankush soni