La sérialisation
La sérialisation permet d'enregistrer ou de charger un objet dans un état précédent à partir d'un flux de données.
- La sérialisation permet donc la persistance des objets (la machine virtuelle peut être arrêtée, l'objet pourra être réinstancié plus tard come si rien n'était arrivé).
- Le flux de données pourra passer par un fichier ou réseau. L'objet pourra ainsi être exécuté sur une autre machine virtuelle java située n'importe où.
Seule contrainte,
la recréation de l'objet doit se faire avec la même classe que
celle de l'objet initialement enregistrée.
La sérialisation
est évidemment déjà implémentée dans les
classes Collections, String,
...
Interface Serializable
Pour être sérialisable, il faut et il suffit que la classe de votre objet (ou une de ses classes mères) ainsi que les classes objets qu'il mémorise (directement ou non) implémentent toutes l'interface java.io.Serializable.
public class maClasse implements java.io.Serializable {
[...]
}
Si une instance de
classe n'implémente pas Serializable et que l'on tente malgré
tout de la sérialiser, une exception NotSerializableException est levée.
Toutes les classes
dérivées d’une classe sérialisable sont aussi sérialisables.
La sérialisation
La sérialisation (sauvegarde) d'un objet passe par un filtre sur flux de données de type binaire: ObjectOuputStream.
Cela signifie donc
qu'il ne faut pas s'amuser à modifier ce type de fichier avec un bloc-notes
par exemple sous peine d'avoir une exception StreamCorruptedException au chargement
!
Il faudra:
- Instancier ObjectOutputStream en passant l'instance d'un flux binaire en paramètre. Exemple:
FileOutputStream fos = new FileOutputStream( "monFichierBin" );
ObjectOutputStream oos = new ObjectOutputStream(fos);
- Utiliser writeObject pour sauver l'instance de votre objet,
oos.writeObject(maClasse);
- Enfin faire un flush() pour vider les tampons et close() pour clôturer le flux.
oos.flush();
oos.close();
Le tout devant pouvoir gérer l'exception IOException
try {
FileOutputStream fos = new FileOutputStream( "monFichierBin" );
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(maClasse);
oos.flush();
oos.close();
}
catch (java.io.IOException e) {
e.printStackTrace();
}
Si vous tentez de
sérialiser une objet n'implémentant Serializable, vous obtiendrez
un exception java.io.NotSerializableException.
Désérialisation
La désérialisation (restaure) d'un objet passe par un filtre sur flux de données de type binaire: ObjectInputStream.
- Instancier ObjectInputStream en passant l'instance d'un flux binaire en paramètre. Exemple:
FileInputStream fis = new FileInputStream( "monFichierBin" );
ObjectInputStream ois = new ObjectInputStream(fis);
- Utiliser readObject() pour l'instancier votre objet, la méthode retournant cette instance.
MaClasse maClasse = (MaClasse) ois.readObject(); (un cast sera nécessaire !)
- Enfin close() pour clôturer le flux (mais le ramasse miette fera le travail aussi).
ois.close();
Là encore, des exceptions peuvent être levées:
try {
[...]
} catch (java.io.IOException e) {
e.printStackTrace();
}
catch (ClassNotFoundException e) {
e.printStackTrace();
}
D'autres exceptions peuvent se produire:
java.io.InvalidClassException: ... Local class not compatible. Cas où la classe a changé entre temps !
ou encore ClassNotFoundException si la classe n'existe pas sur la jvm.
Confidentialité des données
Le problème avec la sérialisation est que l'instance d'un objet est lisible via des éditeurs héxadécimaux par exemple. Il ne faudra donc pas y laisser des informations sensibles comme les mots de passe, ...
Pour éviter cela, le mot clef transient dans la construction d'un attribut va permettre de ne pas sauver cet attribut.
Exemple private transient String motDePasse;
Ces attributs
seront positionnées à null lors de la désérialisation.
Prévoir la possibilité de réinitialiser ces attributs !
Dans le cas d'un mot de passe en redemandant celui-ci par exemple.
Numéro de version
Vous pouvez obtenir un warning sur vos classes sérialisées:
serializable class Main has no definition of serialVersionUID
serialVersionUID est à définir de la manière suivante:
private static final long serialVersionUID = 99999L;
où 99999 est une valeur représentant le n° de version de votre classe (type Long). Donc vous pouvez le commencer à 1 puis l'incrémenter ensuite. Mais quand ?
Ce n° devra changer à chaque ajout ou suppression de champs à sérialiser dans la classe.
Ce n° n'est pas obligatoire. Dans ce cas, c'est le compilateur et non le développeur qui le génèrera. Mais il est fortement conseillé de gérer soit même le serialVersionUID de toutes classes sérialisables. Mais le risque étant d'oublier d'incrémenter la version lors d'une modification de la classe.
C'est ce n° qui
peut provoquer l'exception InvalidClassException. En effet, lors de la désérialisation,
un teste est déclenché pour comparer le n° de version de la
classe et celui de l'objet sérialisé. S'ils sont différents,
l'exception est levée.
Exemple de code
Dans le code suivant, une instance va être sérialisée avec la valeur en cours. Puis cette valeur sera modifiée. Une désérialisation de cette instance est alors lancée et affichage de l'état des valeurs de l'ancienne instance et de celle qui a été modifiée.
MainClass.java
Et la classe sérialisable
Personnaliser la sérialisation
Première solution:
Ajouter deux méthodes dans la classe sérailisable:
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {}
private void writeObject(ObjectOutptStream oos) throws IOException {}
A l'exécution, la machine Java regarde si ces méthodes existent dans la classe Serializable. Si c'est le cas, elle utilisera ces méthodes plutôt que les siennes.
Pas de fantaisie sur
le nom des méthodes et le typage, sinon cela ne marchera pas ! La machine
java explorant la classe à sérializer par introspection pour trouver
ces méthodes.
il reste à utiliser ois ou oos pour charger ou sauver les données que vous désirez (transient n'a alors plus grand intérêt ici !) avec les méthodes correspondantes au typage des données.
Seconde solution:
A venir...
Note
Pour les développeurs sous Android, suivant les cas (par exemple rotation de l'écran), il peut être nécessaire de sauver les instances de vos objets. il existe une solution plus simple: onSaveInstance