Serializable
J'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 Serializable
présente un certain nombre d’inconvénients. Citons-en quelques-uns :
-
Performance. L'interface présente
Serializable
de nombreux avantages, mais les hautes performances n'en font clairement pas partie.
Premièrement , le mécanisme interne Serializable
gé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 Serializable
est 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.
-
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é
transient
pour exclure certaines données, et c'est tout. Un peu comme une « boîte à outils » :/ -
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
Serializable
cela ne donne pas cette opportunité.
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, Externalizable
vous 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 out
paramètres ObjectInput
que 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 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 ? Serializable
Nous avons réussi sans lui... :/ Nous arrivons ici à une autre nuance importante La différence entre Serializable
et Externalizable
ré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, Serializable
la 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 Externalizable
mécanisme de désérialisation sera différent. Au début, le constructeur par défaut est appelé. Et seulement ensuite UserInfo
est 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 Externalizable
doit avoir un constructeur par défaut . Ajoutons-le à notre classe UserInfo
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 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 Serializable
ou Externalizable
), faites attention aux variables static
. Lorsqu'ils sont utilisés, Serializable
ces champs ne sont pas du tout sérialisés (et, par conséquent, leur valeur ne change pas, puisque static
les champs appartiennent à la classe et non à l'objet). Mais lorsque vous l'utilisez, Externalizable
vous 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, Serializable
ils sont sérialisés et désérialisés comme d'habitude, mais lorsqu'ils sont utilisés, il est impossible Externalizable
de désérialiser final
une variable ! La raison est simple : tous final
les 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 final
des 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 Externalizable
classe doivent également avoir des constructeurs par défaut. Voici quelques liens vers de bons articles sur les mécanismes de sérialisation :
À bientôt! :)
GO TO FULL VERSION