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