1. Introduction
En Java, la sérialisation ne fonctionne qu’avec les objets qui l’ont explicitement autorisée. Pour cela, la classe doit implémenter une interface spéciale — java.io.Serializable.
import java.io.Serializable;
public class Person implements Serializable {
// Champs, constructeurs, méthodes
}
Serializable est une interface marqueur : elle n’a pas de méthodes, elle indique simplement à la JVM — « cette classe peut être sérialisée, n’ayez pas peur ! ». Si vous essayez de sérialiser un objet d’une classe qui n’implémente pas Serializable, vous obtiendrez l’exception NotSerializableException. Même si au moins un champ (ou un objet imbriqué) n’est pas sérialisable, la sérialisation échouera.
ObjectOutputStream et ObjectInputStream
- ObjectOutputStream — classe qui écrit des objets dans un flux (par exemple, dans un fichier ou via le réseau).
- ObjectInputStream — classe qui lit des objets depuis un flux.
Ils fonctionnent en paire : l’un sérialise l’objet, l’autre le désérialise.
Méthodes principales
- writeObject(Object obj) — sérialise l’objet et l’écrit dans le flux.
- readObject() — lit un objet depuis le flux, le désérialise et le renvoie.
Important : les deux classes fonctionnent au-dessus des flux d’E/S classiques (OutputStream et InputStream). Le plus souvent, elles sont utilisées avec des flux de fichiers — FileOutputStream et FileInputStream — mais on peut aussi les utiliser avec des flux réseau.
2. Exemple de sérialisation
Écrivons un exemple simple : nous allons sérialiser et désérialiser un objet de la classe Person.
Étape 1. Définir la classe
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
// le champ transient ne sera pas sérialisé
private transient String secret;
public Person(String name, int age, String secret) {
this.name = name;
this.age = age;
this.secret = secret;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + ", secret='" + secret + "'}";
}
}
Étape 2. Sérialiser un objet dans un fichier
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class SerializeDemo {
public static void main(String[] args) throws Exception {
Person person = new Person("Alice", 30, "likes pizza");
// On crée un flux pour écrire dans un fichier
FileOutputStream fileOut = new FileOutputStream("person.bin");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
// On enregistre l'objet
out.writeObject(person);
// On ferme les flux
out.close();
fileOut.close();
System.out.println("L'objet a été sérialisé dans le fichier person.bin");
}
}
Étape 3. Désérialiser un objet depuis un fichier
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class DeserializeDemo {
public static void main(String[] args) throws Exception {
// On ouvre un flux pour lire depuis le fichier
FileInputStream fileIn = new FileInputStream("person.bin");
ObjectInputStream in = new ObjectInputStream(fileIn);
// On restaure l'objet
Person person = (Person) in.readObject();
in.close();
fileIn.close();
System.out.println("Objet désérialisé : " + person);
}
}
Sortie attendue
L'objet a été sérialisé dans le fichier person.bin
Objet désérialisé : Person{name='Alice', age=30, secret='null'}
Attention ! Le champ transient n’est pas sérialisé. Après désérialisation, il vaudra null. C’est important pour les données temporaires ou sensibles.
3. Limitations et particularités
Tous les champs doivent être sérialisables
Si une classe possède des champs qui n’implémentent pas eux-mêmes Serializable (ou contiennent de tels objets), la sérialisation se terminera par une erreur. Par exemple, les champs de type Thread ou Socket ne peuvent pas être rendus « simplement » sérialisables.
Champs statiques et transient
- Champs statiques (static) non sérialisés : ils appartiennent à la classe et non à une instance précise.
- Champs transient — marqués transient, ils sont explicitement exclus de la sérialisation et, après restauration, reçoivent les valeurs par défaut (null, 0, etc.).
Exceptions
- Tenter de sérialiser un objet qui n’implémente pas Serializable provoque une NotSerializableException.
- Lors de la désérialisation, des erreurs peuvent se produire : fichier introuvable, incompatibilité des classes, données corrompues, etc.
Versions des classes
Si vous modifiez la structure d’une classe après la sérialisation (par exemple, ajout/suppression de champs), une InvalidClassException peut survenir lors de la désérialisation. Pour le contrôle de version, on utilise le champ spécial serialVersionUID (nous en parlerons plus en détail dans l’une des prochaines leçons).
4. Pratique : sérialisation et désérialisation d’un objet dans un fichier
Supposons que nous ayons une classe Person et que nous voulions enregistrer une liste de personnes dans un fichier et la relire ensuite.
Classe Person (sérialisable)
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
private transient String secret; // ne sera pas sérialisé
public Person(String name, int age, String secret) {
this.name = name;
this.age = age;
this.secret = secret;
}
@Override
public String toString() {
return name + " (" + age + "), secret: " + secret;
}
}
Sérialisation d’une liste de personnes
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
public class SerializeListDemo {
public static void main(String[] args) throws Exception {
List<Person> people = new ArrayList<>();
people.add(new Person("Alice", 30, "likes pizza"));
people.add(new Person("Bob", 25, "hates broccoli"));
FileOutputStream fileOut = new FileOutputStream("people.bin");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
// On enregistre la liste
out.writeObject(people);
out.close();
fileOut.close();
System.out.println("La liste de personnes a été sérialisée.");
}
}
Désérialisation d’une liste de personnes
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.List;
public class DeserializeListDemo {
public static void main(String[] args) throws Exception {
FileInputStream fileIn = new FileInputStream("people.bin");
ObjectInputStream in = new ObjectInputStream(fileIn);
// On restaure la liste
List<Person> people = (List<Person>) in.readObject();
in.close();
fileIn.close();
for (Person p : people) {
System.out.println(p);
}
}
}
Résultat :
Alice (30), secret: null
Bob (25), secret: null
5. Erreurs courantes
Erreur n° 1 : La classe n’implémente pas Serializable. Si vous oubliez d’ajouter implements Serializable, vous obtiendrez une NotSerializableException lors de la tentative de sérialisation. C’est l’erreur la plus fréquente et la plus simple.
Erreur n° 2 : Champ non sérialisable. Si l’objet contient un champ qui n’est pas sérialisable (par exemple, Thread, Socket ou tout autre type sans Serializable), la sérialisation « plantera ». Marquez ces champs transient ou rendez-les sérialisables.
Erreur n° 3 : Modification de la structure de la classe. Si l’objet a été sérialisé, puis que la classe a changé (ajout/suppression de champs), une InvalidClassException peut se produire à la lecture. Indiquez un serialVersionUID pour un contrôle de version stable.
Erreur n° 4 : Tentative de sérialiser des champs statiques. Les champs avec le modificateur static ne sont pas sérialisés. Après désérialisation, leurs valeurs seront celles définies par défaut dans la classe, et non celles qu’ils avaient au moment de la sérialisation.
Erreur n° 5 : Flux non fermés. Si vous ne fermez pas les flux après utilisation, vous pouvez obtenir un fichier corrompu ou une fuite de ressources. Utilisez try-with-resources ou fermez explicitement les flux.
Erreur n° 6 : Inadéquation des classes. Si la classe a été renommée ou déplacée dans un autre package, la désérialisation ne fonctionnera pas — le nom et le package de la classe enregistrée dans le flux doivent correspondre exactement.
GO TO FULL VERSION