Introduction à la sérialisation Java

1. Introduction

La sérialisation est la conversion de l'état d'un objet en un flux d'octets; la désérialisation fait le contraire. En d'autres termes, la sérialisation est la conversion d'un objet Java en un flux statique (séquence) d'octets qui peut ensuite être enregistré dans une base de données ou transféré sur un réseau.

2. Sérialisation et désérialisation

Le processus de sérialisation est indépendant de l'instance, c'est-à-dire que les objets peuvent être sérialisés sur une plate-forme et désérialisés sur une autre. Les classes éligibles à la sérialisation doivent implémenter une interface de marqueur spéciale Serializable.

Les deux ObjectInputStream et ObjectOutputStream sont des classes de haut niveau qui se prolongent java.io.InputStream et java.io.OutputStream respectivement. ObjectOutputStream peut écrire des types primitifs et des graphiques d'objets dans un OutputStream sous la forme d'un flux d'octets. Ces flux peuvent ensuite être lus à l'aide d' ObjectInputStream .

La méthode la plus importante d' ObjectOutputStream est:

public final void writeObject(Object o) throws IOException;

Ce qui prend un objet sérialisable et le convertit en une séquence (flux) d'octets. De même, la méthode la plus importante d' ObjectInputStream est:

public final Object readObject() throws IOException, ClassNotFoundException;

Qui peut lire un flux d'octets et le reconvertir en un objet Java. Cela peut ensuite être renvoyé à l'objet d'origine.

Illustrons la sérialisation avec une classe Person . Notez que les champs statiques appartiennent à une classe (par opposition à un objet) et ne sont pas sérialisés . Notez également que nous pouvons utiliser le mot-clé transitoire pour ignorer les champs de classe lors de la sérialisation:

public class Person implements Serializable { private static final long serialVersionUID = 1L; static String country = "ITALY"; private int age; private String name; transient int height; // getters and setters }

Le test ci-dessous montre un exemple d'enregistrement d'un objet de type Person dans un fichier local, puis relit cette valeur dans:

@Test public void whenSerializingAndDeserializing_ThenObjectIsTheSame() () throws IOException, ClassNotFoundException { Person person = new Person(); person.setAge(20); person.setName("Joe"); FileOutputStream fileOutputStream = new FileOutputStream("yourfile.txt"); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(person); objectOutputStream.flush(); objectOutputStream.close(); FileInputStream fileInputStream = new FileInputStream("yourfile.txt"); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); Person p2 = (Person) objectInputStream.readObject(); objectInputStream.close(); assertTrue(p2.getAge() == p.getAge()); assertTrue(p2.getName().equals(p.getName())); }

Nous avons utilisé ObjectOutputStream pour enregistrer l'état de cet objet dans un fichier à l'aide de FileOutputStream . Le fichier «yourfile.txt» est créé dans le répertoire du projet. Ce fichier est ensuite chargé à l'aide de FileInputStream. ObjectInputStream récupère ce flux et le convertit en un nouvel objet appelé p2 .

Enfin, nous testons l'état de l'objet chargé, et il correspond à l'état de l'objet d'origine.

Notez que l'objet chargé doit être explicitement converti en un type Person .

3. Mises en garde concernant la sérialisation Java

Il y a quelques mises en garde concernant la sérialisation en Java.

3.1. Héritage et composition

Lorsqu'une classe implémente l' interface java.io.Serializable , toutes ses sous-classes sont également sérialisables. Au contraire, lorsqu'un objet a une référence à un autre objet, ces objets doivent implémenter l' interface Serializable séparément, sinon une NotSerializableException sera levée:

public class Person implements Serializable { private int age; private String name; private Address country; // must be serializable too } 

Si l'un des champs d'un objet sérialisable se compose d'un tableau d'objets, alors tous ces objets doivent également être sérialisables, sinon une NotSerializableException sera levée.

3.2. UID de la version série

La JVM associe un numéro de version ( long ) à chaque classe sérialisable. Il permet de vérifier que les objets enregistrés et chargés ont les mêmes attributs et sont donc compatibles lors de la sérialisation.

Ce numéro peut être généré automatiquement par la plupart des IDE et est basé sur le nom de la classe, ses attributs et les modificateurs d'accès associés. Toute modification entraîne un nombre différent et peut provoquer une exception InvalidClassException .

Si une classe sérialisable ne déclare pas un serialVersionUID , la JVM en générera un automatiquement au moment de l'exécution. Cependant, il est fortement recommandé que chaque classe déclare son serialVersionUID car celui qui est généré dépend du compilateur et peut donc entraîner des InvalidClassExceptions inattendues .

3.3. Sérialisation personnalisée en Java

Java spécifie une manière par défaut dont les objets peuvent être sérialisés. Les classes Java peuvent remplacer ce comportement par défaut. La sérialisation personnalisée peut être particulièrement utile lorsque vous essayez de sérialiser un objet qui a des attributs non sérialisables. Cela peut être fait en fournissant deux méthodes à l'intérieur de la classe que nous voulons sérialiser:

private void writeObject(ObjectOutputStream out) throws IOException;

et

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException;

Avec ces méthodes, nous pouvons sérialiser ces attributs non sérialisables dans d'autres formes qui peuvent être sérialisées:

public class Employee implements Serializable { private static final long serialVersionUID = 1L; private transient Address address; private Person person; // setters and getters private void writeObject(ObjectOutputStream oos) throws IOException { oos.defaultWriteObject(); oos.writeObject(address.getHouseNumber()); } private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { ois.defaultReadObject(); Integer houseNumber = (Integer) ois.readObject(); Address a = new Address(); a.setHouseNumber(houseNumber); this.setAddress(a); } }
public class Address { private int houseNumber; // setters and getters }

Le test unitaire suivant teste cette sérialisation personnalisée:

@Test public void whenCustomSerializingAndDeserializing_ThenObjectIsTheSame() throws IOException, ClassNotFoundException { Person p = new Person(); p.setAge(20); p.setName("Joe"); Address a = new Address(); a.setHouseNumber(1); Employee e = new Employee(); e.setPerson(p); e.setAddress(a); FileOutputStream fileOutputStream = new FileOutputStream("yourfile2.txt"); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(e); objectOutputStream.flush(); objectOutputStream.close(); FileInputStream fileInputStream = new FileInputStream("yourfile2.txt"); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); Employee e2 = (Employee) objectInputStream.readObject(); objectInputStream.close(); assertTrue( e2.getPerson().getAge() == e.getPerson().getAge()); assertTrue( e2.getAddress().getHouseNumber() == e.getAddress().getHouseNumber()); }

Dans ce code, nous voyons comment enregistrer certains attributs non sérialisables en sérialisant Address avec une sérialisation personnalisée. Notez que nous devons marquer les attributs non sérialisables comme transitoires pour éviter l' exception NotSerializableException.

4. Conclusion

Dans ce rapide didacticiel, nous avons examiné la sérialisation Java, discuté des éléments importants à garder à l'esprit et montré comment effectuer une sérialisation personnalisée.

Comme toujours, le code source utilisé dans ce didacticiel est disponible à l'adresse over sur GitHub.