Java RMI sérialisation - O Reilly Médias
Comment faire une classe Serializable
Jusqu'à présent, nous avons mis l'accent sur les mécanismes de sérialisation d'un objet. Nous avons supposé que nous avons un objet sérialisable et discuté, du point de vue du code client, comment sérialiser. L'étape suivante discute de la façon de faire une classe sérialisable.
Il y a quatre choses de base que vous devez faire quand vous faites une classe sérialisable. Elles sont:
- Mettre en œuvre l'interface Serializable.
- Assurez-vous que niveau de l'instance, est publié en feuilleton correctement état défini localement.
- Assurez-vous que l'état superclasse est sérialisé correctement.
- Surchargent equals () et hashCode ().
Regardons chacune de ces étapes plus en détail.
Implémenter l'interface Serializable
fichier File = new File ( "c: \\ temp \\ foo");
Ce n'est pas clair du tout ce qui doit être écrit quand cela est publié en feuilleton. Le problème est que le fichier lui-même a un autre Lifecyle que les données sérialisés. Le fichier peut être modifié ou supprimé entièrement, alors que les informations sérialisé reste inchangé. Ou les informations sérialisé peut être utilisé pour redémarrer l'application sur une autre machine, où « C: \\ temp \\ foo » est le nom d'un fichier tout à fait différent.
Un autre exemple est fourni par la classe Thread. (Si vous ne savez pas beaucoup sur les threads, il suffit d'attendre quelques chapitres et revisiter cet exemple. Il aura plus de sens alors.) Un fil représente un flux d'exécution au sein d'une machine virtuelle Java particulière. Vous non seulement avoir à stocker la pile, et toutes les variables locales, mais aussi toutes les serrures et les fils associés, et redémarrez tous les fils correctement lorsque l'instance est désérialisée.
CONSEIL: Les choses empirent lorsque l'on considère les dépendances de la plate-forme. En général, toute classe qui implique le code natif n'est pas vraiment un bon candidat pour la sérialisation.
Assurez-vous que l'instance niveau, État défini localement est sérialisé correctement
Le mécanisme de sérialisation a un beau comportement par défaut - si tout le niveau de l'instance, les variables définies localement ont des valeurs qui sont soit des objets sérialisables ou types de données primaires, le mécanisme de sérialisation fonctionnera sans effort supplémentaire de notre part. Par exemple, nos implémentations de compte. tels que Account_Impl. présenterait aucun problème pour le mécanisme de sérialisation par défaut:
Account_Impl public class étend les outils UnicastRemoteObject compte en argent _balance privé;
.
>
Bien que _balance n'a pas un type primitif, il ne se réfère à une instance de l'argent. qui est une classe sérialisable.
ArrayList public class étend AbstractList implémente la liste, Cloneable, java.io.
Objet privé sérialisable elementData [];
privé int size;
.
>
Mais caché ici est un énorme problème: ArrayList est une classe de conteneur générique dont l'état est stocké sous forme d'un tableau d'objets. Bien que les tableaux sont des objets de première classe en Java, ils ne sont pas des objets sérialisables. Cela signifie que ArrayList ne peut pas mettre en œuvre que l'interface Serializable. Il doit fournir des informations supplémentaires pour aider le mécanisme de sérialisation gérer ses champs non sérialisable. Il existe trois solutions de base à ce problème:
ArrayList public class étend AbstractList implémente la liste, Cloneable, java.io.
Objet transitoire privé sérialisable elementData [];
privé int size;
.
>
Cela indique au mécanisme de sérialisation par défaut d'ignorer la variable. En d'autres termes, le mécanisme de sérialisation saute simplement sur les variables transitoires. Dans le cas de ArrayList. le mécanisme de sérialisation par défaut tenterait d'écrire la taille. mais ignorer entièrement elementData.
Cela peut être utile dans deux situations, généralement distinctes:
Exécution writeObject () et readObject ()
writeObject private void (java.io.ObjectOutputStream out) jette readObject private void IOException (java.io.ObjectInputStream in) jette IOException, ClassNotFoundException;
Lorsque le mécanisme de sérialisation commence à écrire un objet, il vérifiera si la classe implémente writeObject (). Dans ce cas, le mécanisme de sérialisation n'utilisera pas le mécanisme par défaut et n'écrire aucune des variables d'instance. Au lieu de cela, il appellera writeObject () et dépendent de la méthode pour stocker toutes l'état importante. Voici la mise en œuvre de » ArrayList de writeObject ():
private void synchronisé writeObject (flux java.io.ObjectOutputStream) jette java.
io.IOException stream.defaultWriteObject ();
stream.writeInt (elementData.length);
pour (int i = 0; i
>
La première chose que cela ne fait qu'appeler defaultWriteObject (). defaultWriteObject () appelle le mécanisme de sérialisation par défaut, qui sérialise tous les nontransient, les variables d'instance non statiques. Ensuite, la méthode écrit sur elementData.length puis appelle la writeObject du flux () pour chaque élément de elementData.
Il y a un point important qui est parfois manqué: readObject () et writeObject () sont une paire de méthodes qui doivent être mises en œuvre ensemble. Si vous faites une personnalisation de sérialisation dans l'une de ces méthodes, vous devez implémenter l'autre méthode. Si vous ne le faites pas, l'algorithme de sérialisation échouera.
Tests unitaires et sérialisation
Si vous adoptez une méthodologie des tests unitaires, toute classe sérialisable doit passer les trois tests suivants:
- Si elle met en œuvre readObject (). il devrait mettre en œuvre writeObject (). et vice versa.
- Il est égal (en utilisant la méthode equals ()) à une copie sérialisée de lui-même.
- Il a la même hashcode une copie sérialisée de lui-même.
Des contraintes similaires tiennent pour les classes qui mettent en œuvre l'interface Externalizable.
Jusqu'à présent, nous avons parlé seulement état instance de niveau. Qu'en est-état-niveau de la classe? Supposons que vous avez des informations importantes stockées dans une variable statique? Les variables statiques ne seront pas sauvés par sérialisation sauf si vous ajoutez un code spécial pour le faire. Dans notre contexte, (objets expédition sur le fil entre les clients et les serveurs), sont généralement une statics mauvaise idée de toute façon.
Assurez-vous que superclasse État est traitée correctement
Un autre aspect de la manipulation de l'état d'une superclasse non sérialisable est que non sérialisable superclasse doit avoir un constructeur sans argument. Ce n'est pas important pour la sérialisation sur un objet, mais il est extrêmement important quand désérialiser un objet. Désérialisation fonctionne en créant une instance d'une classe et remplir correctement ses champs. Au cours de ce processus, l'algorithme de désérialisation ne fait appel aucun des constructeurs de la classe sérialisée, mais n'appelle le constructeur sans argument de la première superclasse non sérialisable. S'il n'y a pas un constructeur sans argument, l'algorithme de désérialisation ne peut pas créer des instances de la classe, et ne l'ensemble du processus.
ATTENTION: Si vous ne pouvez pas créer un constructeur sans argument dans les premiers superclasse non sérialisable, vous devrez implémenter l'interface Externalizable à la place.
Il suffit d'ajouter un constructeur sans argument peut sembler un peu problématique. Supposons que l'objet a déjà plusieurs constructeurs, qui tous prennent des arguments. Si vous ajoutez simplement un constructeur sans argument, le mécanisme de sérialisation peut laisser l'objet dans un demi-initialisé, et donc inutilisable, état.
Toutefois, étant donné que la sérialisation fournira les variables d'instance avec des valeurs correctes d'une instance active immédiatement après instanciation de l'objet, la seule façon dont ce problème pourrait se poser est de savoir si les constructeurs font réellement quelque chose avec leurs arguments - en plus des valeurs de réglage variables.
à quelque chose qui ressemble à:
Après cela, writeObject () / readObject () doit être mis en œuvre, et readObject () doit se terminer par un appel à initialiser (). Parfois, cela se traduira par un code qui appelle simplement le mécanisme de sérialisation par défaut, comme dans l'extrait suivant:
writeObject private void (flux java.io.ObjectOutputStream) jette
java.io.IOException stream.defaultWriteObject ();
>
readObject private void (flux java.io.ObjectInputStream) jette
java.io.IOException stream.defaultReadObject ();
initialiser ();
>
CONSEIL: Si la création d'un constructeur sans argument est difficile (par exemple, vous ne disposez pas du code source pour la superclasse), votre classe devra implémenter l'interface Externalizable au lieu de Serializable.
Surchargent equals () et hashCode () si nécessaire
Les mises en œuvre par défaut d'égal à égal () et hashCode (). qui sont héritées de java.lang.Object. il suffit d'utiliser l'emplacement d'une instance en mémoire. Cela peut être problématique. Considérez notre exemple précédent code de copie en profondeur:
ByteArrayOutputStream memoryOutputStream = new ByteArrayOutputStream ();
ObjectOutputStream sérialiseur = new ObjectOutputStream (memoryOutputStream);
serializer.writeObject (serializableObject);
serializer.flush ();
ByteArrayInputStream memoryInputStream = new ByteArrayInputStream (memoryOutputStream.
toByteArray ());
ObjectInputStream désérialiseur = new ObjectInputStream (memoryInputStream);
Objet deepCopyOfOriginalObject = deserializer.readObject ();
Le problème potentiel implique ici le test booléen suivant:
Parfois, comme dans le cas de l'argent et DocumentDescription. la réponse doit être vrai. Si deux instances d'argent ont les mêmes valeurs pour _cents. alors ils sont égaux. Cependant, la mise en œuvre d'égal à égal () hérité de l'objet renvoie false.
Le même problème se produit avec hashCode (). Notez que l'objet implémente hashCode () en retournant l'adresse mémoire de l'instance. Par conséquent, pas deux instances ont toujours le même hashCode () en utilisant la mise en œuvre de l » objet. Si deux objets sont égaux, cependant, ils devraient avoir le même hashcode. Donc, si vous avez besoin de passer outre equals (). vous avez probablement besoin de passer outre hashCode () ainsi.