Introduction à la sérialisation Java

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. Autrement dit, la sérialisation est la conversion d'un objet Java en un flux d'octets statique (séquence), qui peut ensuite être sauvegardé 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.-à-d. Les objets peuvent être sérialisés sur une plate-forme et désérialisés sur une autre. Classes that are eligible for serialization need to implement a special marker interfaceSerializable.

LesObjectInputStream etObjectOutputStream sont des classes de haut niveau qui étendent respectivementjava.io.InputStream etjava.io.OutputStream. ObjectOutputStream peut écrire des types primitifs et des graphiques d'objets sur unOutputStream sous la forme d'un flux d'octets. Ces flux peuvent ensuite être lus en utilisantObjectInputStream.

La méthode la plus importante dansObjectOutputStream 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 dansObjectInputStream est:

public final Object readObject()
  throws IOException, ClassNotFoundException;

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

Illustrons la sérialisation avec une classePerson. Notez questatic fields belong to a class (as opposed to an object) and are not serialized. Notez également que nous pouvons utiliser le mot-clétransient 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 typePerson 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 en utilisantFileOutputStream. Le fichier“yourfile.txt” est créé dans le répertoire du projet. Ce fichier est ensuite chargé en utilisantFileInputStream.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 celui-ci correspond à celui de l'objet d'origine.

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

3. Mises en garde concernant la sérialisation Java

Certaines mises en garde concernent la sérialisation en Java.

3.1. Héritage et composition

Lorsqu'une classe implémente l'interfacejava.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'interfaceSerializable séparément, sinon unNotSerializableException sera lancé:

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 unNotSerializableException sera lancé.

3.2. UID de la version série

The JVM associates a version (long) number with each serializable class. Il permet de vérifier que les objets sauvegardé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. Tout changement entraîne un nombre différent et peut provoquer unInvalidClassException.

Si une classe sérialisable ne déclare pas deserialVersionUID, la JVM en générera une automatiquement lors de l'exécution. Cependant, il est fortement recommandé que chaque classe déclare sesserialVersionUID car celui généré dépend du compilateur et peut donc entraîner desInvalidClassExceptions inattendus.

3.3. Sérialisation personnalisée en Java

Java spécifie un moyen par défaut de sérialiser les objets. 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 doté d'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;

and

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

Avec ces méthodes, nous pouvons sérialiser ces attributs non sérialisables dans d'autres formulaires pouvant être sérialisés:

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érialisantAddress avec une sérialisation personnalisée. Notez que nous devons marquer les attributs non sérialisables commetransient pour éviter lesNotSerializableException.

4. Conclusion

Dans ce rapide didacticiel, nous avons examiné la sérialisation Java, abordé des points importants à garder à l’esprit et montré comment réaliser une sérialisation personnalisée.

Comme toujours, le code source utilisé dans ce tutoriel est disponibleover on GitHub.