Penser en Java 2nde édition | - | Sommaire | Préface | Avant-propos | Chapitre : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | Annexe : A B C D | Tables des matières | - | Thinking in Java |
L'essentiel de tout cela est de rendre quelque chose raisonnablement complexe qui ne puisse pas facilement être sérialisé. L'action de sérialiser, cependant, est plutôt simple. Une fois que l'ObjectOutputStream est crée depuis un autre flux, writeObject() sérialise l'objet. Notez aussi l'appel de writeObject() pour un String. Vous pouvez aussi écrire tous les types de données primitives utilisant les même méthodes qu'DataOutputStream (ils partagent la même interface).
Il y a deux portions de code séparées qui ont une apparence similaire. La première écrit et lit et la seconde, pour varier, écrit et lit un ByteArray. Vous pouvez lire et écrire un objet en utilisant la sérialisation vers n'importe quel DataInputStream ou DataOutputStream incluant, comme vous le verrez dans le Chapitre 15, un réseau. La sortie d'une exécution donne :
Vous pouvez voir que l'objet déserialisé contient vraiment tous les liens qui étaient dans l'objet original.
Notons qu'aucun constructeur, même pas le constructeur par défaut, n'est appelé dans le processus de désérialisation d'un objet Serializable. L'objet entier est restauré par récupération des données depuis l'InputStream.
La sérialisation objet est orientée-byte, et ainsi emploie les hiérarchies d'InputStream et d'OutputStream.
Vous devez vous demander ce qui est nécessaire pour qu'un objet soit récupéré depuis son état sérialisé. Par exemple, supposons que vous sérialisez un objet et que vous l'envoyez comme un fichier à travers un réseau vers une autre machine. Un programme sur l'autre machine pourra-t-il reconstruire l'objet en utilisant seulement le contenu du fichier ?
La meilleure manière de répondre a cette question est (comme d'habitude) en accomplissant une expérience. Le fichier suivant file dans le sous-répertoire pour ce chapitre :
Le fichier qui crée et sérialise un objet Alien va dans le même répertoire :
Plutôt que de saisir et de traiter les exeptions, ce programme prend une approche rapide et sale qui passe les exeptions en dehors de main(), ainsi elle seront reportés en ligne de commande.
Une fois que le programme est compilé et exécuté, copiez le X.file résultant dans un sous répertoire appeléxfiles, où va le code suivant :
Ce programme ouvre le fichier et lit dans l'objet mystery avec succès. Pourtant, dès que vous essayez de trouver quelque chose à propos de l'objet — qui nécessite l'objet Class pour Alien — la Machine Virtuelle Java (JVM) ne peut pas trouver Alien.class (à moins qu'il arrive qu'il soit dans le Classpath, ce qui n'est pas le cas dans cet exemple). Vous obtiendrez un ClassNotFoundException. (Une fois encore, toute les preuves de l'existence de vie alien disparaît avant que la preuve de son existence soit vérifiée !)
Si vous espérez en faire plus après avoir récupéré un objet qui a été sérialisé, vous devrez vous assurer que la JVM puisse trouver les fichiers .class soit dans le chemin de class local ou quelque part sur l'Internet.
Comme vous pouvez le voir, le mécanisme de sérialisation par défaut est d'un usage trivial. Mais que faire si vous avez des besoins spéciaux ? Peut-être que vous avez des problèmes de sécurité spéciaux et que vous ne voulez pas sérialiser des parties de votre objet, ou peut-être que cela n'a pas de sens pour un sous-objet d'être sérialisé si cette partie doit être de nouveau crée quand l'objet est récupéré.
Vous pouvez contrôler le processus de sérialisation en implémentant l'interface Externalizable à la place de l'interface Serializable. L'interface Externalizable étend l'interface Serializable et ajoute deux méthodes, writeExternal() et readExternal(), qui sont automatiquement appelées pour votre objet pendant la sérialisation et la désérialisation afin que vous puissiez exécuter vos opérations spéciales.
L'exemple suivant montre des implémentations simple des méthodes de l'interface Externalizable. Notez que Blip1 et Blip2 sont presque identiques à l'exception d'une subtile différence (voyez si vous pouvez la découvrir en regardant le code) :
La sortie pour ce programme est :
La raison pour laquelle l'objet Blip2 n'est pas récupéré est que le fait d'essayer provoque une exception. Vous pouvez voir la différence entreBlip1 et Blip2 ? Le constructeur de Blip1 est public, tandis que le constructeur de Blip2 ne l'est pas, et cela lance une exception au recouvrement. Essayez de rendre le constructor de Blip2 public et retirez les commentaires //! pour voir les résultats correct.
Quand b1 est récupéré, le constructeur par défaut Blip1 est appelé. Ceci est différent de récupérer un objet Serializable, dans lequel l'objet est construit entièrement de ses bits enregistrés, sans appel au constructeur. Avec un objet Externalizable, tous les comportements de construction par défaut se produisent (incluant les initialisations à ce point du champ de définition), et alors readExternal() est appelé. Vous devez prendre en compte ceci en particulier, le fait que toute la construction par défaut a toujours lieu pour produire le comportement correct dans vos objets Externalizable.
Voici un exemple qui montre ce que vous devez faire pour complétement stocker et retrouver un objet Externalizable :
Les champs s et i sont initialisés seulement dans le second constructeur, mais pas dans le constructeur par défaut. Ceci signifie que si vous n'initialisez pas s et i dans readExternal(), il sera alors null (parce que le stockage pour l'objet arrive nettoyé à zéro dans la première étape de la création de l'objet). Si vous enlevez les commentaires sur les deux lignes suivant les phrases « Vous devez faire ceci » et lancez le programme, vous verrez que lorsque l'objet est récupéré, s est null et i est zéro.
Si vous l'héritage se fait depuis un objet Externalizable, vous appellerez typiquement les versions classe-de-base de writeExternal() et readExternal() pour fournir un stockage et une récupération propre des composants de classe-de-base.
Ainsi pour faire fonctionner correctement les choses vous ne devrez pas seulement écrire les données importantes depuis l'objet pendant la méthode writeExternal() (il n'y a pas de comportement par défaut qui écrit n'importe quels objets pour un objet Externalizable object), mais vous devrez aussi récupérer ces données dans la méthode readExternal(). Ceci peut être un petit peu confus au premier abord parce que le constructeur par défaut du comportement pour un objet Externalizable peut le faire ressembler à une sorte de stockage et de récupération ayant lieu automatiquement. Ce n'est pas le cas.
Quand vous contrôlez la sérialisation, il peut y avoir un sous-objet précis pour qui vous ne voulez pas que le mécanisme de sérialisation java sauve et restaure automatiquement. C'est communément le cas si le sous-objet représente des informations sensibles que vous ne désirez pas sérialiser, comme un mot de passe. Même si cette information est private dans l'objet, une fois qu'elle est sérialisée il est possible pour quelqu'un d'y accéder en lisant un fichier ou en interceptant une transmission réseau.
Une manière de prévenir les parties sensibles de votre objet d'être sérialisé est d'implémenter votre classe comme Externalizable, comme montré précédemment. Ainsi rien n'est sérialisé automatiquement et vous pouvez sérialiser explicitement seulement les parties nécessaires dans writeExternal().
Si vous travaillez avec un objet Serializable, néanmoins, toutes les sérialisation arrivent de façon automatique. Pour contrôler ceci, vous pouvez fermer la sérialisation sur une base de champ-par-champ en utilisant le mot transient, lequel dit « Ne t'embarrasse pas a sauver ou restaurer ceci — Je me charge de ça. »
Par exemple, considérons un objet Login qui conserve les informations à propos d'un login de session particulier. Supposez que, dès que vous vérifiez le login, vous désirez stocker les données, mais sans le mot de passe. La manière la plus simple pour réaliser ceci est en d'implémentant Serializable et en marquant le champ password comme transient. Voici ce à quoi cela ressemble :
Vous pouvez voir que les champs date et username sont normaux (pas transient), et ils sont ainsi sérialisés automatiquement. Pourtant, password est transient, et donc n'est pas stocké sur le disque; aussi le mécanisme de sérialisation ne fait aucune tentative pour le récupérer. La sortie donne :
Lorsque l'objet est récupéré, le champ de password est null. Notez que toString() est obligé de contrôler la valeur null de password parcequ'il essaye d'assembler un objet String utilisant l'opérateur surchargé ‘ + ’, et que cet opérateur est confronté à une référence de type null, on aurait donc un NullPointerException. (Les nouvelles versions de Java contiendront peut être du code pour résoudre ce problème.)
Vous pouvez aussi voir que le champ date est stocké sur et récupéré depuis le disque et n'en génère pas une nouvelle.
Comme les objets Externalizable ne stockent pas tous leurs champs par défaut, le mot-clé transient est a employer avec les objets Serializable seulement.
Si vous n'êtes pas enthousiasmé par l'implémentation de l'interface Externalizable, il y a une autre approche. Vous pouvez implémenter l'interface Serializable et ajouter (notez que je dit « ajouter » et non pas « imposer » ou « implémenter ») des méthodes appelées writeObject() et readObject() qui seront automatiquement appelées quand l'objet est sérialisé et désérialisé, respectivement. C'est à dire, si vous fournissez ces deux méthodes elles seront employées à la place de la sérialisation par défaut.
Ces méthodes devront avoir ces signatures exactes :