FAQ Fichiers, flux et réseauxConsultez toutes les FAQ
Nombre d'auteurs : 15, nombre de questions : 95, dernière mise à jour : 21 mars 2016 Ajouter une question
Cette FAQ a été réalisée à partir des questions fréquemment posées sur le forum Java de http://java.developpez.com ainsi que l'expérience personnelle des auteurs.
Nous tenons à souligner que cette FAQ ne garantit en aucun cas que les informations qu'elle propose sont correctes. Les auteurs font leur maximum, mais l'erreur est humaine. Cette FAQ ne prétend pas non plus être complète. Si vous trouvez une erreur, ou que vous souhaitez nous aider en devenant rédacteur, lisez ceci.
Sur ce, nous vous souhaitons une bonne lecture.
- Comment sérialiser un objet ?
- Comment récupérer un objet sérialisé ?
- Si je fais évoluer ma classe, pourrai-je relire les objets déjà sérialisés ?
- Que faire si je n'avais pas mis de serialVersionUID et que ma classe a changé ?
- Comment empêcher la sérialisation d'un membre de ma classe ?
- Comment gérer sa propre sérialisation ?
- Comment gérer les super-classes non sérialisables ?
- Comment vérifier l'état de l'objet après la désérialisation ?
- Comment sérialiser un Bean en XML ?
- Comment sérialiser un objet en XML ?
- Comment sérialiser un objet en JSON ?
Le but est de la sérialisation est de sauvegarder une instance d'un objet particulier.
Attention : l'objet à sérialiser, de même que ses sous-membres, doit implémenter l'interface java.io.Serializable !
Dans cet exemple, nous allons sauvegarder des options paramétrables par l'utilisateur :
Code Java : | Sélectionner tout |
1 2 3 4 | Options options = new Options(); // Notre classe implémente Serializable. try (ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("options.tmp"))) { output.writeObject(options); } |
Si votre objet – ou un de ses sous-membres – n’implémente pas l'interface java.io.Serializable, cette méthode générera une exception de type java.io.NotSerializableException.
Note : il est possible de sauvegarder plusieurs objets dans le même flux.
L'instance d'un objet sérialisé (c'est-à-dire, sauvegardé à l'aide de l'interface java.io.Serializable) se récupère comme suit :
Code Java : | Sélectionner tout |
1 2 3 | try (ObjectInputStream input = new ObjectInputStream(new FileInputStream("options.tmp"))) { Options options = (Options) input.readObject(); } |
Si la classe que vous tentez de désérialiser n'est pas disponible sur le CLASSPATH, cette méthode générera une exception de type java.lang.ClassNotFoundException.
Note : il est possible de lire plusieurs objets depuis le même flux.
Oui, si vous prenez la précaution d'indiquer la valeur du membre statique serialVersionUID dans votre classe, et que cette valeur reste constante. Définir cette valeur protège des évolutions mineures telles que l'ajout ou le retrait d'attributs ou méthodes.
Par exemple :
Code Java : | Sélectionner tout |
1 2 3 4 5 6 7 | package test; public final class MaClasse implements java.io.Serializable { private static final long serialVersionUID = 1515L; [...] } |
L'outil serialver inclus dans le JDK offre le calcul d'un serialVersionUID à partir de l'état présent d'une classe :
Code Console : | Sélectionner tout |
1 2 | $ serialver -classpath build/classes test.MaClasse test.MaClasse: static final long serialVersionUID = -4630845128859230043L; |
Il vous est alors possible de recopier la valeur obtenue dans la définition de la classe.
Si vous avez modifié la définition de votre classe sans y avoir inclus de serialVersionUID, lisez bien le message d'erreur de l'exception qui est générée lorsque vous tentez de désérialiser la classe. Ce message mentionne une valeur de serialVersionUID ; il vous suffit alors d'affecter cette valeur à la nouvelle version de la classe.
Par exemple, si la désérialisation renvoie ce message d'erreur :
Code Console : | Sélectionner tout |
Exception in thread "main" java.io.InvalidClassException: test.MaClasse; local class incompatible: stream classdesc serialVersionUID = -2335767472728093557, local class serialVersionUID = -4630845128859230043
Il vous suffit alors de déclarer dans votre classe :
Code Java : | Sélectionner tout |
private static final long serialVersionUID = -2335767472728093557L;
Vous devriez désormais être en mesure de la désérialiser.
Vous pouvez empêcher la sérialisation d'un membre de votre classe en utilisant le mot-clé transient.
Code Java : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | public class MaClasse implements java.io.Serializable { private static final long serialVersionUID = 1515L; // Entier transient. public transient int var1 = 4; // Entier normal. public int var2 = 19; } |
Ici, le membre var1 ne sera pas sérialisé et, lors de la désérialisation, elle prendra la valeur 0, malgré la présence de la valeur par défaut 4. L'attribution d'une valeur par défaut se fait lors de l'instanciation de l'objet ! Or, la méthode consistant à lire un objet depuis un fichier ne crée pas cette instance explicitement. Donc var1 n'est jamais initialisé avec sa valeur par défaut. De plus, comme cet attribut est transient, il n'est pas écrit dans le fichier. Cela implique que var1 ne reçoit aucune valeur et contient donc 0.
Ce mot-clé trouve des applications dès lors qu'une donnée sensible ne doit en aucun cas apparaître dans un fichier. Un mot de passe par exemple. Mais ce mot-clé peut également permettre de « remettre à zéro » certaines valeurs. Dans le cas d'un jeu, on pourra ainsi ne pas sauvegarder le temps de jeu depuis le début de la partie.
Méthodes customisables
Il est possible de définir quatre méthodes distinctes dans notre classe de manière à customiser la sérialisation et la désérialisation d'une classe :
- private void writeObject(ObjectOutputStream) - si elle est présente, cette méthode est utilisée pour sérialiser l'objet dans le flux ;
- private void readObject(ObjectInputStream) - si elle est présente, cette méthode est utilisée pour désérialiser l'objet depuis le flux ;
- private Object writeReplace() - si elle est présente, cette méthode sera invoquée après la sérialisation et l'objet produit sera écrit sur le flux ;
- private Object readReplace() - si elle est présente, cette méthode sera invoquée après la désérialisation et l'objet produit sera retourné à la méthode appelante. Cette méthode permet d’implémenter la désérialisation du patron de conception singleton.
Code Java : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public class MaClass implements Serializable { private static final long serialVersionUID = 1515L; private int value = 25; private void writeObject(ObjectOutputStream out) throws IOException { // Sérialisation de l'objet. out.writeInt(value); [...] } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { // Désérialisation de l'objet. // Lecture des membres dans le même ordre que celui dans lequel ils ont été écrits dans le flux. value = in.readInt(); [...] } } |
Attention : les méthodes writeObject() et readObject() ne sont pas invoquées si leur niveau d’accès n'est pas private. Ici, les implémentations de la sérialisation et de la désérialisation restent cachées aux autres classes et aux classes filles.
java.io.Externalizable
Il est également possible de customiser la sérialisation et la désérialisation d'une classe en implémentant l'interface java.io.Externalizable et en implémentant les méthodes writeExternal() et readExternal() définies dans cette interface, ce qui permet plus de contrôles sur les valeurs enregistrées et lues dans le flux.
Code Java : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public class MaClass implements Externalizable { private static final long serialVersionUID = 1515L; private int value = 25; @Override public void writeExternal(ObjectOutput out) throws IOException { // Sérialisation de l'objet. out.writeInt(value); [...] } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { // Désérialisation de l'objet. // Lecture des membres dans le même ordre que celui dans lequel ils ont été écrits dans le flux. value = in.readInt(); [...] } } |
Ces méthodes seront invoquées par les flux d'objets lors de la sérialisation et de la désérialisation. Cette manière de faire a le désavantage de rendre visible le processus de sérialisation/désérialisation aux autres classes et aux classes filles.
Un problème risque de se présenter à vous lorsque votre classe sérialisable hérite d'une classe qui ne l'est pas. Par exemple :
Code Java : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 | public class MaSuperClasse { protected int val1 = 10; protected int val2 = 55; } public class MaClasse extends MaSuperClasse implements Serializable { private static final long serialVersionUID = 1515L; private int val3 = -15; } |
En effet, lors de la déserialisation, seul val3 contiendra une valeur correcte. Cela est dû au fait que la classe mère n'est pas sérialisable et donc le contexte de val1 et val2 n'est pas sauvegardé ou restauré. Il est possible de corriger ce souci en sérialisant/désérialisant manuellement l’état de la super-classe en définissant les méthodes writeObject() et readObject() dans notre classe sérialisable :
Code Java : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public class MaClasse extends MaSuperClasse implements Serializable { private static final long serialVersionUID = 1515L; private int val3 = -15; private void writeObject(ObjectOutputStream out) throws IOException { // Sauvegarde du contexte de la classe sérialisable. out.defaultWriteObject(); // Sauvegarde manuelle des éléments de la classe mère. out.writeInt(val1); out.writeInt(val2); } private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException { // Restauration du contexte de la classe sérialisable. in.defaultReadObject(); // Restauration manuelle des éléments de la classe mère. // Lecture des membres dans le même ordre que celui dans lequel ils ont été écrits dans le flux. val1 = in.readInt(); val2 = in.readInt(); } } |
Nous pouvons invoquer les méthodes defaulWriteObject() sur l'instance d'ObjectOutputStream et defaultReadObject() sur l'instance d'ObjectInputStream pour gérer automatiquement l’état des variables de MaClasse, ce qui évite de devoir manuellement sauvegarder et restaurer val3. Il ne nous reste plus alors qu'à écrire la sérialisation et la désérialisation manuelle de val1 et val2 qui appartiennent à la classe mère.
Pour vérifier l’état de l'objet après la désérialisation, vous pouvez implémenter l'interface java.io.ObjectInputValidation et définir la méthode validateObject() qui permet de tester l’état de l'objet venant d’être restauré. Il vous faut ensuite enregistrer votre valideur sur l'instance d'ObjectInputStream au moment de la lecture dans la méthode readObject().
Code Java : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public class MaClasse implements Serializable, ObjectInputValidation { private static final long serialVersionUID = 1515L; public int value = 8; private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException { // Enregistrement du valideur avec la priorité 0. in.registerValidation(this, 0); // Lecture. in.defaultReadObject(); // La validation sera effectuée lorsque nous quittons cette méthode. } @Override public void validateObject() throws InvalidObjectException { // Effectue des tests de validation. if (value == 0) { throw new InvalidObjectException("Value invalide !"); } } } |
Ici, la méthode validateObject() qui sert à tester la validation sera invoquée après que le processus de désérialisation a quitté la méthode readObject(). Le niveau de priorité spécifié influence l'ordre d'invocation des valideurs lorsqu'il y en a plusieurs qui sont enregistrés sur le flux.
Il est possible de sauvegarder et de restaurer l’état d'objets conformes à la norme Java Beans de plusieurs manières. La classe à sauvegarder ne doit pas nécessairement implémenter l'interface java.io.Serializable et toutes les méthodes utilisables dans le mécanisme de sérialisation classique sont complètement ignorées.
Attention : contrairement à une sérialisation classique qui sauvegarde tous les membres de l'objet, ici, seuls les membres qui répondent à au moins un des critères suivants seront sauvegardés dans le fichier XML :
- membres en accès public ;
- membres disposant à la fois d'un getter et d'un setter.
Ainsi, un membre private ne disposant pas de getter ou de setter sera totalement ignoré, de même qu'un membre protected qui ne dispose que d'un getter ou que d'un setter. Ces méthodes de sauvegarde de l'objet ne sauvegardent pas non plus les écouteurs qui étaient enregistrés sur les diverses propriétés du bean.
Par exemple :
Code Java : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | package test; public class MaClasse { public int val0; private int val1; private int val2; private int val3; public int getVal1() { return val1; } public void setVal1(int value) { this.val1 = value; } public int getVal2() { return val2; } public void setVal3(int val3) { this.val3 = val3; } } |
Nous avons :
- val0 est en accès public ;
- val1 est en accès private avec un getter et un setter ;
- val2 est en accès private avec un getter ;
- val3 est en accès private avec un setter.
XMLEncoder & XMLDecoder
À partir du JDK 1.4, il est possible de sauvegarder et de restaurer l’état d'un bean en utilisant respectivement les classes java.bean.XMLEncoder et java.bean.XMLDecoder. La manière de procéder est similaire à celle de la sérialisation et de désérialisation classique.
La classe java.bean.XMLEncoder permet d’écrire du XML à partir de notre objet :
Code Java : | Sélectionner tout |
1 2 3 | try (XMLEncoder output = new XMLEncoder(new FileOutputStream("fichier.xml"))) { output.writeObject(monObjet); } |
La classe java.bean.XMLDecoder permet de créer un nouvel objet à partir du contenu du XML :
Code Java : | Sélectionner tout |
1 2 3 | try (XMLDecoder input = new XMLDecoder(new FileInputStream("fichier.xml"))) { final MaClasse monObject = (MaClasse) input.readObject(); } |
Si nous faisons :
Code Java : | Sélectionner tout |
1 2 3 4 5 6 7 | MaClasse monObjet = new MaClasse(); monObjet.val0 = 98; monObjet.setVal1(105); monObjet.setVal3(481); try (XMLEncoder output = new XMLEncoder(new FileOutputStream("fichier.xml"))) { output.writeObject(monObjet); } |
Ceci nous donnera un fichier XML similaire à :
Code XML : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <?xml version="1.0" encoding="UTF-8"?> <java version="1.8.0_51" class="java.beans.XMLDecoder"> <object class="testfor.MaClasse" id="MaClasse0"> <void class="testfor.MaClasse" method="getField"> <string>val0</string> <void method="set"> <object idref="MaClasse0"/> <int>98</int> </void> </void> <void property="val1"> <int>105</int> </void> </object> </java> |
Ici, seuls les membres val0 (qui est en accès public) et val1 (qui a un getter et un setter) sont sauvegardés dans le fichier XML.
API JAXB
À partir du JDK 6, il est possible de sauvegarder et de restaurer l’état d'un bean en utilisant l'API JAXB (Java Architecture for XML Binding ou Architecture Java pour les liaisons XML).
Tout d'abord nous devons modifier légèrement la définition de notre classe pour la marquer avec l'annotation javax.xml.bind.annotation.XmlRootElement :
Code Java : | Sélectionner tout |
1 2 3 4 5 6 7 | package test; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement public class MaClasse { [...] |
Il nous faudra ensuite créer un contexte de type javax.xml.bind.JAXBContext en faisant :
Code Java : | Sélectionner tout |
JAXBContext context = JAXBContext.newInstance(MaClasse.class);
Nous pouvons invoquer la méthode createMarshaller() du contexte pour obtenir un objet de type javax.xml.bind.Marshaller qui se chargera de faire la transformation en XML en invoquant sa méthode marshal() :
Code Java : | Sélectionner tout |
1 2 3 4 5 | Marshaller marshaller = context.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); try (OutputStream output = new FileOutputStream("fichier.xml")) { marshaller.marshal(monObjet, output); } |
Nous pouvons invoquer la méthode createUnmarshaller() du contexte pour obtenir un objet de type javax.xml.bind.Unmarshaller qui se chargera de créer notre objet à partir du contenu du XML en invoquant sa méthode unmarshal() :
Code Java : | Sélectionner tout |
1 2 3 4 | Unmarshaller unmarshaller = context.createUnmarshaller(); try (InputStream input = new FileInputStream("fichier.xml")) { final MaClasse monObjet = (MaClasse) unmarshaller.unmarshal(input); } |
Par exemple, si nous faisons :
Code Java : | Sélectionner tout |
1 2 3 4 5 6 7 | MaClasse monObjet = new MaClasse(); monObjet.val0 = 98; monObjet.setVal1(105); monObjet.setVal3(481); try (OutputStream output = new FileOutputStream("fichier.xml")) { marshaller.marshal(monObjet, output); } |
Ceci nous donnera un fichier XML similaire à :
Code XML : | Sélectionner tout |
1 2 3 4 5 | <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <maClasse> <val0>98</val0> <val1>105</val1> </maClasse> |
Ici, aussi seuls les membres val0 et val1 sont sauvegardés dans le fichier XML.
Il est possible de sérialiser/désérialiser entièrement un objet quelconque en XML en utilisant la bibliothèque tierce XStream. La classe à sauvegarder ne doit pas nécessairement implémenter l'interface java.io.Serializable et toutes les méthodes utilisables dans le mécanisme de sérialisation classique sont complètement ignorées. Cependant, ici, tous les membres de la classe, même ceux en accès private ou sans getter ou sans setter seront sauvegardés et restaurés.
Tout d'abord, il nous faut obtenir un contexte XStream :
Code Java : | Sélectionner tout |
XStream xstream = new XStream();
Pour l’écriture, il suffit d'invoquer l'une des variantes de la méthode toXML() du contexte :
Code Java : | Sélectionner tout |
1 2 3 | try (FileOutputStream output = new FileOutputStream("fichier.xml")) { xstream.toXML(monObjet, output); } |
Pour la lecture, il suffit d'invoquer l'une des variantes de la méthode fromXML() du contexte :
Code Java : | Sélectionner tout |
1 2 3 | try (FileInputStream output = new FileInputStream("fichier.xml")) { MaClasse monObjet = (MaClasse) xstream.fromXML(output); } |
Par exemple, reprenons notre classe toute simple :
Code Java : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | package test; public class MaClasse { public int val0; private int val1; private int val2; private int val3; public int getVal1() { return val1; } public void setVal1(int value) { this.val1 = value; } public int getVal2() { return val2; } public void setVal3(int val3) { this.val3 = val3; } } |
Si nous faisons :
Code Java : | Sélectionner tout |
1 2 3 4 5 6 7 | MaClasse monObjet = new MaClasse(); monObjet.val0 = 98; monObjet.setVal1(105); monObjet.setVal3(481); try (FileOutputStream output = new FileOutputStream("fichier.xml")) { xstream.toXML(monObjet, output); } |
Nous obtenons un fichier XML similaire à :
Code XML : | Sélectionner tout |
1 2 3 4 5 6 | <test.MaClasse> <val0>98</val0> <val1>105</val1> <val2>0</val2> <val3>481</val3> </test.MaClasse> |
Ici, tous les membres de notre classe ont été exportés vers le fichier XML et restaurés lors de la désérialisation.
Note : XStream supporte beaucoup de paramètres optionnels ; ainsi, il est possible de configurer le contexte à sa création pour qu'il utilise des pilotes particuliers. Il est également possible de mapper des noms de classes sur des éléments spécifiques du DOM :
Code Java : | Sélectionner tout |
1 2 | xstream.alias("person", Person.class); xstream.alias("phonenumber", PhoneNumber.class); |
Il est possible de sérialiser/désérialiser entièrement un objet quelconque en JSON en utilisant la bibliothèque tierce XStream. La classe à sauvegarder ne doit pas nécessairement implémenter l'interface java.io.Serializable et toutes les méthodes utilisables dans le mécanisme de sérialisation classique sont complètement ignorées. Cependant, ici, tous les membres de la classe, même ceux en accès private ou sans getter ou sans setter seront sauvegardés et restaurés.
XStream dispose de deux pilotes permettant de manipuler du JSON :
- com.thoughtworks.xstream.io.json.JsonHierarchicalStreamDriver - permet de sérialiser un objet au format JSON.
- com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver - permet de sérialiser et désérialiser un objet au format JSON.
Tout d'abord, il nous faut obtenir un contexte XStream utilisant le pilote choisi :
Code Java : | Sélectionner tout |
XStream xstream = new XStream(new JettisonMappedXmlDriver());
Pour l’écriture, il suffit d'invoquer l'une des variantes de la méthode toXML() du contexte :
Code Java : | Sélectionner tout |
1 2 3 | try (FileOutputStream output = new FileOutputStream("fichier.json")) { xstream.toXML(monObjet, output); } |
Pour la lecture, il suffit d'invoquer l'une des variantes de la méthode fromXML() du contexte :
Code Java : | Sélectionner tout |
1 2 3 | try (FileInputStream output = new FileInputStream("fichier.json")) { MaClasse monObjet = (MaClasse) xstream.fromXML(output); } |
Par exemple, reprenons notre classe toute simple :
Code Java : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | package test; public class MaClasse { public int val0; private int val1; private int val2; private int val3; public int getVal1() { return val1; } public void setVal1(int value) { this.val1 = value; } public int getVal2() { return val2; } public void setVal3(int val3) { this.val3 = val3; } } |
Si nous faisons :
Code Java : | Sélectionner tout |
1 2 3 4 5 6 7 | MaClasse monObjet = new MaClasse(); monObjet.val0 = 98; monObjet.setVal1(105); monObjet.setVal3(481); try (FileOutputStream output = new FileOutputStream("fichier.json")) { xstream.toXML(monObjet, output); } |
Nous obtenons un fichier JSON similaire à :
Code JSON : | Sélectionner tout |
1 2 3 4 5 6 7 8 | { "test.MaClasse": { "val0": 98, "val1": 105, "val2": 0, "val3": 481 } } |
Ici, tous les membres de notre classe ont été exportés vers le fichier JSON et restaurés lors de la désérialisation.
Proposer une nouvelle réponse sur la FAQ
Ce n'est pas l'endroit pour poser des questions, allez plutôt sur le forum de la rubrique pour çaLes sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2024 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.