Qu'est-ce qu'un serialVersionUID et pourquoi devrais-je l'utiliser?

2999

Eclipse émet des avertissements quand un serialVersionUIDest manquant.

La classe sérialisable Foo ne déclare pas un champ serialVersionUID final statique de type long

Qu'est-ce que c'est serialVersionUIDet pourquoi est-ce important? Veuillez montrer un exemple où l'absence serialVersionUIDentraînera un problème.

ashokgelal
la source
1
Trouvez une bonne pratique sur serialversionUID; dzone.com/articles/what-is-serialversionuid
ceyun

Réponses:

2290

Les documents pour java.io.Serializablesont probablement une aussi bonne explication que vous obtiendrez:

Le runtime de sérialisation associe à chaque classe sérialisable un numéro de version, appelé a serialVersionUID, qui est utilisé lors de la désérialisation pour vérifier que l'expéditeur et le récepteur d'un objet sérialisé ont chargé des classes pour cet objet qui sont compatibles par rapport à la sérialisation. Si le récepteur a chargé une classe pour l'objet qui a une classe différente serialVersionUIDde celle de la classe de l'expéditeur correspondant, la désérialisation entraînera un InvalidClassException. Une classe sérialisable peut déclarer la sienne serialVersionUIDexplicitement en déclarant un champ nommé serialVersionUIDqui doit être statique, final et de type long:

ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;

Si une classe sérialisable ne déclare pas explicitement un serialVersionUID, le runtime de sérialisation calculera une par défaut enserialVersionUID valeur pour cette classe en fonction de divers aspects de la classe, comme décrit dans la spécification de sérialisation d'objet Java (TM). Cependant, il est fortement recommandé que toutes les classes sérialisables déclarent explicitement des serialVersionUIDvaleurs, car le serialVersionUIDcalcul par défaut est très sensible aux détails de classe qui peuvent varier selon les implémentations du compilateur, et peuvent ainsi entraîner des imprévus InvalidClassExceptionspendant la désérialisation. Par conséquent, pour garantir une serialVersionUIDvaleur cohérente entre les différentes implémentations du compilateur java, une classe sérialisable doit déclarer une serialVersionUIDvaleur explicite . Il est également fortement conseilléserialVersionUIDles déclarations utilisent le modificateur privé dans la mesure du possible, car ces déclarations ne s'appliquent qu'aux serialVersionUIDchamps de classe immédiatement déclarants qui ne sont pas utiles en tant que membres hérités.

Jon Skeet
la source
326
Donc, ce que vous dites essentiellement, c'est que si un utilisateur ne comprend pas tout le matériel ci-dessus, cet utilisateur ne devrait pas se soucier de la sérialisation? Je crois que vous avez répondu "comment?" plutôt que d'expliquer le "pourquoi?". Pour ma part, je ne comprends pas pourquoi je m'embête avec SerializableVersionUID.
Ziggy
366
Le pourquoi est dans le deuxième paragraphe: si vous ne spécifiez pas explicitement serialVersionUID, une valeur est générée automatiquement - mais c'est fragile car cela dépend de l'implémentation du compilateur.
Jon Skeet
14
Et pourquoi Eclipse dit-il que j'ai besoin de "private static final serialVersionUID = 1L;" quand j'étends la classe Exception?
JohnMerlino
21
@JohnMerlino: Eh bien, je ne m'attendrais pas à ce que vous en ayez besoin - mais il peut en suggérer une afin de vous aider à sérialiser correctement les exceptions. Si vous n'allez pas les sérialiser, vous n'avez vraiment pas besoin de la constante.
Jon Skeet
16
@JohnMerlino, pour répondre à la question de savoir pourquoi une partie de vous: Exception implémente Serializable et eclipse avertit que vous n'avez pas défini un serialVersionUID, ce qui serait une bonne idée (si vous ne voulez pas sérialiser la classe) pour éviter les problèmes que la publication de JonSkeet grandes lignes.
zpon
475

Si vous sérialisez simplement parce que vous devez sérialiser pour l'implémentation (peu importe si vous sérialisez pour un HTTPSession, par exemple ... s'il est stocké ou non, vous ne vous souciez probablement pas d' de-serializingun objet de formulaire), alors vous pouvez ignorer cela.

Si vous utilisez réellement la sérialisation, cela n'a d'importance que si vous prévoyez de stocker et de récupérer des objets directement à l'aide de la sérialisation. leserialVersionUID représente la version de votre classe et vous devez l'incrémenter si la version actuelle de votre classe n'est pas rétrocompatible avec sa version précédente.

La plupart du temps, vous n'utiliserez probablement pas directement la sérialisation. Si tel est le cas, générez une valeur SerialVersionUIDpar défaut en cliquant sur l'option de correction rapide et ne vous en faites pas.

MetroidFan2002
la source
78
Je dirais que si vous n'utilisez pas la sérialisation pour le stockage permanent, vous devez utiliser @SuppressWarnings plutôt que d'ajouter une valeur. Il encombre moins la classe et préserve l'abiité du mécanisme serialVersionUID pour vous protéger contre les changements incompatibles.
Tom Anderson
25
Je ne vois pas comment l'ajout d'une ligne (annotation @SuppressWarnings) par opposition à une autre ligne (identifiant sérialisable) "encombre moins la classe". Et si vous n'utilisez pas la sérialisation pour le stockage permanent, pourquoi ne pas simplement utiliser "1"? De toute façon, vous ne vous soucieriez pas de l'ID généré automatiquement dans ce cas.
MetroidFan2002
71
@ MetroidFan2002: Je pense que le point de @ TomAnderson de serialVersionUIDvous protéger contre des changements incompatibles est valide. L'utilisation de @SuppressWarningsdocuments est meilleure si vous ne souhaitez pas utiliser la classe pour un stockage permanent.
AbdullahC
11
"Vous devez l'incrémenter si la version actuelle de votre classe n'est pas rétrocompatible avec sa version précédente:" Vous devez d'abord explorer la prise en charge étendue de la gestion des versions des objets de la sérialisation, (a) pour vous assurer que la classe est vraiment désormais incompatible avec la sérialisation, qui selon la spécification est assez difficile à réaliser; (b) pour essayer un schéma tel que les méthodes read / writeObject () personnalisées, les méthodes readResolve / writeReplace (), les déclarations serializableFields, etc., pour s'assurer que le flux reste compatible. Changer le réel serialVersionUIDest un dernier recours, un conseil du désespoir.
Marquis de Lorne
4
L'incrément @EJP de serialVersionUID entre en jeu lorsque l'auteur initial de la classe a introduit explicitement. Je dirais que l'ID de série généré par jvm devrait être correct. c'est la meilleure réponse que j'ai vue sur la sérialisation.
suréchange
307

Je ne peux pas laisser passer cette occasion de brancher le livre de Josh Bloch Effective Java (2nd Edition). Le chapitre 11 est une ressource indispensable sur la sérialisation Java.

Selon Josh, l'UID généré automatiquement est généré en fonction d'un nom de classe, d'interfaces implémentées et de tous les membres publics et protégés. La modification de l'un de ces éléments de quelque manière que ce soit modifiera le serialVersionUID. Ainsi, vous n'avez pas besoin de les manipuler uniquement si vous êtes certain qu'aucune version de la classe ne sera jamais sérialisée (soit à travers les processus, soit récupérée du stockage ultérieurement).

Si vous les ignorez pour l'instant et constatez plus tard que vous devez modifier la classe d'une manière ou d'une autre tout en conservant la compatibilité avec l'ancienne version de la classe, vous pouvez utiliser l'outil serialver JDK pour générer la serialVersionUIDsur l' ancienne classe et définir explicitement sur la nouvelle classe. (Selon vos modifications, vous devrez peut-être également implémenter la sérialisation personnalisée en ajoutant des méthodes writeObjectet readObject- voir Serializablejavadoc ou le chapitre 11 susmentionné.)

Scott Bale
la source
33
Donc, on pourrait s'embêter avec SerializableVersionUID si l'on était préoccupé par la compatibilité avec les anciennes versions d'une classe?
Ziggy
10
Oui, dans le cas où si la nouvelle version change un membre public en protégé, le SerializableVersionUID par défaut sera différent et lèvera une InvalidClassExceptions.
Chander Shivdasani
3
Nom de classe, interfaces implémentées, toutes les méthodes publiques et protégées, TOUTES les variables d'instance.
Achow
31
Il convient de noter que Joshua Bloch conseille de spécifier pour chaque classe sérialisable l'uid de la version série. Citation du chapitre 11: Quelle que soit la forme sérialisée que vous choisissez, déclarez un UID de version série explicite dans chaque classe sérialisable que vous écrivez. Cela élimine l'UID de la version série en tant que source potentielle d'incompatibilité (article 74). Il existe également un petit avantage en termes de performances. Si aucun UID de version série n'est fourni, un calcul coûteux est nécessaire pour en générer un au moment de l'exécution.
Ashutosh Jindal
136

Vous pouvez dire à Eclipse d'ignorer ces avertissements serialVersionUID:

Fenêtre> Préférences> Java> Compilateur> Erreurs / Avertissements> Problèmes potentiels de programmation

Au cas où vous ne le sauriez pas, il y a beaucoup d'autres avertissements que vous pouvez activer dans cette section (ou même avoir certains signalés comme des erreurs), beaucoup sont très utiles:

  • Problèmes de programmation potentiels: affectation booléenne accidentelle possible
  • Problèmes de programmation potentiels: accès au pointeur nul
  • Code inutile: la variable locale n'est jamais lue
  • Code inutile: contrôle nul redondant
  • Code inutile: transtypage inutile ou 'instanceof'

et beaucoup plus.

mat b
la source
21
vote positif, mais uniquement parce que l'affiche originale ne semble rien sérialiser. Si l'affiche disait "Je sérialise cette chose et ...", vous obtiendrez alors un vote négatif: P
John Gardner
3
Vrai - je supposais que l'interrogateur ne voulait tout simplement pas être averti
mat
14
@Gardner -> d'accord! Mais le questionneur veut aussi savoir pourquoi il ne voudrait peut-être pas être prévenu.
Ziggy
8
Le questionneur se soucie évidemment de la raison pour laquelle il devrait y avoir un UID. Il suffit donc de lui dire d'ignorer l'avertissement.
cinqS
111

serialVersionUIDfacilite la version des données sérialisées. Sa valeur est stockée avec les données lors de la sérialisation. Lors de la désérialisation, la même version est vérifiée pour voir comment les données sérialisées correspondent au code actuel.

Si vous souhaitez mettre à jour vos données, vous commencez normalement par un serialVersionUIDde 0 et vous le remplacez à chaque modification structurelle de votre classe qui modifie les données sérialisées (ajout ou suppression de champs non transitoires).

Le mécanisme de désérialisation intégré ( in.defaultReadObject()) refusera de désérialiser des anciennes versions des données. Mais si vous le souhaitez, vous pouvez définir votre propre fonction readObject () qui peut relire les anciennes données. Ce code personnalisé peut ensuite vérifier le serialVersionUIDafin de savoir dans quelle version les données se trouvent et décider comment les désérialiser. Cette technique de version est utile si vous stockez des données sérialisées qui survivent à plusieurs versions de votre code.

Mais le stockage de données sérialisées sur une période aussi longue n'est pas très courant. Il est beaucoup plus courant d'utiliser le mécanisme de sérialisation pour écrire temporairement des données dans un cache, par exemple, ou les envoyer sur le réseau à un autre programme avec la même version des parties pertinentes de la base de code.

Dans ce cas, vous ne souhaitez pas conserver la compatibilité descendante. Vous êtes uniquement soucieux de vous assurer que les bases de code qui communiquent ont bien les mêmes versions des classes pertinentes. Afin de faciliter une telle vérification, vous devez conserver le serialVersionUIDmême que précédemment et ne pas oublier de le mettre à jour lorsque vous apportez des modifications à vos classes.

Si vous oubliez de mettre à jour le champ, vous pourriez vous retrouver avec deux versions différentes d'une classe avec une structure différente mais avec la même serialVersionUID. Si cela se produit, le mécanisme par défaut ( in.defaultReadObject()) ne détectera aucune différence et tentera de désérialiser les données incompatibles. Vous pouvez maintenant vous retrouver avec une erreur d'exécution cryptique ou une panne silencieuse (champs nuls). Ces types d'erreurs peuvent être difficiles à trouver.

Donc, pour aider à ce cas d'utilisation, la plate-forme Java vous offre le choix de ne pas définir serialVersionUIDmanuellement. Au lieu de cela, un hachage de la structure de classe sera généré au moment de la compilation et utilisé comme id. Ce mécanisme s'assurera que vous n'avez jamais différentes structures de classe avec le même identifiant et vous n'obtiendrez donc pas les échecs de sérialisation d'exécution difficiles à retracer mentionnés ci-dessus.

Mais il y a un revers à la stratégie d'identification générée automatiquement. A savoir que les identifiants générés pour la même classe peuvent différer entre les compilateurs (comme mentionné par Jon Skeet ci-dessus). Donc, si vous communiquez des données sérialisées entre du code compilé avec différents compilateurs, il est recommandé de conserver les identifiants manuellement de toute façon.

Et si vous êtes rétrocompatible avec vos données comme dans le premier cas d'utilisation mentionné, vous souhaiterez probablement également conserver l'ID vous-même. Ceci afin d'obtenir des identifiants lisibles et d'avoir un meilleur contrôle sur quand et comment ils changent.

Alexander Torstling
la source
4
L'ajout ou la suppression de champs non transitoires ne rend pas la sérialisation incompatible avec la classe. Il n'y a donc aucune raison de «renoncer» à de tels changements.
Marquis de Lorne
4
@EJP: Hein? L'ajout de données modifie définitivement les données de sérialisation dans mon monde.
Alexander Torstling
3
@AlexanderTorstling Lisez ce que j'ai écrit. Je n'ai pas dit que cela ne «modifiait pas les données de sérialisation». J'ai dit que cela ne rend pas la sérialisation incompatible avec la classe. Ce n'est pas la même chose. Vous devez lire le chapitre Versioning de la spécification de sérialisation d'objet.
Marquis de Lorne
3
@EJP: Je me rends compte que l'ajout d'un champ non transitoire ne signifie pas nécessairement que vous rendez la classe incompatible avec la sérialisation, mais c'est un changement structurel qui modifie les données sérialisées et vous voulez généralement augmenter la version lorsque vous le faites, sauf si vous gérer la compatibilité descendante, que j'ai également expliqué plus tard dans le post. À quoi voulez-vous en venir exactement?
Alexander Torstling
4
Mon argument reste exactement ce que j'ai dit. L'ajout ou la suppression de champs non transitoires ne rend pas la classe Serialization incompatible. Vous n'avez donc pas besoin de bump le serialVersionUID à chaque fois que vous le faites.
Marquis de Lorne
72

Qu'est-ce qu'un serialVersionUID et pourquoi devrais-je l'utiliser?

SerialVersionUIDest un identifiant unique pour chaque classe, l' JVMutilise pour comparer les versions de la classe en s'assurant que la même classe a été utilisée pendant la sérialisation est chargée pendant la désérialisation.

En spécifier un donne plus de contrôle, bien que JVM en génère un si vous ne le spécifiez pas. La valeur générée peut différer entre différents compilateurs. De plus, parfois, vous voulez simplement pour une raison quelconque interdire la désérialisation des anciens objets sérialisés [ backward incompatibility], et dans ce cas, il vous suffit de changer le serialVersionUID.

Les javadocsSerializable disent :

le calcul serialVersionUID par défaut est très sensible aux détails de classe qui peuvent varier en fonction des implémentations du compilateur, et peuvent ainsi entraîner des InvalidClassExceptions inattendus pendant la désérialisation.

Par conséquent, vous devez déclarer serialVersionUID car cela nous donne plus de contrôle .

Cet article contient de bons points sur le sujet.

Thalaivar
la source
3
@Vinothbabu mais serialVersionUID est statique, les variables statiques ne peuvent donc pas être sérialisées. alors comment se fait-il que jvm vérifie la version, sans savoir quelle est la version de l'objet de désérialisation
Kranthi Sama
3
La seule chose non mentionnée dans cette réponse est que vous pouvez provoquer des conséquences inattendues en incluant aveuglément serialVersionUIDsans savoir pourquoi. Le commentaire de Tom Anderson sur la réponse de MetroidFan2002 répond à ceci: "Je dirais que si vous n'utilisez pas la sérialisation pour le stockage permanent, vous devriez utiliser @SuppressWarnings plutôt que d'ajouter une valeur. Il encombre moins la classe et préserve la capacité de la mécanisme serialVersionUID pour vous protéger contre les modifications incompatibles. "
Kirby
7
serialVersionUIDn'est pas un «identifiant unique pour chaque classe». Le nom de classe complet est celui-là. Il s'agit d'un indicateur de version .
Marquis de Lorne
58

La question initiale a demandé «pourquoi est-ce important» et «exemple» où cela Serial Version IDserait utile. Eh bien, j'en ai trouvé un.

Supposons que vous créez une Carclasse, l'instanciez et l'écrivez dans un flux d'objets. L'objet de voiture aplati reste dans le système de fichiers pendant un certain temps. En attendant, si la Carclasse est modifiée en ajoutant un nouveau champ. Plus tard, lorsque vous essayez de lire (c'est-à-dire désérialiser) l' Carobjet aplati , vous obtenez le java.io.InvalidClassException- car toutes les classes sérialisables reçoivent automatiquement un identifiant unique. Cette exception est levée lorsque l'identifiant de la classe n'est pas égal à l'identifiant de l'objet aplati. Si vous y pensez vraiment, l'exception est levée en raison de l'ajout du nouveau champ. Vous pouvez éviter que cette exception ne soit levée en contrôlant vous-même la gestion des versions en déclarant un serialVersionUID explicite. Il y a aussi un petit avantage de performance à déclarer explicitement votreserialVersionUID(car ne doit pas être calculé). Par conséquent, il est recommandé d'ajouter votre propre serialVersionUID à vos classes sérialisables dès que vous les créez, comme indiqué ci-dessous:

public class Car {
    static final long serialVersionUID = 1L; //assign a long value
}
Rupesh
la source
Et il faut attribuer un nombre long aléatoire, pas 1L, à chaque UID.
abbas
4
@abbas 'On devrait' faire ça pourquoi? Veuillez expliquer quelle différence cela fait.
Marquis de Lorne
Comme son nom l'indique, il représente la version de la classe qui a été utilisée pour sérialiser l'objet. Si vous lui attribuez le même numéro à chaque fois, vous ne pourrez pas trouver la bonne classe pour la désérialiser. Donc, chaque fois que vous effectuez un changement dans la classe, il est préférable de changer la version aussi.
abbas
2
@abbas, cette intention ne s'oppose pas à l'incrémentation de nombres naturels à partir de 1etc.
Vadzim
2
@BillK, je pensais que la vérification de la sérialisation est liée à la paire nom de classe et serialVersionUID. Ainsi, différents schémas de numérotation de différentes classes et bibliothèques ne peuvent en aucun cas interférer. Ou avez-vous impliqué des bibliothèques génératrices de code?
Vadzim
44

Je dois d'abord expliquer ce qu'est la sérialisation.

La sérialisation permet de convertir un objet en un flux, pour envoyer cet objet sur le réseau OU Enregistrer dans un fichier OU enregistrer dans une base de données pour une utilisation de lettre.

Il existe certaines règles de sérialisation .

  • Un objet n'est sérialisable que si sa classe ou sa superclasse implémente l'interface Sérialisable

  • Un objet est sérialisable (implémente lui-même l'interface sérialisable) même si sa superclasse ne l'est pas. Cependant, la première superclasse de la hiérarchie de la classe sérialisable, qui n'implémente pas l'interface sérialisable, DOIT avoir un constructeur sans argument. En cas de violation, readObject () produira une exception java.io.InvalidClassException lors de l'exécution

  • Tous les types primitifs sont sérialisables.

  • Les champs transitoires (avec modificateur transitoire) ne sont PAS sérialisés (c'est-à-dire qu'ils ne sont ni enregistrés ni restaurés). Une classe qui implémente Serializable doit marquer les champs transitoires des classes qui ne prennent pas en charge la sérialisation (par exemple, un flux de fichiers).

  • Les champs statiques (avec modificateur statique) ne sont pas sérialisés.

Lorsque Objectest sérialisé, Java Runtime associe le numéro de version de série, le serialVersionID.

Où nous avons besoin de serialVersionID: pendant la désérialisation pour vérifier que l'expéditeur et le récepteur sont compatibles en ce qui concerne la sérialisation. Si le récepteur charge la classe avec un autre, laserialVersionIDdésérialisation se termine avecInvalidClassCastException.
Une classe sérialisable peut déclarer sa propreserialVersionUIDexplicite en déclarant un champ nomméserialVersionUIDqui doit être statique, final et de type long.

Essayons cela avec un exemple.

import java.io.Serializable;    
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
private String empname;
private byte empage;

public String getEmpName() {
    return name;
}
public void setEmpName(String empname) {
    this.empname = empname;
}
public byte getEmpAge() {
    return empage;
}
public void setEmpAge(byte empage) {
    this.empage = empage;
}

public String whoIsThis() {
    StringBuffer employee = new StringBuffer();
    employee.append(getEmpName()).append(" is ).append(getEmpAge()).append("
years old  "));
    return employee.toString();
}
}

Créer un objet sérialiser

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class Writer {
public static void main(String[] args) throws IOException {
    Employee employee = new Employee();
    employee.setEmpName("Jagdish");
    employee.setEmpAge((byte) 30);

    FileOutputStream fout = new 
FileOutputStream("/users/Jagdish.vala/employee.obj");
    ObjectOutputStream oos = new ObjectOutputStream(fout);
    oos.writeObject(employee);
    oos.close();
    System.out.println("Process complete");
}
}

Désérialiser l'objet

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class Reader {
public static void main(String[] args) throws ClassNotFoundException, 
IOException {
    Employee employee = new Employee();
    FileInputStream fin = new 
    FileInputStream("/users/Jagdish.vala/employee.obj");
    ObjectInputStream ois = new ObjectInputStream(fin);
    employee = (Employee) ois.readObject();
    ois.close();
    System.out.println(employee.whoIsThis());
 }
}    

REMARQUE: modifiez maintenant le serialVersionUID de la classe Employee et enregistrez:

private static final long serialVersionUID = 4L;

Et exécutez la classe Reader. Ne pas exécuter la classe Writer et vous obtiendrez l'exception.

Exception in thread "main" java.io.InvalidClassException: 
com.jagdish.vala.java.serialVersion.Employee; local class incompatible: 
stream classdesc serialVersionUID = 1, local class serialVersionUID = 4
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1623)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1518)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1774)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
at com.krishantha.sample.java.serialVersion.Reader.main(Reader.java:14)
JegsVala
la source
Corrigez-moi si je me trompe - la classe locale est celle que vous avez actuellement / utilisez dans le chemin de classe et le flux est celui utilisé par une autre partie (par exemple, un serveur qui vous renvoie une réponse et a sérialisé le réponse). Vous pouvez rencontrer cette situation lorsque vous communiquez avec un serveur qui a mis à jour ses bibliothèques tierces, mais vous (le client) ne l'avez pas fait.
Victor
37

Si vous n'aurez jamais besoin de sérialiser vos objets en tableau d'octets et de les envoyer / stocker, alors vous n'avez pas à vous en préoccuper. Si vous le faites, vous devez alors considérer votre serialVersionUID car le désérialiseur de l'objet le fera correspondre à la version de l'objet que possède son chargeur de classe. En savoir plus à ce sujet dans la spécification du langage Java.

eishay
la source
10
Si vous n'allez pas sérialiser les objets, pourquoi sont-ils sérialisables?
erickson
7
@erickson - la classe parente peut être sérialisable, disons, ArrayList, mais vous voulez que votre propre objet (disons, une liste de tableaux modifiée) l'utilise comme base mais ne va jamais sérialiser la collection que vous créez.
MetroidFan2002
5
Il n'est mentionné nulle part dans les spécifications du langage Java. Il est mentionné dans la spécification de version d'objet.
Marquis de Lorne
4
Voici le lien vers la spécification de version de l' objet Java 8 .
Basil Bourque
34

Si vous obtenez cet avertissement sur une classe, vous ne pensez jamais à la sérialisation et que vous ne vous êtes pas déclaré implements Serializable , c'est souvent parce que vous avez hérité d'une superclasse, qui implémente Serializable. Souvent, il serait préférable de déléguer à un tel objet au lieu d'utiliser l'héritage.

Donc, au lieu de

public class MyExample extends ArrayList<String> {

    public MyExample() {
        super();
    }
    ...
}

faire

public class MyExample {
    private List<String> myList;

    public MyExample() {
         this.myList = new ArrayList<String>();
    }
    ...
}

et dans les méthodes pertinentes, appelez myList.foo()au lieu de this.foo()(ou super.foo()). (Cela ne convient pas dans tous les cas, mais toujours assez souvent.)

Je vois souvent des gens étendre JFrame ou autre, alors qu'ils n'ont vraiment besoin que de déléguer cela. (Cela aide également à l'auto-complétion dans un IDE, car JFrame a des centaines de méthodes, dont vous n'avez pas besoin lorsque vous voulez appeler vos méthodes personnalisées dans votre classe.)

Un cas où l'avertissement (ou le serialVersionUID) est inévitable est lorsque vous étendez depuis AbstractAction, normalement dans une classe anonyme, en ajoutant uniquement la méthode actionPerformed. Je pense qu'il ne devrait pas y avoir d'avertissement dans ce cas (puisque vous ne pouvez normalement pas sérialiser et désérialiser de telles classes anonymes de toute façon sur différentes versions de votre classe), mais je ne sais pas comment le compilateur pourrait reconnaître cela.

Paŭlo Ebermann
la source
4
Je pense que vous avez raison de dire que la composition plutôt que l'héritage a plus de sens, en particulier lorsque vous discutez de classes telles que ArrayList. Cependant, de nombreux frameworks exigent que les utilisateurs s'étendent à partir de super-classes abstraites qui sont sérialisables (telles que la classe ActionForm de Struts 1.2 ou ExtensionFunctionDefinition de Saxon), auquel cas cette solution est irréalisable. Je pense que vous avez raison, ce serait bien si l'avertissement était ignoré dans certains cas (comme si vous étiez à partir d'une classe sérialisable abstraite)
piepera
2
Sûrement, si vous ajoutez une classe en tant que membre, plutôt que d'en hériter, vous devrez écrire une méthode wrapper pour CHAQUE méthode de la classe membre que vous souhaitez utiliser, ce qui la rendrait irréalisable dans un grand nombre de situations. sauf si java a une fonction similaire à celle de perl __AUTOLOAD, que je ne connais pas.
M_M
4
@M_M: Lorsque vous déléguez de nombreuses méthodes à votre objet encapsulé, bien sûr, il ne serait pas approprié d'utiliser la délégation. Mais je suppose que ce cas est le signe d'une erreur de conception - les utilisateurs de votre classe (par exemple "MainGui") ne devraient pas avoir besoin d'appeler beaucoup de méthodes de l'objet encapsulé (par exemple JFrame).
Paŭlo Ebermann
1
Ce que je n'aime pas dans la délégation, c'est la nécessité de faire référence au délégué. Et chaque référence signifie plus de mémoire. Corrige moi si je me trompe. Si j'ai besoin d'une CustomizedArrayList de 100 objets, cela n'aurait pas beaucoup d'importance, mais si j'ai besoin de centaines de CustomizdeArrayLists de quelques objets, l'utilisation de la mémoire augmente considérablement.
WVrock
2
Throwable est sérialisable, et seul Throwable est lancable, il n'est donc pas possible de définir une exception qui n'est pas sérialisable. La délégation n'est pas possible.
Phil
22

Pour comprendre la signification du champ serialVersionUID, il faut comprendre comment fonctionne la sérialisation / désérialisation.

Lorsqu'un objet de classe Serializable est sérialisé, Java Runtime associe un numéro de version série (appelé serialVersionUID) à cet objet sérialisé. Au moment où vous désérialisez cet objet sérialisé, Java Runtime fait correspondre le serialVersionUID de l'objet sérialisé avec le serialVersionUID de la classe. Si les deux sont égaux, alors seulement, il poursuit le processus de désérialisation, sinon, lance InvalidClassException.

Nous concluons donc que pour réussir le processus de sérialisation / désérialisation, le serialVersionUID de l'objet sérialisé doit être équivalent au serialVersionUID de la classe. Si le programmeur spécifie explicitement la valeur serialVersionUID dans le programme, la même valeur sera associée à l'objet sérialisé et à la classe, quelle que soit la plateforme de sérialisation et de désérialisation (par exemple, la sérialisation peut être effectuée sur une plateforme comme Windows en utilisant sun ou MS JVM et la désérialisation peuvent être sur une plate-forme Linux différente à l'aide de Zing JVM).

Mais dans le cas où serialVersionUID n'est pas spécifié par le programmeur, alors lors de la sérialisation \ désérialisation d'un objet, le runtime Java utilise son propre algorithme pour le calculer. Cet algorithme de calcul serialVersionUID varie d'un JRE à l'autre. Il est également possible que l'environnement dans lequel l'objet est sérialisé utilise un JRE (ex: SUN JVM) et l'environnement dans lequel la désérialisation se produit utilise Linux Jvm (zing). Dans de tels cas, serialVersionUID associé à un objet sérialisé sera différent du serialVersionUID de la classe calculé dans l'environnement de désérialisation. À son tour, la désérialisation ne réussira pas. Ainsi, pour éviter de telles situations / problèmes, le programmeur doit toujours spécifier serialVersionUID de la classe Serializable.

Nitesh Soni
la source
2
L'algorithme ne varie pas, mais il est légèrement sous-spécifié.
Marquis de Lorne
... l'algorithme ne varie pas, mais il est légèrement sous-spécifié ... ce qui signifie que tout jvm peut varier ..... @ user207421
AnthonyJClink
18

Ne vous embêtez pas, le calcul par défaut est vraiment bon et suffit pour 99,9999% des cas. Et si vous rencontrez des problèmes, vous pouvez - comme déjà indiqué - introduire les UID au fur et à mesure des besoins (ce qui est hautement improbable)

grand johnson
la source
2
Ordures. Elle est adéquate dans le cas où la classe n'a pas changé. Vous n'avez aucune preuve pour prendre en charge '99 .9999% '.
Marquis de Lorne
18

Comme pour un exemple où le serialVersionUID manquant pourrait causer un problème:

Je travaille sur cette application Java EE composée d'un module Web qui utilise un EJBmodule. Le module Web appelle le EJBmodule à distance et transmet un POJOqui implémente Serializablecomme argument.

Cette POJO'sclasse a été empaquetée à l'intérieur du pot EJB et à l'intérieur de son propre pot dans le WEB-INF / lib du module web. Ils sont en fait de la même classe, mais lorsque je conditionne le module EJB, je déballe le pot de ce POJO pour le regrouper avec le module EJB.

L'appel au EJBa échoué avec l'exception ci-dessous car je ne l'avais pas déclaré serialVersionUID:

Caused by: java.io.IOException: Mismatched serialization UIDs : Source
 (Rep.
 IDRMI:com.hordine.pedra.softbudget.domain.Budget:5CF7CE11E6810A36:04A3FEBED5DA4588)
 = 04A3FEBED5DA4588 whereas Target (Rep. ID RMI:com.hordine.pedra.softbudget.domain.Budget:7AF5ED7A7CFDFF31:6227F23FA74A9A52)
 = 6227F23FA74A9A52
Henrique Ordine
la source
16

J'utilise généralement serialVersionUIDdans un seul contexte: quand je sais que ça sortira du contexte de la VM Java.

Je le saurais quand j'utiliserai ObjectInputStreamet ObjectOutputStreampour mon application ou si je connais une bibliothèque / framework que j'utilise l'utilisera. Le serialVersionID garantit que différentes machines virtuelles Java de versions ou de fournisseurs différents fonctionneront correctement ou s'il est stocké et récupéré en dehors de la machine virtuelle par exempleHttpSession les données de session peuvent rester même pendant un redémarrage et une mise à niveau du serveur d'applications.

Pour tous les autres cas, j'utilise

@SuppressWarnings("serial")

car la plupart du temps la valeur par défaut serialVersionUIDest suffisante. Cela comprend Exception,HttpServlet .

Archimedes Trajano
la source
Il n'inclut pas HttpServlet dans les conteneurs où ils peuvent être échangés, ou Exception dans RMI par exemple.
Marquis de Lorne
14

Les données de champ représentent certaines informations stockées dans la classe. La classe implémente l' Serializableinterface, donc eclipse a automatiquement proposé de déclarer le serialVersionUIDchamp. Commençons par la valeur 1 définie ici.

Si vous ne voulez pas que cet avertissement apparaisse, utilisez ceci:

@SuppressWarnings("serial")
Mukti
la source
11

Ce serait bien si CheckStyle pouvait vérifier que le serialVersionUID sur une classe qui implémente Serializable a une bonne valeur, c'est-à-dire qu'il correspond à ce que le générateur d'ID de version série produirait. Si vous avez un projet avec beaucoup de DTO sérialisables, par exemple, se souvenir de supprimer le serialVersionUID existant et de le régénérer est une douleur, et actuellement le seul moyen (à ma connaissance) de vérifier cela est de régénérer pour chaque classe et de comparer avec le vieux. C'est très très douloureux.


la source
8
Si vous définissez le serialVersionUID toujours à la même valeur que le générateur produirait, vous n'en avez pas vraiment besoin du tout. Après tout, sa raison d'être est de rester la même après les changements, lorsque la classe est toujours compatible.
Paŭlo Ebermann
4
La raison en est que différents compilateurs proposent la même valeur pour la même classe. Comme expliqué dans les javadocs (également répondu ci-dessus), la version générée est fragile et peut varier même lorsque la classe est correctement désérialisable. Tant que vous exécutez ce test sur le même compilateur à chaque fois, il doit être sûr. Dieu vous aide si vous mettez à jour le jdk et qu'une nouvelle règle sort, même si votre code n'a pas changé.
Andrew Backer
2
Il n'est pas nécessaire de faire correspondre ce serialverqui produirait. -1
Marquis de Lorne
En général, ce n'est pas obligatoire du tout. @ Le cas d'AndrewBacker nécessiterait deux compilateurs différents sur le même fichier .java, les deux versions des fichiers .class communiquant entre elles - la plupart du temps, vous créez simplement la classe et la distribuez. Si c'est le cas, ne pas avoir de SUID fonctionnerait bien.
Bill K
1
Les personnes qui utilisent vraiment la sérialisation à des fins de stockage / récupération définissent généralement la valeur serialVersionUIDà 1. Si une version plus récente de la classe est incompatible, mais nécessite toujours de pouvoir gérer les anciennes données, vous incrémentez le numéro de version et ajoutez du code spécial à traiter des formats plus anciens. Je pleure chaque fois que je vois un serialVersionUIDchiffre supérieur à 1, soit parce que c'était un nombre aléatoire (inutile), soit parce que la classe a apparemment besoin de gérer plus de 10 versions différentes.
john16384
11

SerialVersionUID est utilisé pour le contrôle de version de l'objet. vous pouvez également spécifier serialVersionUID dans votre fichier de classe. La conséquence de ne pas spécifier serialVersionUID est que lorsque vous ajoutez ou modifiez un champ dans la classe, la classe déjà sérialisée ne pourra pas récupérer car serialVersionUID généré pour la nouvelle classe et pour l'ancien objet sérialisé sera différent. Le processus de sérialisation Java repose sur un serialVersionUID correct pour récupérer l'état de l'objet sérialisé et lève java.io.InvalidClassException en cas de non-correspondance de serialVersionUID

En savoir plus: http://javarevisited.blogspot.com/2011/04/top-10-java-serialization-interview.html#ixzz3VQxnpOPZ

Neethu
la source
9

Pourquoi utiliser à l' SerialVersionUIDintérieurSerializable Class en Java?

Pendant serialization, le runtime Java crée un numéro de version pour une classe, afin de pouvoir le désérialiser ultérieurement. Ce numéro de version est appelé SerialVersionUIDJava.

SerialVersionUIDest utilisé pour la version des données sérialisées. Vous ne pouvez désérialiser une classe que si elle SerialVersionUIDcorrespond à l'instance sérialisée. Lorsque nous ne déclarons pas SerialVersionUIDdans notre classe, le runtime Java le génère pour nous mais ce n'est pas recommandé. Il est recommandé de déclarer SerialVersionUIDcomme private static final longvariable pour éviter le mécanisme par défaut.

Lorsque vous déclarez une classe Serializableen implémentant une interface marqueur java.io.Serializable, le runtime Java conserve l'instance de cette classe sur le disque à l'aide du mécanisme de sérialisation par défaut, à condition que vous n'ayez pas personnalisé le processus à l'aide de l' Externalizableinterface.

voir aussi Pourquoi utiliser SerialVersionUID dans la classe Serializable en Java

roottraveller
la source
8

Si vous souhaitez modifier un grand nombre de classes qui n'avaient pas de serialVersionUID défini en premier lieu tout en conservant la compatibilité avec les anciennes classes, des outils comme IntelliJ Idea, Eclipse échouent car ils génèrent des nombres aléatoires et ne fonctionnent pas sur un tas de fichiers en une fois. Je propose le script bash suivant (je suis désolé pour les utilisateurs de Windows, envisagez d'acheter un Mac ou de le convertir en Linux) pour résoudre facilement le problème serialVersionUID:

base_dir=$(pwd)                                                                  
src_dir=$base_dir/src/main/java                                                  
ic_api_cp=$base_dir/target/classes                                               

while read f                                                                     
do                                                                               
    clazz=${f//\//.}                                                             
    clazz=${clazz/%.java/}                                                       
    seruidstr=$(serialver -classpath $ic_api_cp $clazz | cut -d ':' -f 2 | sed -e 's/^\s\+//')
    perl -ni.bak -e "print $_; printf qq{%s\n}, q{    private $seruidstr} if /public class/" $src_dir/$f
done

vous enregistrez ce script, dites add_serialVersionUID.sh à vous ~ / bin. Ensuite, vous l'exécutez dans le répertoire racine de votre projet Maven ou Gradle comme:

add_serialVersionUID.sh < myJavaToAmend.lst

Ce .lst inclut la liste des fichiers java pour ajouter le serialVersionUID au format suivant:

com/abc/ic/api/model/domain/item/BizOrderTransDO.java
com/abc/ic/api/model/domain/item/CardPassFeature.java
com/abc/ic/api/model/domain/item/CategoryFeature.java
com/abc/ic/api/model/domain/item/GoodsFeature.java
com/abc/ic/api/model/domain/item/ItemFeature.java
com/abc/ic/api/model/domain/item/ItemPicUrls.java
com/abc/ic/api/model/domain/item/ItemSkuDO.java
com/abc/ic/api/model/domain/serve/ServeCategoryFeature.java
com/abc/ic/api/model/domain/serve/ServeFeature.java
com/abc/ic/api/model/param/depot/DepotItemDTO.java
com/abc/ic/api/model/param/depot/DepotItemQueryDTO.java
com/abc/ic/api/model/param/depot/InDepotDTO.java
com/abc/ic/api/model/param/depot/OutDepotDTO.java

Ce script utilise l'outil JDK serialVer sous le capot. Assurez-vous donc que votre $ JAVA_HOME / bin est dans le CHEMIN.

schnell18
la source
2
Me donne une idée: toujours régénérer l'uid de la version série avec un outil comme celui-ci, jamais à la main, avant de faire une version - de cette façon, vous évitez d'oublier d'apporter une modification à une classe dont l'uid de la version série aurait dû changer en raison d'un réel changement incompatible. Garder une trace de cela manuellement est très difficile.
Ignazio
6

Cette question est très bien documentée dans Effective Java de Joshua Bloch. Un très bon livre et une lecture incontournable. Je décrirai certaines des raisons ci-dessous:

Le runtime de sérialisation propose un numéro appelé version série pour chaque classe sérialisable. Ce numéro est appelé serialVersionUID. Maintenant, il y a des maths derrière ce nombre et il sort en fonction des champs / méthodes définis dans la classe. Pour la même classe, la même version est générée à chaque fois. Ce numéro est utilisé pendant la désérialisation pour vérifier que l'expéditeur et le récepteur d'un objet sérialisé ont chargé des classes pour cet objet qui sont compatibles en ce qui concerne la sérialisation. Si le récepteur a chargé une classe pour l'objet qui a un serialVersionUID différent de celui de la classe de l'expéditeur correspondant, la désérialisation entraînera une InvalidClassException.

Si la classe est sérialisable, vous pouvez également déclarer explicitement votre propre serialVersionUID en déclarant un champ nommé "serialVersionUID" qui doit être statique, final et de type long. La plupart des IDE comme Eclipse vous aident à générer cette longue chaîne.

Geek
la source
6

Chaque fois qu'un objet est sérialisé, l'objet est estampillé d'un numéro d'identification de version pour la classe de l'objet. Cet identifiant est appelé serialVersionUID et il est calculé en fonction des informations sur la structure de la classe. Supposons que vous ayez créé une classe Employé et qu'elle ait l'ID de version # 333 (attribué par JVM). Maintenant, lorsque vous sérialiserez l'objet de cette classe (Supposons objet Employé), JVM lui attribuera l'UID en tant que # 333.

Considérez une situation - à l'avenir, vous devrez éditer ou changer votre classe et dans ce cas, lorsque vous la modifierez, JVM lui attribuera un nouvel UID (supposons # 444). Maintenant, lorsque vous essayez de désérialiser l'objet employé, JVM compare l'ID de version de l'objet sérialisé (objet employé) (# 333) avec celui de la classe, c'est-à-dire # 444 (puisqu'il a été modifié). En comparaison, JVM trouvera que les deux versions UID sont différentes et donc la désérialisation échouera. Par conséquent, si serialVersionID pour chaque classe est défini par le programmeur lui-même. Il en sera de même même si la classe évolue à l'avenir et donc JVM trouvera toujours que la classe est compatible avec un objet sérialisé même si la classe est modifiée. Pour plus d'informations, vous pouvez vous référer au chapitre 14 de HEAD FIRST JAVA.

Naved Ali
la source
1
Chaque fois que la classe d'un objet est sérialisée, elle serialVersionUIDest transmise. Il n'est pas envoyé avec chaque objet.
Marquis de Lorne
3

Une explication simple:

  1. Êtes-vous en train de sérialiser des données?

    La sérialisation consiste essentiellement à écrire des données de classe dans un fichier / flux / etc. La désérialisation consiste à lire ces données dans une classe.

  2. Comptez-vous entrer en production?

    Si vous testez simplement quelque chose avec des données non importantes / fausses, ne vous en faites pas (sauf si vous testez directement la sérialisation).

  3. Est-ce la première version?

    Si oui, réglez serialVersionUID=1L.

  4. S'agit-il de la deuxième, troisième, etc. version prod?

    Maintenant, vous devez vous inquiéter serialVersionUIDet vous devriez l'examiner en profondeur.

Fondamentalement, si vous ne mettez pas à jour la version correctement lorsque vous mettez à jour une classe que vous devez écrire / lire, vous obtiendrez une erreur lorsque vous essayez de lire les anciennes données.

gagarwa
la source
2

'serialVersionUID' est un nombre 64 bits utilisé pour identifier de manière unique une classe pendant le processus de désérialisation. Lorsque vous sérialisez un objet, serialVersionUID de la classe est également écrit dans le fichier. Chaque fois que vous désérialisez cet objet, le temps d'exécution java extrait cette valeur serialVersionUID des données sérialisées et compare la même valeur associée à la classe. Si les deux ne correspondent pas, alors 'java.io.InvalidClassException' sera levée.

Si une classe sérialisable ne déclare pas explicitement un serialVersionUID, le runtime de sérialisation calculera la valeur serialVersionUID pour cette classe en fonction de divers aspects de la classe comme les champs, les méthodes, etc., vous pouvez faire référence à ce lien pour l'application de démonstration.

Hari Krishna
la source
1

Pour faire court, ce champ est utilisé pour vérifier si les données sérialisées peuvent être désérialisées correctement. La sérialisation et la désérialisation sont souvent effectuées par différentes copies du programme - par exemple, le serveur convertit l'objet en chaîne et le client convertit la chaîne reçue en objet. Ce champ indique que les deux fonctionnent avec la même idée de ce qu'est cet objet. Ce champ est utile lorsque:

  • vous disposez de nombreuses copies différentes de votre programme à différents endroits (comme 1 serveur et 100 clients). Si vous changez votre objet, modifiez votre numéro de version et oubliez de mettre à jour un de ces clients, il saura qu'il n'est pas capable de désérialisation

  • vous avez stocké vos données dans un fichier et plus tard vous essayez de l'ouvrir avec une version mise à jour de votre programme avec un objet modifié - vous saurez que ce fichier n'est pas compatible si vous gardez votre version correcte

Quand est-ce important?

Le plus évident - si vous ajoutez des champs à votre objet, les anciennes versions ne pourront pas les utiliser car ils n'ont pas ces champs dans leur structure d'objet.

Moins évident - Lorsque vous désérialisez un objet, les champs qui n'étaient pas présents dans la chaîne seront conservés comme NULL. Si vous avez supprimé le champ de votre objet, les anciennes versions garderont ce champ comme allways-NULL, ce qui peut conduire à un mauvais comportement si les anciennes versions s'appuient sur les données de ce champ (de toute façon vous l'avez créé pour quelque chose, pas seulement pour le plaisir :-))

Le moins évident - Parfois, vous changez l'idée que vous mettez dans le sens d'un domaine. Par exemple, quand vous avez 12 ans, vous voulez dire "vélo" sous "vélo", mais quand vous avez 18 ans, vous voulez dire "moto" - si vos amis vous invitent à "faire du vélo à travers la ville" et vous serez le seul à venu à vélo, vous comprendrez combien il est important de garder la même signification dans tous les domaines :-)

Stanislav Orlov
la source
1

Tout d'abord pour répondre à votre question, lorsque nous ne déclarons pas SerialVersionUID dans notre classe, le runtime Java le génère pour nous, mais ce processus est sensible à de nombreuses métadonnées de classe, y compris le nombre de champs, le type de champs, le modificateur d'accès des champs, l'interface implémentée par classe, etc. Il est donc recommandé de le déclarer nous-mêmes et Eclipse vous en avertit.

Sérialisation: Nous travaillons souvent avec des objets importants dont l'état (données dans les variables de l'objet) est si important que nous ne pouvons pas risquer de le perdre en raison de pannes d'alimentation / système (ou) de pannes de réseau en cas d'envoi de l'état d'objet à d'autres machine. La solution à ce problème est nommée "Persistance", ce qui signifie simplement la persistance (conservation / sauvegarde) des données. La sérialisation est l'une des nombreuses autres façons d'obtenir la persistance (en enregistrant les données sur le disque / la mémoire). Lors de l'enregistrement de l'état de l'objet, il est important de créer une identité pour l'objet, afin de pouvoir le relire correctement (dé-sérialisation). Cette identification unique est l'ID est SerialVersionUID.

Shanks D Shiva
la source
0

Qu'est-ce que SerialVersionUID? Réponse: - Disons qu'il y a deux personnes, une du siège et une autre de l'ODC, toutes deux vont effectuer la sérialisation et la désérialisation respectivement. Dans ce cas, pour authentifier que le destinataire qui se trouve dans ODC est la personne authentifiée, JVM crée un ID unique connu sous le nom de SerialVersionUID.

Voici une belle explication basée sur le scénario,

Pourquoi SerialVersionUID?

Sérialisation : au moment de la sérialisation, avec chaque objet, l'expéditeur JVM enregistre un identifiant unique. JVM est responsable de générer cet ID unique sur la base du fichier .class correspondant qui est présent dans le système émetteur.

Désérialisation : au moment de la désérialisation, la JVM côté récepteur comparera l'ID unique associé à l'objet avec l'ID unique de la classe locale, c'est-à-dire que la JVM créera également un ID unique basé sur le fichier .class correspondant qui est présent dans le système récepteur. Si les deux identifiants uniques correspondent, seule la désérialisation sera effectuée. Sinon, nous obtiendrons une exception d'exécution indiquant InvalidClassException. Cet identifiant unique n'est rien d'autre que SerialVersionUID

forkdbloke
la source