JavaRush /Blog Java /Random-FR /Que cache le modificateur transitoire en Java ?
Анзор Кармов
Niveau 31
Санкт-Петербург

Que cache le modificateur transitoire en Java ?

Publié dans le groupe Random-FR
Bonjour! Dans l'article d'aujourd'hui, nous examinerons le modificateur transitoire en Java. Parlons de pourquoi ce modificateur est nécessaire et comment l'utiliser correctement. Aller! Que cache le modificateur transitoire en Java - 1

Rappelons-nous la sérialisation

Le modificateur transientest utilisé dans le processus de sérialisation et de désérialisation d'objets. Parlons donc brièvement de cela d’abord. Que cache le modificateur transitoire en Java - 2Supposons que nous ayons un objet et qu’il comporte des champs dont chacun a une valeur. Tout cela s’appelle l’état de l’objet. La sérialisation est la conversion de l'état d'un objet en une séquence d'octets. Ces octets sont généralement stockés dans un fichier. La désérialisation est le processus inverse. Imaginons que nous sérialisons un objet en octets et stockions cet ensemble d'octets dans un fichier. Lors de la désérialisation, le programme a besoin de :
  1. Lit un ensemble d'octets à partir d'un fichier.
  2. Construisez un objet initial à partir de cet ensemble d'octets et définissez chaque champ sur la valeur que l'objet avait au moment de la sérialisation.
Quand cela pourrait-il être utile ? Par exemple, lorsque nous voulons que le programme sauvegarde son état lors de l'arrêt et le restaure à la prochaine mise sous tension. Lorsque vous fermez IntelliJ IDEA, les mêmes onglets et classes seront probablement ouverts la prochaine fois que vous l'allumerez.

Rappelons la sérialisation en pratique

Eh bien, regardons maintenant la sérialisation en pratique. Si vous souhaitez mieux comprendre le sujet, nous vous recommandons de lire le matériel Sérialisation et désérialisation en Java . Eh bien, dans cet article, nous allons aller plus loin et passer directement aux exemples. Disons que nous avons une classe Useravec un ensemble de champs, de getters et de setters, et une méthodetoString :
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    private String firstName;
    private String lastName;
    private String email;
    private LocalDate birthDate;
    private String login;
    private String password;

    public User() {}

    public User(String firstName, String lastName, String email, LocalDate birthDate, String login, String password) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
        this.birthDate = birthDate;
        this.login = login;
        this.password = password;
    }

    /*
        Геттеры, Сеттеры
     */

    @Override
    public String toString() {
        return "User{" +
                "firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                ", email='" + email + '\'' +
                ", birthDate=" + birthDate +
                ", login='" + login + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}
Nous souhaitons sérialiser les objets de cette classe à l'avenir. Écrivons une méthode qui prend un objet Useret une chaîne path- le chemin d'accès au fichier dans lequel nous allons enregistrer les octets :
static void serialize(User user, String path) throws IOException {
    FileOutputStream outputStream = null;
    ObjectOutputStream objectOutputStream = null;
    try {
        //create 2 threads to serialize the object and save it to a file
        outputStream = new FileOutputStream(path);
        objectOutputStream = new ObjectOutputStream(outputStream);

        // сохраняем an object в файл
        objectOutputStream.writeObject(user);
    } finally {
        // Закроем потоки в блоке finally
        if (objectOutputStream != null) {
            objectOutputStream.close();
        }
        if (outputStream != null) {
            outputStream.close();
        }
    }
}
Nous écrirons également une méthode de désérialisation. La méthode prend une chaîne path(le chemin d'accès au fichier à partir duquel l'objet sera « chargé ») et renvoie un objet de type User:
static User deserialize(String path) throws IOException, ClassNotFoundException {
    FileInputStream fileInputStream = null;
    ObjectInputStream objectInputStream = null;

    try {

        //создаем 2 потока для десериализации an object из file
        fileInputStream = new FileInputStream(path);
        objectInputStream = new ObjectInputStream(fileInputStream);

        //загружаем an object из file
        return  (User) objectInputStream.readObject();
    } finally {
        if (fileInputStream != null) {
            fileInputStream.close();
        }
        if (objectInputStream != null) {
            objectInputStream.close();
        }
    }
}
Tous les outils sont prêts à l'emploi. Il est temps de diviser les octets en atomes . Écrivons une méthode maindans laquelle nous créons un objet de classe Useret le sérialisons. Ensuite, nous allons le charger et le comparer avec ce qui était à l'origine :
public static void main(String[] args) throws IOException, ClassNotFoundException {
    // вставьте свой путь до file
    final String path = "/home/zor/user.ser";

    // create our object
    User user = new User();
    user.setFirstName("Stefan");
    user.setLastName("Smith");
    user.setEmail("ssmith@email.com");
    user.setBirthDate(LocalDate.of(1991, 7, 16));
    user.setLogin("ssmith");
    user.setPassword("gemma_arterton_4ever_in_my_heart91");

    System.out.println("Initial user: " + user + "\r\n");


    serialize(user, path);
    User loadedUser = deserialize(path);
    System.out.println("Loaded user from file: " + loadedUser + "\r\n");
}
Si nous exécutons la méthode, nous verrons le résultat suivant :
Initial user: User{firstName='Stefan', lastName='Smith', email='ssmith@email.com', birthDate=1991-07-16, login='ssmith', password='gemma_arterton_4ever_in_my_heart91'}

Loaded user from file: User{firstName='Stefan', lastName='Smith', email='ssmith@email.com', birthDate=1991-07-16, login='ssmith', password='gemma_arterton_4ever_in_my_heart91'}
Comme vous pouvez le voir sur le résultat, les objets sont identiques. Mais il y a un petit mais... Et c'est exactement là que la honte espagnole transient entre en jeu .

Modificateur (enfin)transient

Quelqu'un a-t-il pensé que nous avions enregistré le mot de passe de l'utilisateur ? Surtout un tel mot de passe... Oui, oui, nous l'avons trouvé nous-mêmes, mais quand même... Parfois, il y a des situations où certains champs ne peuvent pas être sérialisés, ou il vaut mieux ne pas le faire. Dans l'exemple ci-dessus, je souhaite enregistrer tous les champs sauf le mot de passe. Comment y parvenir ? Réponse : utilisez le modificateur transient. transientest un modificateur placé avant un champ de classe (similaire à d'autres modificateurs tels que public, finaletc.) pour indiquer que le champ ne doit pas être sérialisé. Les champs marqués du mot-clé transientne sont pas sérialisés. Modifions maintenant l'exemple avec notre utilisateur pour corriger une petite confusion et ne pas enregistrer le mot de passe de l'utilisateur. Pour cela, marquez le champ correspondant dans la classe avec le mot-clé transient:
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    private String firstName;
    private String lastName;
    private String email;
    private LocalDate birthDate;
    private String login;
    private transient String password;

    /*
        Конструкторы, геттеры, сеттеры, toString...
     */
}
Si nous exécutons à nouveau la méthode de l'exemple ci-dessus main, nous verrons que le mot de passe n'est pas enregistré :
Initial user: User{firstName='Stefan', lastName='Smith', email='ssmith@email.com', birthDate=1991-07-16, login='ssmith', password='gemma_arterton_4ever_in_my_heart91'}

Loaded user from file: User{firstName='Stefan', lastName='Smith', email='ssmith@email.com', birthDate=1991-07-16, login='ssmith', password='null'}
Génial, nous avons atteint notre objectif et ne stockons pas d’informations confidentielles. Surtout ce genre d'informations... (désolé)

Quand utiliser le transitoire ?

Un exemple avec un utilisateur était nécessaire afin de plonger dans le contexte de la sérialisation. Parlons maintenant plus spécifiquement du moment où utiliser le modificateur transient.

  • Champs calculés par programme

Certaines classes comportent parfois des champs calculés en fonction d'autres champs ou d'autres informations. Ils sont calculés, pour ainsi dire, à la volée. Pour donner un exemple d’un tel domaine, imaginons une commande dans une boutique en ligne ou dans un service de livraison de nourriture. Chaque commande comprend, entre autres informations, une liste de marchandises et un coût total. Il correspond à son tour au coût total de chaque produit. Il s'avère que le coût final ne doit pas être fixé « à la main » : il doit être calculé par programme, en résumant le coût de toutes les marchandises. Les champs comme ceux-ci qui doivent être calculés par programme n'ont pas besoin d'être sérialisés. Par conséquent, nous les marquons avec un modificateur transient.
class Order implements Serializable {

    private List items;
    private transient BigDecimal totalAmount; //вычисляется на ходу

}

  • Champs avec informations privées

Certaines classes stockent également des informations privées. Nous avons examiné un exemple d'une telle classe au début de l'article. Vous ne devez pas permettre à ces informations de fuir en dehors de la JVM. Par conséquent, les champs contenant de telles données doivent être marqués d'un modificateur transientsi vous souhaitez sérialiser une telle classe.

  • Champs qui n'implémentent pas l'interfaceSerializable

Parfois, une classe contient des champs-objets d'autres classes qui n'implémentent pas l'interface Serializable. Des exemples de tels champs sont les enregistreurs, les flux d'E/S, les objets qui stockent les connexions à la base de données et d'autres classes d'utilitaires. Si vous essayez de sérialiser un objet contenant des champs non sérialisables, vous recevrez une erreur java.io.NotSerializableException. Pour éviter cela, tous les champs qui n'implémentent pas l'interface Serializabledoivent être marqués d'un modificateur transient.
public class FileReader implements Serializable {
    // Первые 2 поля не реализуют Serializable
    // Помечаем их How transient поля
    private transient InputStream is;
    private transient BufferedReader buf;
    private String fileName;

    // Constructors, Getters, Setters

    public String readFile() throws IOException {
        try {
            is = new FileInputStream(fileName);
            buf = new BufferedReader(new InputStreamReader(is));
            String line = buf.readLine();
            StringBuilder sb = new StringBuilder();
            while (line != null) {
                sb.append(line).append("\n");
                line = buf.readLine();
            }
            return sb.toString();
        } finally {
            if (buf != null) {
                buf.close();
            }
            if (is != null) {
                is.close();
            }
        }
    }
}

  • Champs contenant des informations sur l'état de l'objet

Eh bien, une dernière chose. Il n'est pas nécessaire de sérialiser les champs qui ne font pas partie des informations sur l'état de l'objet. Les exemples ci-dessus relèvent de cette règle. Mais vous pouvez également inclure ici tous les autres champs ajoutés pour le débogage ou pour exécuter une sorte de fonction de service qui ne contiennent pas d'informations sur l'état de l'objet.

transientEtfinal

Résultats

C'est tout. Aujourd'hui nous avons parlé du modificateur transient:
  1. Nous nous sommes souvenus de la sérialisation en théorie et en pratique.
  2. Nous avons réalisé que pour ne pas sérialiser certains champs de la classe, ils doivent être marqués d'un modificateur transient.
  3. Nous avons discuté dans quelles situations ce modificateur devrait être utilisé. Il y avait quatre situations de ce type :
    1. les champs calculés par programme ;
    2. les champs contenant des informations secrètes ;
    3. les champs qui n'implémentent pas l'interface Serializable;
    4. champs qui ne font pas partie de l’état de l’objet.
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION