Java Language Versioning et serialVersionUID


Exemple

Lorsque vous implémentez l'interface java.io.Serializable pour rendre une classe sérialisable, le compilateur recherche un static final nommé serialVersionUID de type long . Si la classe n'a pas ce champ déclaré explicitement, le compilateur créera un tel champ et l'attribuera avec une valeur qui sort d'un calcul de serialVersionUID dépendant de l' serialVersionUID . Ce calcul dépend de divers aspects de la classe et suit les spécifications de sérialisation d’objet données par Sun. Cependant, la valeur n'est pas garantie pour toutes les implémentations du compilateur.

Cette valeur est utilisée pour vérifier la compatibilité des classes par rapport à la sérialisation et cela lors de la désérialisation d'un objet enregistré. Serialization Runtime vérifie que serialVersionUID lu à partir des données serialVersionUID et que serialVersionUID déclaré dans la classe sont exactement les mêmes. Si ce n'est pas le cas, il génère une InvalidClassException .

Il est fortement recommandé de déclarer et d'initialiser explicitement le champ statique de type long et nommé 'serialVersionUID' dans toutes les classes que vous souhaitez rendre Serializable au lieu de compter sur le calcul par défaut de la valeur pour ce champ même si vous ne voulez pas utiliser le contrôle de version. Le calcul 'serialVersionUID' est extrêmement sensible et peut varier d'une implémentation de compilateur à l'autre. Vous pouvez donc obtenir l' InvalidClassException même pour la même classe simplement parce que vous avez utilisé différentes implémentations de compilateur.

public class Example implements Serializable {          
    static final long serialVersionUID = 1L /*or some other value*/;
    //...
}

Tant que serialVersionUID est identique, la sérialisation Java peut gérer différentes versions d'une classe. Les changements compatibles et incompatibles sont;

Changements compatibles

  • Ajouter des champs: Lorsque la classe en cours de reconstitution comporte un champ qui ne figure pas dans le flux, ce champ dans l'objet sera initialisé à la valeur par défaut pour son type. Si une initialisation spécifique à une classe est nécessaire, la classe peut fournir une méthode readObject capable d'initialiser le champ à des valeurs autres que celles par défaut.
  • Ajout de classes: le flux contiendra la hiérarchie de types de chaque objet du flux. La comparaison de cette hiérarchie dans le flux avec la classe en cours peut détecter des classes supplémentaires. Comme il n'y a aucune information dans le flux à partir de laquelle initialiser l'objet, les champs de la classe seront initialisés aux valeurs par défaut.
  • Suppression de classes: La comparaison de la hiérarchie de classes dans le flux avec celle de la classe en cours peut détecter qu'une classe a été supprimée. Dans ce cas, les champs et les objets correspondant à cette classe sont lus dans le flux. Les champs primitifs sont ignorés, mais les objets référencés par la classe supprimée sont créés, car ils peuvent être référencés ultérieurement dans le flux. Ils seront récupérés lorsque le flux est récupéré ou réinitialisé.
  • Ajouter des méthodes writeObject / readObject: Si la version lisant le flux possède ces méthodes, readObject doit, comme d'habitude, lire les données requises écrites dans le flux par la sérialisation par défaut. Il doit d'abord appeler defaultReadObject avant de lire les données facultatives. La méthode writeObject devrait normalement appeler defaultWriteObject pour écrire les données requises, puis écrire des données facultatives.
  • Ajouter java.io.Serializable: Cela équivaut à ajouter des types. Il n'y aura aucune valeur dans le flux pour cette classe, donc ses champs seront initialisés aux valeurs par défaut. La prise en charge des sous-classes de classes non sérialisables nécessite que le sur-type de la classe ait un constructeur sans argument et que la classe elle-même soit initialisée aux valeurs par défaut. Si le constructeur no-arg n'est pas disponible, l'exception InvalidClassException est levée.
  • Modification de l'accès à un champ: Les modificateurs d'accès public, package, protected et private n'ont aucun effet sur la capacité de la sérialisation à affecter des valeurs aux champs.
  • Changer un champ statique en non statique ou transitoire en non-transitoire: lorsque vous utilisez la sérialisation par défaut pour calculer les champs sérialisables, cette modification équivaut à ajouter un champ à la classe. Le nouveau champ sera écrit dans le flux, mais les classes précédentes ignoreront la valeur car la sérialisation n'affectera pas de valeurs aux champs statiques ou transitoires.

Changements incompatibles

  • Suppression de champs: Si un champ est supprimé dans une classe, le flux écrit ne contiendra pas sa valeur. Lorsque le flux est lu par une classe antérieure, la valeur du champ est définie sur la valeur par défaut car aucune valeur n'est disponible dans le flux. Cependant, cette valeur par défaut peut compromettre la capacité de la version antérieure à respecter son contrat.
  • Déplacement des classes vers le haut ou le bas de la hiérarchie: cela ne peut pas être autorisé car les données du flux apparaissent dans la mauvaise séquence.
  • Changer un champ non statique en statique ou un champ non-transitoire en transitoire: en cas de sérialisation par défaut, cette modification équivaut à supprimer un champ de la classe. Cette version de la classe n'écrira pas ces données dans le flux, elle ne sera donc pas disponible pour être lue par les versions antérieures de la classe. Comme lors de la suppression d'un champ, le champ de la version antérieure sera initialisé à la valeur par défaut, ce qui peut entraîner un échec inattendu de la classe.
  • Modification du type déclaré d'un champ primitif: Chaque version de la classe écrit les données avec son type déclaré. Les versions antérieures de la classe qui tentent de lire le champ échoueront car le type des données du flux ne correspond pas au type du champ.
  • Changer la méthode writeObject ou readObject pour qu'elle n'écrive plus ou ne lise plus les données de champ par défaut ou ne les modifie pas de manière à ce que celle-ci tente de l'écrire ou de la lire lorsque la version précédente ne l'a pas fait. Les données de champ par défaut doivent toujours apparaître ou ne pas apparaître dans le flux.
  • Changer une classe de Serializable à Externalizable ou vice versa est un changement incompatible puisque le flux contiendra des données incompatibles avec l'implémentation de la classe disponible.
  • Changer une classe d'un type non-enum en un type enum ou vice versa puisque le flux contiendra des données incompatibles avec l'implémentation de la classe disponible.
  • La suppression de Serializable ou Externalizable est une modification incompatible car, une fois écrite, elle ne fournira plus les champs requis par les anciennes versions de la classe.
  • L'ajout de la méthode writeReplace ou readResolve à une classe est incompatible si le comportement produit un objet incompatible avec une version antérieure de la classe.