web-dev-qa-db-fra.com

Comment Java sérialisation désérialise les champs finaux quand aucun constructeur par défaut n'est spécifié?

J'ai une classe définissant un type de valeur immuable que je dois maintenant sérialiser. L'immutabilité vient des champs finaux qui sont définis dans le constructeur. J'ai essayé de sérialiser, et cela fonctionne (étonnamment?) - mais je ne sais pas comment.

Voici un exemple de la classe

public class MyValueType implements Serializable
{
    private final int value;

    private transient int derivedValue;

    public MyValueType(int value)
    {
        this.value = value;
        this.derivedValue = derivedValue(value);
    }

    // getters etc...
}

Étant donné que la classe n'a pas de constructeur no arg, comment peut-elle être instanciée et le champ final défini?

(Un aparté - J'ai remarqué cette classe en particulier parce que IDEA ne générait pas d'avertissement d'inspection "no serialVersionUID" pour cette classe, mais a généré avec succès des avertissements pour d'autres classes que je viens de rendre sérialisables. )

49
mdma

La désérialisation est implémentée par la JVM à un niveau inférieur aux constructions de langage de base. Plus précisément, il n'appelle aucun constructeur.

40

Étant donné que la classe n'a pas de constructeur no arg, comment peut-elle être instanciée et le champ final défini?

Une magie noire désagréable se produit. Il existe une porte dérobée dans la machine virtuelle Java qui permet de créer un objet sans invoquer de constructeur. Les champs du nouvel objet sont d'abord initialisés à leurs valeurs par défaut (false, 0, null, etc.), puis le code de désérialisation de l'objet remplit les champs avec des valeurs du flux d'objets.

(Maintenant que Java est open source, vous pouvez lire le code qui fait cela ... et pleurer!)

20
Stephen C

Michael et Stephen vous ont donné une excellente réponse, je veux juste vous mettre en garde contre les champs transient.

Si la valeur par défaut (null pour les références, 0 pour les primitives) ne leur est pas acceptable après la désérialisation, vous devez fournir votre version de readObject et l'initialiser à cet endroit.

    private void readObject (
            final ObjectInputStream s
        ) throws
            ClassNotFoundException,
            IOException
    {
        s.defaultReadObject( );

        // derivedValue is still 0
        this.derivedValue = derivedValue( value );
    }
12