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 |
De toute façon, tout ce qui est défini dans une interface est automatiquement public donc writeObject() et readObject() doivent être private, à ce moment là ils feront partie d'une interface. Puisque vous devez suivre exactement les signatures, l'effet est le même que si vous implémentiez une interface.
Il apparaîtra que lorsque vous appelez ObjectOutputStream.writeObject(), l'objet Serializable que vous lui transmettez est interrogé (utilisant la réflexion, pas de doute) pourvoir si il implémente son propre writeObject(). Si c'est le cas, le processus normale de sérialisation est omis et est le writeObject()appelé. Le même type de situation existe pour readObject().
Il y a une autre entorse. À l'intérieur de votre writeObject( ), vous pouvez choisir d'exécuter l'action writeObject() par défaut en appelant defaultWriteObject(). Également, dans readObject()vous pouvez appeler defaultReadObject(). Voici un exemple simple qui démontre comment vous pouvez contrôler le stockage et la récupération d'un objet Serializable :
Dans cet exemple, un champ String est normal et le second est transient, pour prouver que le champ non-transient est sauvé par la méthode defaultWriteObject() et le que le champ transient est sauvé et récupéré explicitement. Les champs sont initialisés dans le constructeur plutôt qu'au point de définition pour prouver qu'ils n'ont pas été initialisés par un certain mécanisme automatique durant la sérialisation.
Si vous employez le mécanisme par défaut pour écrire les parties non-transient de votre objet, vous devrez appeler defaultWriteObject() comme la première action dans writeObject() et defaultReadObject() comme la première action dans readObject(). Ce sont d'étranges appels à des méthodes. Il apparaît, par exemple, que vous appelez defaultWriteObject() pour un ObjectOutputStream et ne lui passez aucun argument, mais il tourne d'une manière ou d'une autre autour et connaît la référence à votre objet et comment écrire toutes les parties non-transient. Étrange.
Le stockage et la récupération des objets transient utilisent un code plus familier. Et cependant, pensez à ce qu'il se passe ici. Dans main(), un objet SerialCtl est créé, puis est sérialisé en un ObjectOutputStream. (Notez dans ce cas qu'un tampon est utilisé à la place d'un fichier — c'est exactement pareil pour tout l' ObjectOutputStream.) La sérialisation survient à la ligne :
La méthode writeObject() doit examiner sc pour voir si il possède sa propre méthode writeObject(). (Non pas en contrôlant l'interface — il n'y en a pas — ou le type de classe, mais en recherchant en fait la méthode en utilisant la réflexion.) Si c'est le cas, elle l'utilise. Une approche similaire garde true pour readObject(). Peut-être que c'est la seule réelle manière dont ils peuvent résoudre le problème, mais c'est assurément étrange.
Il est possible que vous désiriez changer la version d'une classe sérialisable (les objets de la classe original peuvent être stockés dans un base de donnée, par exemple). Ceci est supporté mais vous devrez probablement le faire seulement dans les cas spéciaux, et cela requiert un profondeur supplémentaire de compréhension qui ne sera pas tenté d'atteindre ici. Les documents HTML du JDK téléchargeables depuis java.sun.com couvrent ce sujet de manière très approfondie.
Vous pourrez aussi noter dans la documentation HTML du JDK que de nombreux commentaires commencent par :
Attention : Les objets sérialisés de cette classe ne seront pas compatibles avec les futures versions de Swing. Le support actuel de la sérialisation est approprié pour le stockage à court terme ou le RMI entre les applications. ...
Ceci parce que le mécanisme de versionning est trop simple pour fonctionner de manière fiable dans toutes les situations, surtout avec les JavaBeans. Ils travaillent sur un correction de la conception, et c'est le propos de l'avertissement.
Il est plutôt attrayant de faire appel à la technologie de la sérialisation pour stocker certains états de votre programme afin que vous puissiez facilement récupérer le programme dans l'état actuel plus tard. Mais avant de de pouvoir faire cela, il faut répondre à certaines questions. Qu'arrive-t-il si vous sérialisez deux objets qui ont tous les deux une référence à un troisième objet ? Quand vous récupérez ces deux objets depuis leur état sérialisé, aurez vous une seule occurrence du troisième objet ? Que ce passe-t-il si vous sérialisez vos deux objets pour séparer les fichiers et les désérialisez dans différentes parties de votre code ?
Voici un exemple qui montre le problème :
Une chose intéressante ici est qu'il est possible d'utiliser la sérialisation d'objet depuis et vers un tableau de bytes comme une manière de faire une « copie en profondeur » de n'importe quel objet qui estSerializable. (une copie en profondeur veux dire que l'on copie la structure complète des objets, plutôt que seeulement l'objet de base et ses références.) La copie est abordée en profondeur dans l'Annexe A.
Les objets Animal contiennent des champs de type House. Dans main(), une ArrayList de ces Animals est crée et est sérialisée deux fois vers un flux et ensuite vers un flux distinct. Quand ceci est désérialisé et affiché, on obtient les résultats suivant pour une exécution (les objets seront dans des emplacements mémoire différents à chaque exécution) :
Bien sur vous vous attendez à ce que les objets déserialisés aient des adresses différentes des originaux. Mais notez que dans animals1 et animals2 les mêmes adresses apparaissent, incluant les références à l'objet House que tous les deux partagent. D'un autre coté, quand animals3 est récupéré le système n'a pas de moyen de savoir que les objets de l'autre flux sont des alias des objets du premier flux, donc il crée un réseau d'objets complétement différent.
Aussi longtemps que vos sérialisez tout dans un flux unique, vous pourrez récupérer le même réseau d'objets que vous avez écrits, sans aucune duplication accidentelle d'objets. Bien sûr, vous pouvez modifier l'état de vos objets entre la période d'écriture du premier et du dernier, mais c'est de votre responsabilité — les objets seront écrit dans l'état où ils sont quel qu'il soit (et avec les connexions quelles qu'elles soient qu'ils ont avec les autres objets) au moment ou vous les sérialiserez.
La chose la plus sûre à faire si vous désirez sauver l'état d'un système est de sérialiser comme une opération « atomique. » Si vous sérialisez quelque chose, faites une autre action, et en sérialisez une autre en plus, etc., alors vous ne stockerez pas le système sûrement. Au lieu de cela, mettez tous les objets qui comprennent l'état de votre système dans un simple conteneur et écrivez simplement ce conteneur à l'extérieur en une seule opération. Ensuite vous pourrez aussi bien le récupérer avec un simple appel à une méthode.
L'exemple suivant est un système imaginaire de conception assistée par ordinateur (CAD) qui démontre cette approche. En plus, il projette dans le sujet des champs static — si vous regardez la documentation vous verrez que Class est Serializable, donc il sera facile de stocker les champs static en sérialisant simplement l'objet Class. Cela semble comme une approche sensible, en tous cas.
La classe Shape implemente Serializable, donc tout ce qui est hérité de Shape est aussi automatiquement Serializable. Chaque Shape contient des données, et chaque classe Shape dérivée contient un champ static qui détermine la couleur de tous ces types de Shapes. (Placer un champ static dans la classe de base ne donnera qu'un seul champ, puisque les champs static ne sont pas reproduit dans les classes dérivés.) Les méthodes dans les classes de base peuvent être surpassées [overridden] pour établir les couleurs des types variables (les méthodes static ne sont pas dynamiquement délimitées, donc ce sont des méthodes normales). La méthode randomFactory() crée un Shape différent chaque fois que vous y faites appel, utilisant des valeurs aléatoires pour les données du Shape.