71 votes

Une classe abstraite doit-elle avoir un serialVersionUID ?

En Java, si une classe implémente Serializable mais est abstraite, doit-elle avoir un serialVersionUID long déclaré, ou les sous-classes n'ont-elles besoin que de cela ?

Dans ce cas, il est en effet prévu que toutes les sous-classes traitent de la sérialisation puisque le but du type est d'être utilisé dans les appels RMI.

3 votes

Je commence toujours à écrire une réponse, puis je réalise que je ne sais pas vraiment, même si j'ai une intuition. +1 pour une question à laquelle je ne peux pas répondre.

52voto

Bill the Lizard Points 147311

Le serialVersionUID est fourni pour déterminer la compatibilité entre un objet désarticulé et la version actuelle de la classe. En tant que telle, elle n'est pas vraiment nécessaire dans la première version d'une classe, ou dans ce cas, dans une classe de base abstraite. Vous n'aurez jamais une instance de cette classe abstraite à sérialiser/désérialiser, donc elle n'a pas besoin d'un serialVersionUID.

(Bien sûr, cela génère un avertissement du compilateur, dont vous voulez vous débarrasser, n'est-ce pas ?)

Il s'avère que le commentaire de James est correct. Le serialVersionUID d'une classe de base abstraite fait se propagent aux sous-classes. Compte tenu de cela, vous faire avez besoin du serialVersionUID dans votre classe de base.

Le code à tester :

import java.io.Serializable;

public abstract class Base implements Serializable {

    private int x = 0;
    private int y = 0;

    private static final long serialVersionUID = 1L;

    public String toString()
    {
        return "Base X: " + x + ", Base Y: " + y;
    }
}

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Sub extends Base {

    private int z = 0;

    private static final long serialVersionUID = 1000L;

    public String toString()
    {
        return super.toString() + ", Sub Z: " + z;
    }

    public static void main(String[] args)
    {
        Sub s1 = new Sub();
        System.out.println( s1.toString() );

        // Serialize the object and save it to a file
        try {
            FileOutputStream fout = new FileOutputStream("object.dat");
            ObjectOutputStream oos = new ObjectOutputStream(fout);
            oos.writeObject( s1 );
            oos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

        Sub s2 = null;
        // Load the file and deserialize the object
        try {
            FileInputStream fin = new FileInputStream("object.dat");
            ObjectInputStream ois = new ObjectInputStream(fin);
            s2 = (Sub) ois.readObject();
            ois.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println( s2.toString() );
    }
}

Exécutez le main in Sub une fois pour qu'il crée et enregistre un objet. Ensuite, changez le serialVersionUID dans la classe de base, commentez les lignes de main qui sauvegardent l'objet (pour qu'il ne le sauvegarde pas à nouveau, vous voulez juste charger l'ancien), et exécutez-le à nouveau. Il en résultera une exception

java.io.InvalidClassException: Base; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2

0 votes

Bonne réponse... @SuppressWarnings("serial") supprimera le message d'avertissement.

1 votes

@Ryan : Merci, mais je traite généralement les avertissements comme des erreurs et je les traite directement.

1 votes

...mais je comprends que tout le monde n'est pas aussi dogmatique que moi à ce sujet, donc votre commentaire est apprécié.

7voto

Tom Points 164

Oui, en général, pour la même raison que toute autre classe a besoin d'un identifiant de série - pour éviter qu'un identifiant soit généré pour elle. En fait, toute classe (et non interface) qui implémente la sérialisation doit définir un identifiant de version de série, sinon vous risquez des erreurs de désérialisation lorsque la même compilation .class n'est pas présente dans les JVM du serveur et du client.

Il existe d'autres options si vous cherchez à faire quelque chose de fantaisiste. Je ne suis pas sûr de ce que vous voulez dire par "c'est l'intention des sous classes...". Allez-vous écrire des méthodes de sérialisation personnalisées (par exemple, writeObject, readObject) ? Si oui, il existe d'autres options pour traiter avec une super classe.

voir : http://java.sun.com/javase/6/docs/api/java/io/Serializable.html

HTH Tom

3voto

Viktor Stolbin Points 1070

En fait, l'indication du lien de Tom est manquante. serialVersionID est en fait calculé par le runtime de sérialisation, c'est-à-dire pas pendant la compilation.

Si une classe sérialisable ne déclare pas explicitement un fichier serialVersionUID, le runtime de sérialisation calculera une valeur de valeur de serialVersionUID par défaut pour cette classe en fonction de divers aspects de la classe...

Cela rend les choses encore plus compliquées avec différentes versions de JRE.

2voto

Oliv Points 828

Conceptuellement, les données sérialisées ressemblent à ceci :

subClassData(className + version + fieldNames + fieldValues)
parentClassData(className + version + fieldNames + fieldValues)
... (up to the first parent, that implements Serializable)

Ainsi, lorsque vous désérialisez, une erreur de version dans l'une des classes de la hiérarchie entraîne l'échec de la désérialisation. Rien n'est stocké pour les interfaces, il n'est donc pas nécessaire de spécifier la version pour elles.

Donc la réponse est : oui, vous devez fournir serialVersionUID dans la classe abstraite de base, même si elle ne possède pas de champs : className + version est encore stocké.

Notez également ce qui suit :

  1. Si une classe ne possède pas un champ qui est rencontré dans les données sérialisées (un champ supprimé), elle est ignorée.
  2. Si une classe possède un champ qui n'est pas présent dans les données sérialisées (un nouveau champ), il est défini à 0/false/null. Il n'est pas fixé à la valeur par défaut comme on pourrait s'y attendre.
  3. Si un champ change de type, la valeur désérialisée doit pouvoir être affectée au nouveau type. Par exemple, si vous aviez un champ Object avec String en changeant le type de champ en String va réussir, mais le changer en Integer ne le fera pas. Cependant, le changement de champ de int a long ne fonctionnera pas, même si vous pouvez assigner int à la valeur long variable.
  4. Si une sous-classe ne prolonge plus la classe parent qu'elle prolonge dans les données sérialisées, elle est ignorée (comme dans le cas 1).
  5. Si une sous-classe étend maintenant une classe qui n'est pas trouvée dans les données sérialisées, les champs de la classe parent sont restaurés avec la valeur 0/false/nulle (comme dans le cas 2).

En d'autres termes, vous pouvez réorganiser les champs, les ajouter et les supprimer, et même modifier la hiérarchie des classes. Vous ne devez pas renommer les champs ou les classes (l'opération n'échouera pas, mais elle sera traitée comme si ce champ avait été supprimé et ajouté). Vous ne pouvez pas modifier le type des champs avec un type primitif, et vous pouvez modifier les champs de type référence à condition que le nouveau type soit assignable à partir de toutes les valeurs sérialisées.

Remarque : si la classe de base n'implémente pas l'option Serializable et que seule la sous-classe le fait, alors les champs de la classe de base se comporteront comme transient .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X