CodeGym /Blog Java /Random-FR /Interface externalisable en Java
Auteur
Volodymyr Portianko
Java Engineer at Playtika

Interface externalisable en Java

Publié dans le groupe Random-FR
Salut! Aujourd'hui, nous allons continuer à connaître la sérialisation et la désérialisation des objets Java. Dans la dernière leçon, nous avons appris à connaître l' interface du marqueur sérialisable , examiné des exemples de son utilisation et également appris comment utiliser le mot-clé transient pour contrôler le processus de sérialisation. Eh bien, dire que nous « contrôlons le processus » est peut-être exagéré. Nous avons un mot-clé, un identifiant de version, et c'est à peu près tout. Le reste du processus est caché dans Java et nous ne pouvons pas y accéder. Bien sûr, en termes de commodité, c'est bien. Mais un programmeur ne devrait pas seulement être guidé par son propre confort, n'est-ce pas ? :) Il y a d'autres facteurs que vous devez considérer. C'est pourquoi sérialisablen'est pas le seul mécanisme de sérialisation-désérialisation en Java. Aujourd'hui, nous allons nous familiariser avec l' interface externalisable . Mais avant de commencer à l'étudier, vous pourriez avoir une question raisonnable : pourquoi avons-nous besoin d'un autre mécanisme ? Serializablea fait son travail, et qu'est-ce qu'il n'y a pas à aimer dans la mise en œuvre automatique de l'ensemble du processus ? Et les exemples que nous avons examinés étaient également simples. Donc quel est le problème? Pourquoi avons-nous besoin d'une autre interface pour essentiellement les mêmes tâches ? Le fait est qu'il Serializablea plusieurs lacunes. Nous en énumérons quelques-uns :
  1. Performance. L' Serializableinterface présente de nombreux avantages, mais les hautes performances n'en font clairement pas partie.

    Présentation de l'interface externalisable - 2

    Tout d'abord, Serializable l'implémentation interne de génère une grande quantité d'informations de service et toutes sortes de données temporaires.

    Deuxièmement, Serializable s'appuie sur l'API Reflection (vous n'avez pas besoin d'approfondir cela pour le moment ; vous pouvez en lire plus à votre guise, si cela vous intéresse). Cette chose vous permet de faire des choses apparemment impossibles en Java : par exemple, modifier les valeurs des champs privés. CodeGym a un excellent article sur l'API Reflection . Vous pouvez lire à ce sujet là-bas.

  2. La flexibilité. Nous ne contrôlons pas le processus de sérialisation-désérialisation lorsque nous utilisons l' Serializableinterface.

    D'une part, c'est très pratique, car si nous ne sommes pas particulièrement préoccupés par les performances, alors il semble agréable de ne pas avoir à écrire de code. Mais que se passe-t-il si nous devons vraiment ajouter certaines de nos propres fonctionnalités (nous fournirons un exemple ci-dessous) à la logique de sérialisation ?

    Fondamentalement, tout ce que nous avons pour contrôler le processus est le transientmot-clé pour exclure certaines données. C'est ça. C'est toute notre boîte à outils :/

  3. Sécurité. Cet élément découle en partie de l'élément précédent.

    Nous n'avons pas passé beaucoup de temps à y réfléchir auparavant, mais que se passe-t-il si certaines informations de votre classe ne sont pas destinées aux yeux et aux oreilles indiscrets des autres ? Un exemple simple est un mot de passe ou d'autres données personnelles de l'utilisateur, qui dans le monde d'aujourd'hui sont régis par un tas de lois.

    Si nous utilisons Serializable, nous ne pouvons rien y faire. Nous sérialisons tout tel quel.

    Mais si nous le faisons correctement, nous devons chiffrer ce type de données avant de les écrire dans un fichier ou de les envoyer sur un réseau. Mais Serializablene rend pas cela possible.

Présentation de l'interface externalisable - 3Eh bien, voyons enfin à quoi ressemblerait la classe si nous utilisions l' Externalizableinterface.

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

public class UserInfo implements Externalizable {

   private String firstName;
   private String lastName;
   private String superSecretInformation;

private static final long SERIAL_VERSION_UID = 1L;

   // ...constructor, getters, setters, toString()...

   @Override
   public void writeExternal(ObjectOutput out) throws IOException {

   }

   @Override
   public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {

   }
}
Comme vous pouvez le voir, nous avons des changements importants ! La principale est évidente : lors de l'implémentation de l' Externalizableinterface, vous devez implémenter deux méthodes requises : writeExternal()etreadExternal(). Comme nous l'avons dit précédemment, la responsabilité de la sérialisation et de la désérialisation incombera au programmeur. Mais maintenant, vous pouvez résoudre le problème de l'absence de contrôle sur le processus ! L'ensemble du processus est programmé directement par vous. Naturellement, cela permet un mécanisme beaucoup plus souple. De plus, le problème de sécurité est résolu. Comme vous pouvez le voir, notre classe a un champ de données personnelles qui ne peut pas être stocké en clair. Maintenant, nous pouvons facilement écrire du code qui satisfait cette contrainte. Par exemple, nous pouvons ajouter à notre classe deux méthodes privées simples pour chiffrer et déchiffrer des données sensibles. Nous écrirons les données dans le fichier et les lirons à partir du fichier sous forme cryptée. Le reste des données sera écrit et lu tel quel :) En conséquence, notre classe ressemble à ceci :

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Base64;

public class UserInfo implements Externalizable {

   private String firstName;
   private String lastName;
   private String superSecretInformation;

   private static final long serialVersionUID = 1L;

   public UserInfo() {
   }

   public UserInfo(String firstName, String lastName, String superSecretInformation) {
       this.firstName = firstName;
       this.lastName = lastName;
       this.superSecretInformation = superSecretInformation;
   }

   @Override
   public void writeExternal(ObjectOutput out) throws IOException {
       out.writeObject(this.getFirstName());
       out.writeObject(this.getLastName());
       out.writeObject(this.encryptString(this.getSuperSecretInformation()));
   }

   @Override
   public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
       firstName = (String) in.readObject();
       lastName = (String) in.readObject();
       superSecretInformation = this.decryptString((String) in.readObject());
   }

   private String encryptString(String data) {
       String encryptedData = Base64.getEncoder().encodeToString(data.getBytes());
       System.out.println(encryptedData);
       return encryptedData;
   }

   private String decryptString(String data) {
       String decrypted = new String(Base64.getDecoder().decode(data));
       System.out.println(decrypted);
       return decrypted;
   }

   public String getFirstName() {
       return firstName;
   }

   public String getLastName() {
       return lastName;
   }

   public String getSuperSecretInformation() {
       return superSecretInformation;
   }
}
Nous avons implémenté deux méthodes qui utilisent les mêmes ObjectOutputparamètres ObjectInputque nous avons déjà rencontrés dans la leçon sur Serializable. Au bon moment, nous chiffrons ou déchiffrons les données requises, et nous utilisons les données chiffrées pour sérialiser notre objet. Voyons à quoi cela ressemble en pratique:

import java.io.*;

public class Main {

   public static void main(String[] args) throws IOException, ClassNotFoundException {

       FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);

       UserInfo userInfo = new UserInfo("Paul", "Piper", "Paul Piper's passport data");

       objectOutputStream.writeObject(userInfo);

       objectOutputStream.close();

   }
}
Dans les méthodes encryptString()et decryptString(), nous avons spécifiquement ajouté une sortie de console pour vérifier la forme sous laquelle les données secrètes seront écrites et lues. Le code ci-dessus affichait la ligne suivante : SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRh Le cryptage a réussi ! Le contenu complet du fichier ressemble à ceci : ¬í sr UserInfoГ!}ҐџC‚ћ xpt Ivanovt $SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRhx Essayons maintenant d'utiliser notre logique de désérialisation.

public class Main {

   public static void main(String[] args) throws IOException, ClassNotFoundException {

       FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);


       UserInfo userInfo = (UserInfo) objectInputStream.readObject();
       System.out.println(userInfo);

       objectInputStream.close();

   }
}
Eh bien, rien ne semble compliqué ici. Ça devrait marcher ! Nous l'exécutons et obtenons... Exception dans le thread "main" java.io.InvalidClassException : UserInfo; Présentation de l'interface externalisable - 4aucun constructeur valide :( Apparemment, ce n'est pas si facile ! Le mécanisme de désérialisation a lancé une exception et a exigé que nous créions un constructeur par défaut. Je me demande pourquoi. Avec , Serializablenous nous sommes débrouillés sans un... :/ Ici, nous avons rencontré une autre nuance importante. Le La différence entre Serializableet Externalizableréside non seulement dans l'accès "élargi" du programmeur et dans sa capacité à contrôler le processus avec plus de souplesse, mais également dans le processus lui-même. Surtout, dans le mécanisme de désérialisation . Lors de l'utilisationSerializable, la mémoire est simplement allouée à l'objet, puis les valeurs sont lues à partir du flux et utilisées pour définir les champs de l'objet. Si nous utilisons Serializable, le constructeur de l'objet n'est pas appelé ! Tout le travail se fait par réflexion (l'API Reflection, que nous avons brièvement mentionnée dans la dernière leçon). Avec Externalizable, le mécanisme de désérialisation est différent. Le constructeur par défaut est appelé en premier. Ce n'est qu'après cela que la méthode UserInfode l'objet créé est readExternal()appelée. Il est responsable de la définition des champs de l'objet. C'est pourquoi toute classe implémentant l' Externalizableinterface doit avoir un constructeur par défaut . Ajoutons-en un à notre UserInfoclasse et réexécutons le code :

import java.io.*;

public class Main {

   public static void main(String[] args) throws IOException, ClassNotFoundException {

       FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);


       UserInfo userInfo = (UserInfo) objectInputStream.readObject();
       System.out.println(userInfo);

       objectInputStream.close();
   }
}
Sortie console : Données de passeport de Paul Piper UserInfo \ firstName = 'Paul', lastName = 'Piper', superSecretInformation = 'Données de passeport de Paul Piper' } Maintenant, c'est quelque chose de complètement différent ! Tout d'abord, la chaîne décryptée avec des informations secrètes a été affichée sur la console. Ensuite, l'objet que nous avons récupéré du fichier s'est affiché sous forme de chaîne ! Nous avons donc résolu tous les problèmes avec succès :) Le sujet de la sérialisation et de la désérialisation semble simple, mais, comme vous pouvez le voir, les leçons ont été longues. Et il y a tellement plus que nous n'avons pas couvert! Il existe encore de nombreuses subtilités lors de l'utilisation de chacune de ces interfaces. Mais pour éviter d'exploser votre cerveau à cause de nouvelles informations excessives, je vais brièvement énumérer quelques points plus importants et vous donner des liens vers des lectures supplémentaires. Alors, que devez-vous savoir d'autre ? Tout d'abord , lors de la sérialisation (que vous utilisiez Serializableou Externalizable), faites attention aux staticvariables. Lorsque vous utilisez Serializable, ces champs ne sont pas du tout sérialisés (et, par conséquent, leurs valeurs ne changent pas, car staticles champs appartiennent à la classe, pas à l'objet). Mais lorsque vous utilisezExternalizable, vous contrôlez vous-même le processus, vous pouvez donc techniquement les sérialiser. Mais nous ne le recommandons pas, car cela risque de créer de nombreux bugs subtils. Deuxièmement , vous devez également faire attention aux variables avec le finalmodificateur. Lorsque vous utilisez Serializable, elles sont sérialisées et désérialisées comme d'habitude, mais lorsque vous utilisez Externalizable, il est impossible de désérialiser une finalvariable ! La raison est simple : tous finalles champs sont initialisés lorsque le constructeur par défaut est appelé — après cela, leur valeur ne peut plus être modifiée. Par conséquent, pour sérialiser des objets qui ont finaldes champs, utilisez la sérialisation standard fournie par Serializable. Troisièmement , lorsque vous utilisez l'héritage, toutes les classes descendantes qui héritent de certainsExternalizableLa classe doit également avoir des constructeurs par défaut. Voici le lien vers un bon article sur les mécanismes de sérialisation : Jusqu'à la prochaine fois! :)
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION