JavaRush /Blog Java /Random-FR /Interface externalisable en Java

Interface externalisable en Java

Publié dans le groupe Random-FR
Bonjour! Aujourd'hui, nous allons poursuivre notre introduction à la sérialisation et à la désérialisation des objets Java. Dans la dernière leçon, nous avons découvert l' interface du marqueur Serialisable , examiné des exemples de son utilisation et appris également à contrôler le processus de sérialisation à l'aide du mot-clé transient . Eh bien, « gérer le processus » est bien sûr un mot fort. Nous avons un mot-clé, un identifiant de version, et c'est essentiellement tout. Le reste du processus est « câblé » à l’intérieur de Java et il n’y a aucun accès. D'un point de vue pratique, c'est bien sûr une bonne chose. Mais un programmeur dans son travail ne doit pas se concentrer uniquement sur son propre confort, n'est-ce pas ? :) Il y a d'autres facteurs à considérer. Par conséquent, Serialisable n'est pas le seul outil de sérialisation-désérialisation en Java. Aujourd'hui, nous allons nous familiariser avec l' interface Externalisable . Mais avant même de passer à l’étude, vous vous posez peut-être une question raisonnable : pourquoi avons-nous besoin d’un autre outil ? SerializableJ'ai fait mon travail et la mise en œuvre automatique de l'ensemble du processus ne peut que me réjouir. Les exemples que nous avons examinés n’étaient pas compliqués non plus. Alors, quel est le problème ? Pourquoi une autre interface pour essentiellement la même tâche ? Le fait est qu’il Serializableprésente un certain nombre d’inconvénients. Citons-en quelques-uns :
  1. Performance. L'interface présente Serializablede nombreux avantages, mais les hautes performances n'en font clairement pas partie.

Présentation de l'interface externalisable - 2

Premièrement , le mécanisme interne Serializablegénère une grande quantité d'informations de service et divers types de données temporaires pendant le fonctionnement.
Deuxièmement (vous n'êtes pas obligé d'entrer dans les détails maintenant et de lire à votre guise si cela vous intéresse), le travail Serializableest basé sur l'utilisation de l'API Reflection. Cet engin permet de faire des choses qui sembleraient impossibles en Java : par exemple modifier les valeurs des champs privés. JavaRush propose un excellent article sur l'API Reflection , vous pouvez le lire ici.

  1. La flexibilité. Nous ne contrôlons pas du tout le processus de sérialisation-désérialisation lorsque nous utilisons le Serializable.

    D’une part, c’est très pratique, car si nous ne nous soucions pas vraiment des performances, la possibilité de ne pas écrire de code semble pratique. Mais que se passe-t-il si nous avons vraiment besoin d'ajouter certaines de nos propres fonctionnalités (un exemple d'entre elles sera ci-dessous) à la logique de sérialisation ?

    Essentiellement, tout ce dont nous avons pour contrôler le processus est un mot-clé transientpour exclure certaines données, et c'est tout. Un peu comme une « boîte à outils » :/

  2. Sécurité. Ce point découle en partie du précédent.

    Nous n’y avons pas beaucoup réfléchi auparavant, mais que se passe-t-il si certaines informations de votre classe ne sont pas destinées aux « oreilles des autres » (plus précisément aux yeux) ? Un exemple simple est un mot de passe ou d’autres données personnelles d’utilisateur, qui, dans le monde moderne, sont réglementées par un certain nombre de lois.

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

    Mais, dans le bon sens, nous devons chiffrer ce type de données avant de les écrire dans un fichier ou de les transmettre sur le réseau. Mais Serializablecela ne donne pas cette opportunité.

Présentation de l'interface externalisable - 3Eh bien, voyons enfin à quoi ressemblerait une classe en utilisant le Externalizable.
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;

   //...конструктор, геттеры, сеттеры, toString()...

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

   }

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

   }
}
Comme vous pouvez le constater, nous avons apporté des changements importants ! La principale est évidente : lors de l'implémentation d'une interface, Externalizablevous devez implémenter deux méthodes obligatoires - writeExternal()et readExternal(). Comme nous l'avons dit plus tôt, l'entière responsabilité de la sérialisation et de la désérialisation incombera au programmeur. Cependant, vous pouvez désormais résoudre le problème du manque de contrôle sur ce processus ! L’ensemble du processus est programmé directement par vous, ce qui crée bien entendu un mécanisme beaucoup plus flexible. De plus, le problème de sécurité est également résolu. Comme vous pouvez le constater, nous avons un champ dans notre classe : les données personnelles qui ne peuvent pas être stockées en clair. Nous pouvons désormais facilement écrire du code qui répond à cette contrainte. Par exemple, ajoutez deux méthodes privées simples à notre classe pour chiffrer et déchiffrer les données secrètes. Nous les écrirons dans un fichier et les lirons à partir du fichier sous forme cryptée. Et nous écrirons et lirons le reste des données tel quel :) En conséquence, notre classe ressemblera à 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 ObjectOutput outparamètres ObjectInputque nous avons déjà rencontrés dans la conférence sur Serializable. Au bon moment, nous chiffrons ou déchiffrons les données nécessaires, et sous cette forme nous les utilisons pour sérialiser notre objet. Voyons à quoi cela ressemblera 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("Ivan", "Ivanov", "Ivan Ivanov's passport data");

       objectOutputStream.writeObject(userInfo);

       objectOutputStream.close();

   }
}
Dans les méthodes encryptString()et decryptString(), nous avons spécifiquement ajouté une sortie à la console pour vérifier sous quelle forme les données secrètes seront écrites et lues. Le code ci-dessus affiche la ligne suivante sur la console : SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRh Le cryptage a réussi ! Le contenu complet du fichier ressemble à ceci : ¬н sr UserInfoГ!}ҐџC‚ћ xpt Ivant Ivanovt $SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRhx Essayons maintenant d'utiliser la logique de désérialisation que nous avons écrite.
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, il ne semble pas y avoir rien de compliqué ici, ça devrait marcher ! Lançons... Exception dans le thread "main" java.io.InvalidClassException : UserInfo ; pas de constructeur valide Présentation de l'interface externalisable - 4 Oups :( Cela s'est avéré pas si simple ! Le mécanisme de désérialisation a levé une exception et nous a obligé à créer un constructeur par défaut. Je me demande pourquoi ? SerializableNous avons réussi sans lui... :/ Nous arrivons ici à une autre nuance importante La différence entre Serializableet Externalizableréside non seulement dans l'accès « étendu » pour le programmeur et la capacité de gérer le processus de manière plus flexible, mais aussi dans le processus lui-même. Tout d'abord, dans le mécanisme de désérialisation ... Lorsqu'elle est utilisée, Serializablela mémoire est simplement alloué pour un objet, après quoi les valeurs sont lues à partir du flux, qui remplissent tous ses champs . Si nous utilisons Serializable, le constructeur de l'objet n'est pas appelé ! Tout le travail est effectué par réflexion (API Reflection, que nous avons brièvement mentionnée dans le dernier cours). Dans le cas de , le Externalizablemécanisme de désérialisation sera différent. Au début, le constructeur par défaut est appelé. Et seulement ensuite UserInfoest appelé la méthode objet créée readExternal(), qui se charge de remplir les champs de l'objet. C'est-à-dire pourquoi toute classe qui implémente l'interface Externalizabledoit avoir un constructeur par défaut . Ajoutons-le à notre classe UserInfoet 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 de la console : données du passeport d'Ivan Ivanov UserInfo{firstName='Ivan', lastName='Ivanov', superSecretInformation='Données du passeport d'Ivan Ivanov'} Une affaire complètement différente ! Tout d'abord, la chaîne déchiffrée avec les données secrètes a été sortie sur la console, puis notre objet a été récupéré à partir du fichier au format chaîne ! C'est ainsi que nous avons résolu avec succès tous les problèmes :) Le sujet de la sérialisation et de la désérialisation semble simple, mais comme vous pouvez le constater, nos conférences se sont avérées longues. Et ce n'est pas tout ! Il existe de nombreuses autres subtilités lors de l'utilisation de chacune de ces interfaces, mais pour que votre cerveau n'explose pas à cause du volume de nouvelles informations, je vais brièvement énumérer quelques points plus importants et fournir des liens vers des lectures supplémentaires. Alors, que devez-vous savoir d’autre ? Tout d'abord , lors de la sérialisation (peu importe que vous utilisiez Serializableou Externalizable), faites attention aux variables static. Lorsqu'ils sont utilisés, Serializableces champs ne sont pas du tout sérialisés (et, par conséquent, leur valeur ne change pas, puisque staticles champs appartiennent à la classe et non à l'objet). Mais lorsque vous l'utilisez, Externalizablevous contrôlez le processus vous-même, donc techniquement, cela peut être fait. Mais cela n’est pas recommandé, car cela comporte de nombreuses erreurs subtiles. Deuxièmement , il faut également prêter attention aux variables avec le modificateur final. Lorsqu'ils sont utilisés, Serializableils sont sérialisés et désérialisés comme d'habitude, mais lorsqu'ils sont utilisés, il est impossible Externalizablede désérialiser finalune variable ! La raison est simple : tous finalles champs sont initialisés lorsque le constructeur par défaut est appelé, et après cela, leur valeur ne peut plus être modifiée. Par conséquent, pour sérialiser des objets contenant finaldes champs, utilisez la sérialisation standard via Serializable. Troisièmement , lors de l'utilisation de l'héritage, toutes les classes héritières descendant d'une Externalizableclasse doivent également avoir des constructeurs par défaut. Voici quelques liens vers de bons articles sur les mécanismes de sérialisation : À bientôt! :)
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION