CodeGym /Cours /JAVA 25 SELF /Champs transient, serialVersionUID

Champs transient, serialVersionUID

JAVA 25 SELF
Niveau 43 , Leçon 1
Disponible

1. Détails sur transient

En Java, le mot-clé transient est une façon de dire au sérialiseur : « Merci de ne pas sérialiser ce champ, ignore-le lors de l’enregistrement de l’objet ! ». Si vous déclarez un champ comme transient, il ne sera pas inclus dans le flux d’octets sérialisé. C’est particulièrement utile pour les données sensibles (par exemple, les mots de passe) ou les calculs temporaires qui n’ont pas besoin d’être sauvegardés.

Exemple : à quoi sert transient ?

Supposons que nous ayons une classe utilisateur :

import java.io.Serializable;

public class User implements Serializable {
    private String username;
    private transient String password; // Nous ne voulons pas enregistrer le mot de passe !

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    // Ici, nous avons des getters et des setters
}

Si nous sérialisons un objet de cette classe, le champ password n’ira pas dans le fichier (ou un autre flux). Cela signifie qu’à la désérialisation, le mot de passe aura sa valeur par défaut — pour les objets c’est null, pour les nombres — 0, pour booleanfalse.

Comment cela fonctionne-t-il en pratique ?

Faisons une mini-expérience. Commençons par sérialiser l’utilisateur :

import java.io.*;

public class TransientDemo {
    public static void main(String[] args) throws Exception {
        User user = new User("alice", "qwerty123");

        // On enregistre l'objet dans un fichier
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("user.ser"));
        out.writeObject(user);
        out.close();

        // Maintenant, on relit l'objet
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("user.ser"));
        User restored = (User) in.readObject();
        in.close();

        System.out.println("Username: " + restored.username);
        System.out.println("Password: " + restored.password);
    }
}

Résultat :

Username: alice
Password: null

Comme vous le voyez, le champ password n’a pas été restauré — il est transient, donc le sérialiseur l’a ignoré.

Où et pourquoi utiliser transient ?

  • Mots de passe et jetons. Ne les sérialisez jamais !
  • Données mises en cache ou temporaires. Par exemple, si vous avez un champ qui peut être calculé « à la volée ».
  • Objets qu’on ne peut pas ou ne doit pas sérialiser. Par exemple, des références à des connexions de base de données, des flux, des sockets.

Particularités du comportement des champs transient

Quand un objet est désérialisé, tous les champs marqués transient reçoivent leurs valeurs par défaut. S’il faut leur redonner du sens, vous pouvez utiliser la méthode readObject et les remplir manuellement (recalculer le cache, demander le mot de passe à l’utilisateur, etc.).

2. serialVersionUID : identifiant unique de version de la classe

serialVersionUID est un champ statique spécial de type long qui définit la « version » d’une classe sérialisable. À la sérialisation, la valeur de serialVersionUID est écrite ; à la désérialisation, la JVM la compare à la valeur de la classe actuelle. Si elles ne correspondent pas, une exception sera levée et l’objet ne sera pas restauré.

Comment déclarer serialVersionUID ?

Très simplement :

private static final long serialVersionUID = 1L;

En général, on le déclare directement dans la classe qui implémente Serializable :

import java.io.Serializable;

public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    // ... autres champs et méthodes
}

À quoi sert serialVersionUID ?

Imaginez que vous ayez sauvegardé un objet de la classe dans un fichier, puis modifié la structure de la classe (ajout d’un champ, renommage, etc.). Si le serialVersionUID diffère, la JVM considère que la classe n’est pas compatible avec l’ancienne version et ne vous laissera pas désérialiser l’objet. Cela prévient des erreurs inattendues.

Que se passe-t-il si l’on ne déclare pas serialVersionUID ?

Si vous ne déclarez pas explicitement serialVersionUID, le compilateur en générera un automatiquement — sur la base de la structure de la classe. Mais même une petite modification (par exemple, ajout ou suppression d’un champ) entraînera un changement du serialVersionUID. En conséquence, vous ne pourrez pas désérialiser des objets sauvegardés par l’ancienne version de la classe.

C’est pourquoi il est recommandé de définir toujours explicitement le serialVersionUID !

Démonstration : non-correspondance de serialVersionUID

1) Créons d’abord une classe et sérialisons un objet :

import java.io.Serializable;

public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private String username;

    public User(String username) {
        this.username = username;
    }
}

2) Puis modifions serialVersionUID :

import java.io.Serializable;

public class User implements Serializable {
    private static final long serialVersionUID = 2L; // Était 1L, devient 2L !
    private String username;

    public User(String username) {
        this.username = username;
    }
}

Résultat :

java.io.InvalidClassException: User; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2

La JVM avertit honnêtement : « Les versions sont incompatibles ! »

Quelle valeur de serialVersionUID choisir ?

Le plus souvent, on utilise des valeurs simples (1L, 2L, 42L), et dans les grands projets l’IDE génère des valeurs « longues ». L’essentiel est de ne le changer que lorsque la structure de la classe change de manière incompatible.

3. Pratique : transient et serialVersionUID en action

Exemple : une classe avec un champ transient

Modifions une application pédagogique (par exemple, un gestionnaire de contacts) et ajoutons à la classe utilisateur un champ pour stocker un jeton d’autorisation temporaire, qui ne doit pas être sérialisé.

import java.io.Serializable;

public class Contact implements Serializable {
    private static final long serialVersionUID = 1L;

    private String name;
    private String phone;
    private transient String sessionToken; // jeton temporaire

    public Contact(String name, String phone, String sessionToken) {
        this.name = name;
        this.phone = phone;
        this.sessionToken = sessionToken;
    }

    @Override
    public String toString() {
        return "Contact{" +
               "name='" + name + '\'' +
               ", phone='" + phone + '\'' +
               ", sessionToken='" + sessionToken + '\'' +
               '}';
    }
}

Essayons maintenant de sérialiser et désérialiser l’objet :

import java.io.*;

public class TransientAndSUIDDemo {
    public static void main(String[] args) throws Exception {
        Contact c = new Contact("Ivan", "+19990001122", "token-12345");

        // On enregistre l'objet
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("contact.ser"));
        out.writeObject(c);
        out.close();

        // On restaure l'objet
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("contact.ser"));
        Contact restored = (Contact) in.readObject();
        in.close();

        System.out.println("Avant la sérialisation: " + c);
        System.out.println("Après la désérialisation: " + restored);
    }
}

Sortie :

Avant la sérialisation: Contact{name='Ivan', phone='+19990001122', sessionToken='token-12345'}
Après la désérialisation: Contact{name='Ivan', phone='+19990001122', sessionToken='null'}

Comme vous le voyez, le champ sessionToken n’a pas été restauré — il est transient.

Exemple : expérience avec serialVersionUID

1) D’abord, sérialisez un objet avec serialVersionUID = 1L.
2) Ensuite, changez serialVersionUID en 2L et essayez de désérialiser le même fichier.

Résultat : vous obtiendrez une InvalidClassException, comme montré ci-dessus.

4. Pourquoi vaut-il mieux définir explicitement serialVersionUID ?

  • L’explicite vaut mieux que l’implicite. Vous contrôlez la compatibilité : si la structure de la classe n’a pas changé de façon critique, vous laissez l’ancien serialVersionUID, et les objets se désérialisent sans problème.
  • La génération automatique est risquée. Toute modification peut changer la valeur calculée et « casser » la compatibilité des données sauvegardées.
  • L’IDE aide. La plupart des IDE (par exemple, IntelliJ IDEA) savent générer automatiquement le serialVersionUID.

5. Erreurs courantes avec transient et serialVersionUID

Erreur n°1 : oubli de marquer un champ sensible comme transient.
En conséquence, des mots de passe ou des jetons se retrouvent par inadvertance dans les fichiers sérialisés. Ce n’est pas seulement gênant, c’est dangereux.

Erreur n°2 : serialVersionUID non déclaré explicitement.
La classe a été modifiée et il est désormais impossible de désérialiser les anciens objets : la JVM les considère incompatibles, alors que la structure n’a peut-être pas changé de manière critique.

Erreur n°3 : modification de serialVersionUID sans nécessité.
Si vous avez simplement ajouté un getter ou un commentaire, il n’est pas nécessaire de changer serialVersionUID — sinon, les anciennes données cesseront d’être désérialisées.

Erreur n°4 : serialVersionUID n’est pas static ou pas final.
Le champ doit être déclaré comme private static final long serialVersionUID. Sinon, la JVM ne l’interprétera pas correctement.

Erreur n°5 : oubli de restaurer un champ transient après la désérialisation.
Si la valeur est critique pour le fonctionnement de l’objet, restaurez-la dans readObject — sinon l’objet peut mal fonctionner.

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