CodeGym /Cours /JAVA 25 SELF /Introduction à la sérialisation d’objets : pourquoi ...

Introduction à la sérialisation d’objets : pourquoi c’est utile

JAVA 25 SELF
Niveau 42 , Leçon 0
Disponible

1. Pourquoi utiliser la sérialisation

Imaginez que votre objet soit l’ensemble des affaires que vous emportez en vacances. La sérialisation, c’est l’emballage de tout le contenu de la valise dans un conteneur spécial que l’on peut mettre en soute ou expédier par la poste. La désérialisation, en conséquence, c’est le déballage de ce conteneur pour retrouver les objets dans leur état initial.

En substance, la sérialisation transforme un objet en flux d’octets que l’on peut enregistrer dans un fichier, transmettre sur le réseau ou simplement conserver en mémoire. La désérialisation fait l’inverse : elle reconstruit l’objet à partir de ce flux. Pour simplifier à l’extrême, la sérialisation, c’est comme « geler » un objet pour ensuite le « décongeler » et le récupérer dans le même état.

Sauvegarder l’état des objets entre les exécutions du programme

L’un des scénarios les plus fréquents est la sauvegarde de l’état du programme. Par exemple, vous avez une liste d’utilisateurs, des résultats de jeu ou des paramètres d’application. Tout cela est pratique à stocker directement sous forme d’objets. Pour que les données ne se perdent pas entre les lancements, on les sérialise dans un fichier, puis au lancement suivant on les désérialise.

Un bon exemple est une sauvegarde dans un jeu. Lorsqu’un joueur termine un niveau, sa progression est « gelée » et écrite dans un fichier à l’aide de la sérialisation. Le lendemain, il relance le jeu et la progression est « décongelée » : les données du fichier redeviennent des objets et le joueur reprend exactement là où il s’était arrêté.

Créons une sauvegarde simple :

import java.io.*;

// La classe du joueur doit être Serializable
class Player implements Serializable {
    String name;
    int score;

    Player(String name, int score) {
        this.name = name;
        this.score = score;
    }
}

public class GameSaveExample {
    public static void main(String[] args) throws Exception {
        // Nous créons un objet joueur
        Player player = new Player("Ihor", 1500);

        // --- Sauvegarde (sérialisation) ---
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("save.dat"))) {
            out.writeObject(player);
            System.out.println("Progression sauvegardée!");
        }

        // --- Chargement (désérialisation) ---
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("save.dat"))) {
            Player loaded = (Player) in.readObject();
            System.out.println("Progression chargée: " + loaded.name + " avec le score " + loaded.score);
        }
    }
}

Note : pour que ce code fonctionne, la classe Player doit implémenter l’interface Serializable. Plus de détails à son sujet dans la prochaine leçon !

  • Player — une classe ordinaire avec les champs nom et score, marquée par l’interface Serializable (implements Serializable).
  • ObjectOutputStream écrit l’objet dans le fichier "save.dat".
  • ObjectInputStream lit ce même objet en retour.
  • Au final, on obtient une véritable sauvegarde : au prochain lancement, le programme chargera l’objet joueur avec le même état.

Transmission d’objets sur le réseau et entre des JVM

Dans les systèmes distribués, il est souvent nécessaire de transmettre des objets entre différents programmes ou même différentes machines. Par exemple, vous avez un client et un serveur qui doivent échanger des messages. La sérialisation permet « d’emballer » un objet d’un côté, de l’envoyer sur le réseau et de le « déballer » de l’autre côté.

Exemple : le client envoie au serveur un objet de commande (Order), le serveur le reçoit, le désérialise et le traite.

Utilisation dans les technologies Java

  • RMI (Remote Method Invocation) : permet d’appeler les méthodes d’objets distants — la sérialisation est nécessaire pour transmettre les arguments et les valeurs de retour.
  • Sessions HTTP : dans les servlets, les objets de session sont sérialisés lors du redémarrage du conteneur.
  • JMS (Java Message Service) : les messages entre composants peuvent être sérialisés.
  • Mise en cache : les objets peuvent être sérialisés pour être stockés en cache (sur disque ou dans un stockage distribué).

Mise en cache et portabilité

Si vous souhaitez enregistrer rapidement des résultats intermédiaires (par exemple pour la mise en cache), la sérialisation est un excellent outil. Vous sérialisez un objet, vous l’enregistrez sur disque ou en mémoire, puis vous le restaurez rapidement sans refaire les calculs.

2. Exemples de scénarios d’utilisation de la sérialisation

Sauvegarder une collection d’utilisateurs dans un fichier

Supposons que vous ayez une classe User :

public class User {
    String name;
    int age;
    // ... autres champs
}

Et vous avez une liste d’utilisateurs :

List<User> users = new ArrayList<>();
users.add(new User("John", 25));
users.add(new User("Mary", 30));
// ... et ainsi de suite

Pour enregistrer cette liste dans un fichier, vous la sérialisez. Lorsque nécessaire, vous la désérialisez et obtenez la même liste avec les mêmes utilisateurs. Rappelons que la classe User (et tous ses champs) doit prendre en charge la sérialisation, c’est‑à‑dire implémenter Serializable.

Transmettre un message entre client et serveur

Un exemple classique est le chat. L’utilisateur écrit un message, l’objet Message est sérialisé et envoyé sur le réseau. Le serveur reçoit le flux d’octets, désérialise l’objet, le traite et, éventuellement, le retransmet.

import java.io.*;
import java.net.*;

// Le message doit être Serializable
class Message implements Serializable {
    String text;

    Message(String text) {
        this.text = text;
    }
}

// Serveur
class Server {
    public static void main(String[] args) throws Exception {
        try (ServerSocket serverSocket = new ServerSocket(5000)) {
            System.out.println("Le serveur attend une connexion...");
            Socket socket = serverSocket.accept();
            System.out.println("Le client s'est connecté!");

            try (ObjectInputStream in = new ObjectInputStream(socket.getInputStream())) {
                Message msg = (Message) in.readObject();
                System.out.println("Message reçu: " + msg.text);
            }
        }
    }
}

// Client
class Client {
    public static void main(String[] args) throws Exception {
        try (Socket socket = new Socket("localhost", 5000)) {
            try (ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream())) {
                Message msg = new Message("Salut, serveur!");
                out.writeObject(msg);
                System.out.println("Message envoyé!");
            }
        }
    }
}

Comment ça marche :

  1. On lance d’abord Server (il attend une connexion).
  2. Puis on lance Client (il se connecte à "localhost:5000").
  3. Le client sérialise l’objet Message et l’envoie via le socket.
  4. Le serveur reçoit le flux d’octets, le désérialise et affiche le texte.

Ici, nous utilisons des sockets (ServerSocket, Socket) — c’est un mécanisme d’interaction réseau que vous étudierez plus tard. L’important pour l’instant n’est pas le détail du réseau, mais l’idée même : le client crée un objet Message, le sérialise et l’envoie ; le serveur reçoit un flux d’octets, le désérialise en objet et affiche le message. Ainsi, même si les classes ServerSocket et Socket ne sont pas encore claires, l’exemple montre la valeur de la sérialisation : grâce à elle, on peut « emballer » un objet, le transmettre sur le réseau et le déballer de l’autre côté sans conversions superflues.

Mise en cache d’objets

Dans les grandes applications, on utilise souvent la mise en cache pour accélérer l’exécution. Par exemple, les résultats de calculs complexes sont sérialisés et enregistrés dans un cache (fichier, base de données, stockage distribué). À la requête suivante, on peut restaurer rapidement le résultat en désérialisant l’objet.

import java.io.*;

// Le résultat de calcul que nous voulons mettre en cache
class Result implements Serializable {
    int value;

    Result(int value) {
        this.value = value;
    }
}

public class CacheExample {
    private static final String CACHE_FILE = "cache.dat";

    public static void main(String[] args) throws Exception {
        Result result;

        // Vérifier si un cache existe
        File file = new File(CACHE_FILE);
        if (file.exists()) {
            // Charger le résultat depuis le cache
            try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(file))) {
                result = (Result) in.readObject();
                System.out.println("Chargé depuis le cache: " + result.value);
            }
        } else {
            // Calcul "lourd" (pour l'exemple, simplement le carré d'un nombre)
            int x = 12345;
            System.out.println("Calcul en cours... (c'est long)");
            result = new Result(x * x);

            // Enregistrer le résultat dans le cache
            try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file))) {
                out.writeObject(result);
                System.out.println("Enregistré dans le cache: " + result.value);
            }
        }
    }
}

3. Limites et risques de la sérialisation

La sérialisation est un outil puissant, mais elle comporte des écueils. Voyons les principales limites et risques.

Tous les objets ne sont pas sérialisables

En Java, tous les objets ne sont pas sérialisables « prêts à l’emploi ». Par exemple, les objets liés à des ressources externes (fichiers, connexions réseau, flux d’entrée/sortie) ne sont pas sérialisables. C’est logique : il est impossible de sérialiser un « fichier ouvert » ou une connexion réseau « vivante » — leur état dépend du système d’exploitation et de l’environnement d’exécution.

Exemple : une classe avec un champ de type FileInputStream ne peut pas être sérialisée — une erreur surviendra lors de la tentative de sérialisation.

Questions de sécurité

La sérialisation est une source potentielle de failles de sécurité. Si vous désérialisez des données provenant d’une source non fiable (par exemple d’Internet), un attaquant peut fournir un flux d’octets malveillant entraînant un comportement inattendu de votre programme, voire l’exécution de code malveillant.

Règle : Ne désérialisez jamais des données provenant de sources non fiables ! C’est comme accepter un colis d’un expéditeur inconnu — il peut contenir n’importe quoi.

Compatibilité des versions

Si vous modifiez la structure d’une classe (par exemple, en ajoutant ou supprimant un champ), les objets sérialisés auparavant peuvent devenir incompatibles avec la nouvelle version de la classe. Cela peut entraîner des erreurs lors de la désérialisation. Cette question sera détaillée dans les leçons suivantes.

Performances

La sérialisation binaire en Java est assez rapide, mais parfois pas la plus compacte et pas toujours pratique pour l’échange avec d’autres langages de programmation. Pour l’échange avec des systèmes externes, on utilise souvent des formats texte (JSON, XML).

4. Erreurs typiques lors des premiers pas avec la sérialisation

Erreur n° 1 : tentative de sérialiser un objet qui n’implémente pas l’interface Serializable.
En conséquence, vous obtiendrez une exception NotSerializableException. N’oubliez pas d’indiquer explicitement implements Serializable dans la classe et de vérifier que tous les champs sont eux aussi sérialisables !

Erreur n° 2 : sérialisation d’objets avec des champs non sérialisables.
Si votre classe contient un champ d’un type qui ne prend pas en charge la sérialisation (par exemple, un flux ou une connexion à une base de données), la sérialisation échouera. Solution : marquer ces champs transient (nous y reviendrons plus tard).

Erreur n° 3 : désérialiser des données provenant de sources non fiables.
Cela peut conduire à des vulnérabilités de sécurité ou même à l’exécution de code malveillant. Ne faites confiance qu’aux données sérialisées par votre propre programme !

Erreur n° 4 : modifications de la structure de la classe après la sérialisation.
Si vous avez enregistré un objet, puis ajouté ou supprimé un champ dans la classe, une erreur peut survenir lors de la tentative de désérialisation ou des valeurs « étranges » peuvent apparaître. Plus de détails — dans les prochaines leçons.

Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION