Serializable
a 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 Serializable
a plusieurs lacunes. Nous en énumérons quelques-uns :
-
Performance. L'
Serializable
interface présente de nombreux avantages, mais les hautes performances n'en font clairement pas partie.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. -
La flexibilité. Nous ne contrôlons pas le processus de sérialisation-désérialisation lorsque nous utilisons l'
Serializable
interface.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
transient
mot-clé pour exclure certaines données. C'est ça. C'est toute notre boîte à outils :/ -
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
Serializable
ne rend pas cela possible.

Externalizable
interface.
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' Externalizable
interface, 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 ObjectOutput
paramètres ObjectInput
que 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; 
Serializable
nous nous sommes débrouillés sans un... :/ Ici, nous avons rencontré une autre nuance importante. Le La différence entre Serializable
et Externalizable
ré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 UserInfo
de 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' Externalizable
interface doit avoir un constructeur par défaut . Ajoutons-en un à notre UserInfo
classe 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 Serializable
ou Externalizable
), faites attention aux static
variables. Lorsque vous utilisez Serializable
, ces champs ne sont pas du tout sérialisés (et, par conséquent, leurs valeurs ne changent pas, car static
les 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 final
modificateur. 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 final
variable ! La raison est simple : tous final
les 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 final
des 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 certainsExternalizable
La 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! :)
GO TO FULL VERSION