1. Introduction
En Java, on utilise le plus souvent l’interface Serializable pour sérialiser des objets. Elle est simple : il suffit d’implémenter l’interface, et l’objet peut être écrit/lu à l’aide de ObjectOutputStream/ObjectInputStream. Mais parfois, cela ne suffit pas :
- Il faut contrôler entièrement quels champs sont sérialisés et comment.
- Il faut garantir la compatibilité entre différentes versions de la classe.
- Il est important de réduire la taille du fichier sérialisé ou d’accélérer le processus.
Pour ces cas, Java propose l’interface Externalizable — une approche plus « manuelle » et flexible de la sérialisation.
En bref :
- Serializable — sérialisation automatique : Java décide elle‑même quoi écrire et comment.
- Externalizable — sérialisation manuelle : c’est vous qui indiquez quoi et comment sauvegarder/restaurer.
2. Contrat Externalizable : implémenter writeExternal et readExternal
Pour utiliser Externalizable, il faut :
- Implémenter l’interface java.io.Externalizable.
- Implémenter obligatoirement deux méthodes :
- void writeExternal(ObjectOutput out) throws IOException
- void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
Exemple :
import java.io.*;
public class User implements Externalizable {
private String name;
private int age;
// Constructeur public sans argument obligatoire !
public User() {}
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(name);
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name = in.readUTF();
age = in.readInt();
}
@Override
public String toString() {
return name + " (" + age + ")";
}
}
Important : le développeur décide lui‑même quels champs seront sérialisés et dans quel ordre. Mais il y a une exigence incontournable : la classe doit avoir un constructeur public sans argument. S’il est absent, la désérialisation provoquera une InvalidClassException.
3. Quand utiliser Externalizable ?
Utilisez Externalizable si :
- Vous avez besoin d’un contrôle total sur le format des données. Par exemple, vous voulez sérialiser seulement une partie des champs, ou les sérialiser dans un ordre/format particulier.
- Optimiser les performances et la taille du fichier. La sérialisation standard ajoute des informations de service (métadonnées, noms de classes, types, etc.). Avec Externalizable, vous n’écrivez que les données nécessaires.
- Assurer la rétrocompatibilité. Si la structure de la classe change, vous pouvez implémenter manuellement la logique de lecture des anciennes et des nouvelles versions des données.
- Sérialiser des objets non standards. Par exemple, si vous avez des champs qui ne peuvent pas être sérialisés de manière standard (par exemple transient, volatile ou des structures complexes).
Quand ne faut-il pas l’utiliser ?
- Si vous n’avez pas besoin d’un contrôle total — utilisez Serializable, c’est plus simple et plus sûr.
- Si vous n’êtes pas certain de pouvoir maintenir la compatibilité du format de données lors des évolutions de la classe.
4. Avantages et inconvénients de Externalizable par rapport à Serializable
Avantages :
- Contrôle total de la sérialisation. Vous décidez vous‑même quoi écrire/lire et comment.
- Compacité. Pas de métadonnées superflues — seulement vos données.
- Vitesse. Moins de données — écriture/lecture plus rapides.
- Flexibilité. Vous pouvez prendre en charge différentes versions du format, ajouter de la compression, du chiffrement, etc.
Inconvénients :
- Implémentation manuelle — l’erreur est facile. Si vous mélangez l’ordre d’écriture/lecture, la sérialisation peut « se casser » (erreur ou données incorrectes).
- Pas de prise en charge automatique de transient ni de serialVersionUID. Il faut tout concevoir et implémenter manuellement.
- Plus difficile à maintenir. En cas de modification de la structure de la classe, il ne faut pas oublier de mettre à jour les méthodes de sérialisation.
- Un constructeur public sans argument est obligatoire.
- Moins de « magie » — plus de responsabilité.
5. Exemples : sérialisation et désérialisation d’un objet simple
Sérialisation d’un objet
User user = new User("Alice", 30);
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("user.bin"))) {
out.writeObject(user);
}
Désérialisation d’un objet
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("user.bin"))) {
User loaded = (User) in.readObject();
System.out.println(loaded); // Alice (30)
}
Attention : si vous changez l’ordre d’écriture/lecture des champs, ou si vous oubliez d’en sérialiser un, les données seront incorrectes ! Les méthodes writeExternal et readExternal doivent être strictement alignées sur la même séquence d’opérations.
Exemple : ne sérialiser qu’une partie des champs
public class SecretUser implements Externalizable {
private String login;
private transient String password; // transient n'a pas d'importance pour Externalizable
public SecretUser() {}
public SecretUser(String login, String password) {
this.login = login;
this.password = password;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(login);
// Ne sérialisons pas le mot de passe !
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
login = in.readUTF();
password = null; // le mot de passe n'est pas restauré
}
}
6. Pratique : comparer la taille du fichier sérialisé
Comparons combien « pèsent » les fichiers sérialisés via Serializable et via Externalizable.
Classe avec Serializable
public class UserSerializable implements Serializable {
private String name;
private int age;
public UserSerializable(String name, int age) {
this.name = name;
this.age = age;
}
}
Classe avec Externalizable
public class UserExternalizable implements Externalizable {
private String name;
private int age;
public UserExternalizable() {}
public UserExternalizable(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(name);
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name = in.readUTF();
age = in.readInt();
}
}
Code de comparaison
import java.io.*;
public class CompareSerialization {
public static void main(String[] args) throws Exception {
UserSerializable s = new UserSerializable("Bob", 25);
UserExternalizable e = new UserExternalizable("Bob", 25);
// Serializable
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin"))) {
out.writeObject(s);
}
// Externalizable
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ext.bin"))) {
out.writeObject(e);
}
System.out.println("Serializable file size: " + new File("ser.bin").length());
System.out.println("Externalizable file size: " + new File("ext.bin").length());
}
}
Résultat :
Le fichier ser.bin (Serializable) est généralement plus grand — il contient des informations de service Java. Le fichier ext.bin (Externalizable) ne contient que vos données, il est généralement plus petit.
7. Erreurs courantes avec Externalizable
Erreur n° 1 : Absence de constructeur public sans argument.
La classe qui implémente Externalizable doit impérativement posséder un constructeur public sans argument. Sans lui, la désérialisation lèvera une InvalidClassException.
Erreur n° 2 : Non-respect de l’ordre d’écriture et de lecture des champs.
Les méthodes writeExternal et readExternal doivent opérer dans le même ordre. Si, à l’écriture, vous sauvegardez d’abord le champ name mais qu’à la lecture vous tentez d’abord de lire age, les données seront corrompues.
Erreur n° 3 : Champs omis lors de la sérialisation.
Si vous oubliez d’écrire un champ dans writeExternal, à la désérialisation il aura la valeur null (pour les types références) ou 0 (pour les numériques).
Erreur n° 4 : Mauvaise utilisation de transient ou de serialVersionUID.
Contrairement à Serializable, avec Externalizable ces mécanismes ne fonctionnent pas automatiquement — c’est à vous de contrôler quels champs sauvegarder et lesquels ne pas sauvegarder.
Erreur n° 5 : Modification de la structure de la classe sans mise à jour des méthodes.
Si vous ajoutez ou supprimez des champs sans adapter writeExternal et readExternal, les anciennes données sauvegardées peuvent ne plus se charger correctement.
GO TO FULL VERSION