[JAVA] Point 87: envisagez d'utiliser un formulaire sérialisé personnalisé

87. Pensez à utiliser un formulaire sérialisé personnalisé

Si vous choisissez un formulaire sérialisé standard, vous devez penser que vous ne pourrez pas le réparer à l'avenir.

Ne choisissez pas le formulaire sérialisé par défaut sans vous demander s'il est approprié ou non

En général, vous ne devez choisir le formulaire sérialisé par défaut que si les résultats du codage sont presque les mêmes que lorsque vous avez choisi de concevoir un formulaire sérialisé personnalisé. La forme sérialisée par défaut est un codage efficace de la représentation physique du graphe d'objets. En d'autres termes

Si le formulaire sérialisé par défaut convient

** Le formulaire sérialisé par défaut est souvent approprié lorsque la représentation physique de l'objet et le contenu logique correspondent. ** ** Par exemple, le formulaire sérialisé suivant convient à une classe qui exprime simplement le nom d'une personne.

// Good candidate for default serialized form
public class Name implements Serializable {

    /**
     * must be non-null
     * @serial
     */
    private final String lastName;

    /**
     * must be non-null
     * @serial
     */
    private final String firstName;

    /**
     * Middle name, or null if there is none
     * @serial
     */
    private final String middleName;

    public Name(String lastName, String firstName, String middleName) {
        super();
        this.lastName = lastName;
        this.firstName = firstName;
        this.middleName = middleName;
    }

}

Le champ d'instance de Name correspond au contenu logique.

** Même si vous décidez que le formulaire sérialisé par défaut est approprié, vous devrez peut-être fournir une méthode readObject pour garantir des conditions et une sécurité immuables. ** ** Dans le cas du Name ci-dessus, la méthode readObject doit garantir que lastName et firstName ne sont pas null. Cela sera décrit en détail aux points 88 et 90.

Bien que lastName, firstName et middleName de Name soient des champs privés, ils sont documentés. La raison est que ces champs privés sont exposés comme une forme sérialisée de la classe. Si vous ajoutez la balise @serial, vous pouvez créer une page dédiée sur le formulaire sérialisé avec le Javadoc généré.

Si le formulaire sérialisé par défaut ne convient pas

Considérez une classe qui représente une liste de chaînes de caractères comme indiqué ci-dessous, en supposant qu'elle se trouve exactement à l'opposé de la classe Name.

//Ne doit pas être le formulaire sérialisé par défaut!
public final class StringList implements Serializable {
    private int size = 0;
    private Entry head = null;

    private static class Entry {
        String data;
        Entry previous;
        Entry next;
    }
}

Logiquement, cette classe représente une séquence de chaînes. Physiquement, il représente une séquence sous forme de liste bidirectionnelle. Si vous acceptez le formulaire sérialisé par défaut, le formulaire sérialisé reflétera tous les éléments de la liste concaténée et tous les liens entre les entrées.

** Il existe quatre inconvénients à l'utilisation du formulaire sérialisé par défaut en cas de divergence entre la représentation physique et le contenu logique. ** **

Liste de chaînes révisée

Une forme sérialisée rationnelle d'une StringList est une liste composée de plusieurs chaînes. Cette structure est une représentation logique des données dans une StringList. Vous trouverez ci-dessous une StringList révisée utilisant writeObject et readObject.

package tryAny.effectiveJava;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

//Amélioration de la liste de chaînes
public final class StringList2 implements Serializable {
    private transient int size = 0;
    private transient Entry head = null;

    // No longer Serializable
    private static class Entry {
        String data;
        Entry previous;
        Entry next;
    }

    //Ajouter une chaîne à la liste
    public final void add(String s) {

    }

    private void writeObject(ObjectOutputStream s) throws IOException {
        s.defaultWriteObject();
        s.writeInt(size);

        for (Entry e = head; e != null; e = e.next) {
            s.writeObject(e.data);
        }
    }

    private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
        s.defaultReadObject();
        int numElements = s.readInt();

        for (int i = 0; i < numElements; i++) {
            add((String) s.readObject());
        }
    }
}

Dans la classe ci-dessus, writeObject appelle d'abord la méthode defaultWriteObject et readObject appelle d'abord la méthode defatlReadObject. On dit parfois qu'il n'est pas nécessaire d'appeler les méthodes defaultWriteObject et defatlReadObject lorsque tous les champs de la classe sont transitoires, mais ce n'est pas le cas et doit être appelé. Ces appels permettent d'ajouter des champs non transitoires dans les versions ultérieures tout en maintenant la compatibilité.

En termes de performances, mesurée avec une liste de 10 chaînes de 10 caractères, la StringList révisée a environ la moitié de la quantité de mémoire avant, double la vitesse de sérialisation et élimine le souci de débordement de pile.

Lorsque la condition invariante de Object n'est pas liée à l'implémentation

L'implémentation physique de la table de hachage est une série de valeurs de hachage clés dans des compartiments de hachage. La valeur de hachage de la clé dépend de l'implémentation, donc l'utilisation du formulaire sérialisé par défaut crée de sérieux bogues.

transient L'appel de defaultWriteObject sérialisera tous les champs à l'exception de ceux spécifiés de façon transitoire, que le formulaire sérialisé par défaut soit utilisé ou non. Essayez d'ajouter le modificateur transitoire autant que possible pour éviter une sérialisation inutile.

Les champs marqués transitoires ont des valeurs initiales par défaut lorsqu'ils sont désérialisés. En d'autres termes, il est nul pour les variables de type référence, 0 pour int et false pour boolean. Si cela n'est pas acceptable, définissez-le avec la méthode readObject (Item88) ou retardez l'initialisation lors de son utilisation (Item83).

Si vous synchronisez avec d'autres méthodes, synchronisez au moment de la sérialisation

Puisqu'il est nécessaire de synchroniser au moment de la sérialisation, il est également nécessaire d'ajouter synchronized à writeObject.

Spécifiez serialVersionUID

En le rendant explicite, les incompatibilités de source peuvent être évitées (Item86). De plus, comme il n'est pas nécessaire de générer le serialVersionUID au moment de l'exécution, les performances seront légèrement améliorées. Lors de l'écriture d'une nouvelle classe, ce nombre peut être n'importe quoi et n'a pas besoin d'être une valeur unique.

Si vous souhaitez attribuer un UID à une classe existante et maintenir la compatibilité, vous devez utiliser une ancienne version de l'UID.

Recommended Posts

Point 87: envisagez d'utiliser un formulaire sérialisé personnalisé
Élément 90: considérez les proxies de sérialisation au lieu des instances sérialisées
Comment supprimer des éléments d'adaptateur personnalisés à l'aide d'un modèle personnalisé
[Introduction à Spring Boot] Soumettez un formulaire à l'aide de thymeleaf