une classe abstraite doit avoir un serialVersionUID
-
23-08-2019 - |
Question
En java, si une classe implémente Serializable mais est abstraite, devrait-il avoir un serialVersionUID longtemps déclaré, ou faire les sous-classes exigent seulement?
Dans ce cas, il est en effet l'intention que toutes les sous-classes traitent sérialisation comme le but du type doit être utilisé dans les appels RMI.
La solution
Le serialVersionUID est prévu pour déterminer la compatibilité entre un objet deseralized et la version actuelle de la classe. En tant que tel, il est pas vraiment nécessaire dans la première version d'une classe, ou dans ce cas, dans une classe de base abstraite. Vous aurez jamais une instance de cette classe abstraite à linéariser / désérialiser, donc il n'a pas besoin de serialVersionUID.
(Bien sûr, il ne génère un avertissement du compilateur, que vous voulez vous débarrasser de, non?)
Il se trouve le commentaire de James est correct. Le serialVersionUID d'une classe de base abstraite ne se propager aux sous-classes. À la lumière de cela, vous ne besoin de la 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écuter le principal sous une fois pour l'obtenir pour créer et enregistrer un objet. Puis changez la serialVersionUID dans la classe de base, commentez les lignes principales qui économisent l'objet (il ne sauve pas encore, vous voulez juste charger l'ancien), et l'exécuter à nouveau. Cela se traduira par une exception
java.io.InvalidClassException: Base; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
Autres conseils
Oui, en général, pour la même raison que toute autre classe a besoin d'un identifiant de série - pour éviter un être généré. Fondamentalement, toute catégorie (pas d'interface) qui implémente sérialisable doit définir id version de série ou vous risquez des erreurs désérialisation lorsque la même compilation .class est pas dans les JVMs serveur et client.
Il y a d'autres options si vous essayez de faire quelque chose de fantaisie. Je ne sais pas ce que vous entendez par « il est dans l'intention des sous-classes ... ». Est-ce que vous allez écrire des méthodes de sérialisation personnalisées (par exemple. WriteObject, readObject)? Dans ce cas, il existe d'autres options pour faire face à une super-classe.
voir: http://java.sun.com/javase/6 /docs/api/java/io/Serializable.html
HTH Tom
En fait, pointant sur le lien de Tom si elle manque serialVersionID
est effectivement calculé par sérialisation d'exécution lors de la compilation à savoir pas
Si une classe sérialisable ne déclare pas explicitement serialVersionUID, l'environnement d'exécution de sérialisation calcule un valeur par défaut serialVersionUID pour cette classe en fonction de divers aspects de la classe ...
Cela rend les choses encore plus compliquées ayant différentes versions de JRE.
Conceptuellement, les données sérialisés 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érialiser, dans la version non-concordance dans l'une des classes dans la hiérarchie provoque la désérialisation à l'échec. Rien n'est stocké pour les interfaces, il n'y a donc pas besoin de spécifier la version pour eux.
Réponse: oui, vous avez besoin de fournir serialVersionUID
dans la classe abstraite de base aussi bien. Même si elle ne possède pas de champs (className + version
sont stockés même s'il n'y a pas de champs).
A noter également les éléments suivants:
- Si la classe ne dispose pas d'un champ, qui se trouve dans les données sérialisés (un champ supprimé), il est ignoré.
- Si la classe a un champ, qui ne figure pas dans les données sérialisés (un nouveau champ), il est réglé sur 0 / faux / null (non à la valeur par défaut comme on pouvait s'y attendre).
- si un champ change type de données, la valeur désérialisée doit être cessible au nouveau type. Par exemple. si vous aviez un champ
Object
avec une valeurString
, en changeant le type de champ àString
réussira, mais en changeant deInteger
ne sera pas. Cependant, le changement de champint
àlong
ne fonctionnera pas, même si vous pouvez attribuer une valeur deint
àlong
variable. - Si la sous-classe ne prolonge plus la classe parente, qu'elle prolonge dans les données sérialisés, il est ignoré (comme dans le cas 1).
- Si une sous-classe étend maintenant une classe, qui ne se trouve pas dans les données sérialisés, les champs de classe parente sont restaurés avec 0 / valeur false / null (comme dans le cas 2).
En mots simples: vous pouvez modifier l'ordre des champs, ajouter et les supprimer, changer même la hiérarchie des classes. Vous ne devriez pas renommer des champs ou des classes (il ne manquera pas, mais les valeurs ne seront pas désérialisée). Vous ne pouvez pas modifier le type de champs de type primitif, et vous pouvez modifier les champs de type de référence à condition que le nouveau type est cessible de toutes les valeurs.
. Remarque: si la classe de base ne met pas en œuvre Serializable et seule la sous-classe ne puis champs de la classe de base se comporterait comme transient