web-dev-qa-db-fra.com

Une classe abstraite doit-elle avoir un serialVersionUID

En Java, si une classe implémente Serializable mais est abstraite, doit-elle avoir un numéro de série longVersionUID déclaré, ou les sous-classes l'exigent-elles seulement?

Dans ce cas, l'intention est effectivement que toutes les sous-classes traitent de la sérialisation, l'objectif du type devant être utilisé dans les appels RMI.

60
Yishai

SerialVersionUID est fourni pour déterminer la compatibilité entre un objet déseralisé et la version actuelle de la classe. En tant que tel, il 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 d'instance de cette classe abstraite à sérialiser/désérialiser, elle n'aura donc pas besoin d'un serialVersionUID.

(Bien sûr, cela génère un avertissement du compilateur, que vous voulez supprimer, non?)

Il s'avère que le commentaire de James est correct. Le serialVersionUID d'une classe de base abstraite does get est propagé aux sous-classes. A la lumière de cela, vous do avez besoin de 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 programme principal dans Sub une fois pour le faire créer et enregistrer un objet. Modifiez ensuite le serialVersionUID dans la classe de base, mettez en commentaire les lignes principales qui sauvegardent l'objet (pour qu'il ne l'enregistre pas à nouveau, vous souhaitez simplement charger l'ancien), puis exécutez-le à nouveau. Cela entraînera une exception

Java.io.InvalidClassException: Base; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
43
Bill the Lizard

Oui, en général, pour la même raison qu'une autre classe a besoin d'un identifiant de série - pour éviter d'en générer un. Fondamentalement, toute classe (et non pas interface) qui implémente sérialisable doit définir l'ID de version série ou vous risquez des erreurs de désérialisation lorsque la même compilation .class n'est pas dans les JVM du serveur et du client.

Il existe d'autres options si vous essayez de faire quelque chose d'extraordinaire. Je ne suis pas sûr de comprendre ce que vous entendez par "c’est l’intention des sous-classes ...". Allez-vous écrire des méthodes de sérialisation personnalisées (par exemple, writeObject, readObject)? Si c'est le cas, il existe d'autres options pour traiter avec une classe supérieure.

voir: http://Java.Sun.com/javase/6/docs/api/Java/io/Serializable.html

HTH Tom

6
Tom

En fait, le fait de faire remarquer que la variable serialVersionID manque dans le lien de Tom est en fait calculé par la sérialisation, c’est-à-dire pas pendant la compilation.

Si une classe sérialisable ne déclare pas explicitement un SerialVersionUID, l'exécution de la sérialisation calculera alors une valeur Par défaut serialVersionUID 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.

2
Viktor Stolbin

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 non-concordance 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 une version pour celles-ci.

Réponse: oui, vous devez également fournir serialVersionUID dans la classe abstraite de base. Même s'il n'y a pas de champs (className + version sont stockés même s'il n'y a pas de champs).

Notez également les éléments suivants:

  1. Si la classe n'a pas de champ trouvé dans les données sérialisées (un champ supprimé), il est ignoré.
  2. Si la classe a un champ qui n'est pas présent dans les données sérialisées (un nouveau champ), il est défini sur 0/false/null (pas avec la valeur par défaut comme on pourrait s'y attendre).
  3. si un champ change de type de données, la valeur désérialisée doit être assignable au nouveau type. Par exemple. Si vous aviez un champ Object avec une valeur String, changer le type de champ en String réussira, mais passer à Integer ne le fera pas. Cependant, changer le champ de int à long ne fonctionnera pas, même si vous pouvez affecter la valeur int à la variable long.
  4. Si la sous-classe n'étend plus la classe parente, qu'elle étend 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, introuvable dans les données sérialisées, les champs de la classe mère sont restaurés avec la valeur 0/false/null (comme dans le cas 2).

En termes simples: vous pouvez réorganiser, ajouter et supprimer des champs, voire modifier la hiérarchie des classes. Vous ne devez pas renommer les champs ou les classes (cela n'échouera pas, mais les valeurs ne seront pas désérialisées). Vous ne pouvez pas changer le type de champs avec le type primitif et vous pouvez modifier les champs de type référence si le nouveau type est assignable à partir de toutes les valeurs.

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

0
Oliv