IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Penser en Java 2nde édition - Sommaire |  Préface |  Avant-propos | Chapitre : 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15 |  Annexe : A B C D  | Tables des matières - Thinking in Java

  Chapitre 11 - Le système d’E/S de Java

pages : 1 2 3 4 5 6 7 8 9 10 

L'essentiel de tout cela est de rendre quelque chose raisonnablement complexe qui ne puisse pas facilement être sérialisé. L'action de sérialiser, cependant, est plutôt simple. Une fois que l'ObjectOutputStream est crée depuis un autre flux, writeObject() sérialise l'objet. Notez aussi l'appel de writeObject() pour un String. Vous pouvez aussi écrire tous les types de données primitives utilisant les même méthodes qu'DataOutputStream (ils partagent la même interface).

Il y a deux portions de code séparées qui ont une apparence similaire. La première écrit et lit et la seconde, pour varier, écrit et lit un ByteArray. Vous pouvez lire et écrire un objet en utilisant la sérialisation vers n'importe quel DataInputStream ou DataOutputStream incluant, comme vous le verrez dans le Chapitre 15, un réseau. La sortie d'une exécution donne :

Worm constructor: 6
Worm constructor: 5
Worm constructor: 4
Worm constructor: 3
Worm constructor: 2
Worm constructor: 1
w = :a(262):b(100):c(396):d(480):e(316):f(398)
Worm storage, w2 = :a(262):b(100):c(396):d(480):e(316):f(398)
Worm storage, w3 = :a(262):b(100):c(396):d(480):e(316):f(398)

Vous pouvez voir que l'objet déserialisé contient vraiment tous les liens qui étaient dans l'objet original.

Notons qu'aucun constructeur, même pas le constructeur par défaut, n'est appelé dans le processus de désérialisation d'un objet Serializable. L'objet entier est restauré par récupération des données depuis l'InputStream.

La sérialisation objet est orientée-byte, et ainsi emploie les hiérarchies d'InputStream et d'OutputStream.

Trouver la classe

Vous devez vous demander ce qui est nécessaire pour qu'un objet soit récupéré depuis son état sérialisé. Par exemple, supposons que vous sérialisez un objet et que vous l'envoyez comme un fichier à travers un réseau vers une autre machine. Un programme sur l'autre machine pourra-t-il reconstruire l'objet en utilisant seulement le contenu du fichier ?

La meilleure manière de répondre a cette question est (comme d'habitude) en accomplissant une expérience. Le fichier suivant file dans le sous-répertoire pour ce chapitre :

//: c11:Alien.java
// Une classe sérializable.
import java.io.*;

public class Alien implements Serializable {
} ///:~

Le fichier qui crée et sérialise un objet Alien va dans le même répertoire :

//: c11:FreezeAlien.java
// Crée un fichier de sortie sérialisé.
import java.io.*;

public class FreezeAlien {
  // Lance les exeptions vers la console:
  public static void main(String[] args)
  throws IOException {
    ObjectOutput out =
      new ObjectOutputStream(
        new FileOutputStream("X.file"));
    Alien zorcon = new Alien();
    out.writeObject(zorcon);
  }
} ///:~

Plutôt que de saisir et de traiter les exeptions, ce programme prend une approche rapide et sale qui passe les exeptions en dehors de main(), ainsi elle seront reportés en ligne de commande.

Une fois que le programme est compilé et exécuté, copiez le X.file résultant dans un sous répertoire appeléxfiles, où va le code suivant :

//: c11:xfiles:ThawAlien.java
// Essaye de récupérer un fichier sérialisé sans
// la classe de l'objet qui est stocké dans ce fichier.
import java.io.*;

public class ThawAlien {
  public static void main(String[] args)
  throws IOException, ClassNotFoundException {
    ObjectInputStream in =      new ObjectInputStream(
        new FileInputStream("X.file"));
    Object mystery = in.readObject();
    System.out.println(mystery.getClass());
  }
} ///:~

Ce programme ouvre le fichier et lit dans l'objet mystery avec succès. Pourtant, dès que vous essayez de trouver quelque chose à propos de l'objet — qui nécessite l'objet Class pour Alien — la Machine Virtuelle Java (JVM) ne peut pas trouver Alien.class (à moins qu'il arrive qu'il soit dans le Classpath, ce qui n'est pas le cas dans cet exemple). Vous obtiendrez un ClassNotFoundException. (Une fois encore, toute les preuves de l'existence de vie alien disparaît avant que la preuve de son existence soit vérifiée !)

Si vous espérez en faire plus après avoir récupéré un objet qui a été sérialisé, vous devrez vous assurer que la JVM puisse trouver les fichiers .class soit dans le chemin de class local ou quelque part sur l'Internet.

Contrôler la sérialisation

Comme vous pouvez le voir, le mécanisme de sérialisation par défaut est d'un usage trivial. Mais que faire si vous avez des besoins spéciaux ? Peut-être que vous avez des problèmes de sécurité spéciaux et que vous ne voulez pas sérialiser des parties de votre objet, ou peut-être que cela n'a pas de sens pour un sous-objet d'être sérialisé si cette partie doit être de nouveau crée quand l'objet est récupéré.

Vous pouvez contrôler le processus de sérialisation en implémentant l'interface Externalizable à la place de l'interface Serializable. L'interface Externalizable étend l'interface Serializable et ajoute deux méthodes, writeExternal() et readExternal(), qui sont automatiquement appelées pour votre objet pendant la sérialisation et la désérialisation afin que vous puissiez exécuter vos opérations spéciales.

L'exemple suivant montre des implémentations simple des méthodes de l'interface Externalizable. Notez que Blip1 et Blip2 sont presque identiques à l'exception d'une subtile différence (voyez si vous pouvez la découvrir en regardant le code) :

//: c11:Blips.java
// Emploi simple d'Externalizable & un piège.
import java.io.*;
import java.util.*;

class Blip1 implements Externalizable {
  public Blip1() {
    System.out.println("Blip1 Constructor");
  }
  public void writeExternal(ObjectOutput out)
      throws IOException {
    System.out.println("Blip1.writeExternal");
  }
  public void readExternal(ObjectInput in)
     throws IOException, ClassNotFoundException {
    System.out.println("Blip1.readExternal");
  }
}

class Blip2 implements Externalizable {
  Blip2() {
    System.out.println("Blip2 Constructor");
  }
  public void writeExternal(ObjectOutput out)
      throws IOException {
    System.out.println("Blip2.writeExternal");
  }
  public void readExternal(ObjectInput in)
     throws IOException, ClassNotFoundException {
    System.out.println("Blip2.readExternal");
  }
}

public class Blips {
  // Lance les exeptions vers la console :
  public static void main(String[] args)
  throws IOException, ClassNotFoundException {
    System.out.println("Constructing objects:");
    Blip1 b1 = new Blip1();
    Blip2 b2 = new Blip2();
    ObjectOutputStream o =      new ObjectOutputStream(
        new FileOutputStream("Blips.out"));
    System.out.println("Saving objects:");
    o.writeObject(b1);
    o.writeObject(b2);
    o.close();
    //Maintenant faites les revenir :
    ObjectInputStream in =      new ObjectInputStream(
        new FileInputStream("Blips.out"));
    System.out.println("Recovering b1:");
    b1 = (Blip1)in.readObject();
    // OOPS! Lance une exeption :
//! System.out.println("Recovering b2:");
//! b2 = (Blip2)in.readObject();
  }
} ///:~

La sortie pour ce programme est :

Constructing objects:
Blip1 Constructor
Blip2 Constructor
Saving objects:
Blip1.writeExternal
Blip2.writeExternal
Recovering b1:
Blip1 Constructor
Blip1.readExternal

La raison pour laquelle l'objet Blip2 n'est pas récupéré est que le fait d'essayer provoque une exception. Vous pouvez voir la différence entreBlip1 et Blip2 ? Le constructeur de Blip1 est public, tandis que le constructeur de Blip2 ne l'est pas, et cela lance une exception au recouvrement. Essayez de rendre le constructor de Blip2 public et retirez les commentaires //! pour voir les résultats correct.

Quand b1 est récupéré, le constructeur par défaut Blip1 est appelé. Ceci est différent de récupérer un objet Serializable, dans lequel l'objet est construit entièrement de ses bits enregistrés, sans appel au constructeur. Avec un objet Externalizable, tous les comportements de construction par défaut se produisent (incluant les initialisations à ce point du champ de définition), et alors readExternal() est appelé. Vous devez prendre en compte ceci en particulier, le fait que toute la construction par défaut a toujours lieu pour produire le comportement correct dans vos objets Externalizable.

Voici un exemple qui montre ce que vous devez faire pour complétement stocker et retrouver un objet Externalizable :

//: c11:Blip3.java
// Reconstruction d'un objet externalizable.
import java.io.*;
import java.util.*;

class Blip3 implements Externalizable {
  int i;
  String s; // Aucune initialisation
  public Blip3() {
    System.out.println("Blip3 Constructor");
    // s, i n'est pas initialisé
  }
  public Blip3(String x, int a) {
    System.out.println("Blip3(String x, int a)");
    s = x;
    i = a;
    // s & i initialisé seulement dans le non
    // constructeur par défaut.
  }
  public String toString() { return s + i; }
  public void writeExternal(ObjectOutput out)
  throws IOException {
    System.out.println("Blip3.writeExternal");
    // Vous devez faire ceci :
    out.writeObject(s);
    out.writeInt(i);
  }
  public void readExternal(ObjectInput in)
  throws IOException, ClassNotFoundException {
    System.out.println("Blip3.readExternal");
    // Vous devez faire ceci :
    s = (String)in.readObject();
    i =in.readInt();
  }
  public static void main(String[] args)
  throws IOException, ClassNotFoundException {
    System.out.println("Constructing objects:");
    Blip3 b3 = new Blip3("A String ", 47);
    System.out.println(b3);
    ObjectOutputStream o =      new ObjectOutputStream(
        new FileOutputStream("Blip3.out"));
    System.out.println("Saving object:");
    o.writeObject(b3);
    o.close();
    // Maintenant faites le revenir :
    ObjectInputStream in =      new ObjectInputStream(
        new FileInputStream("Blip3.out"));
    System.out.println("Recovering b3:");
    b3 = (Blip3)in.readObject();
    System.out.println(b3);
  }
} ///:~

Les champs s et i sont initialisés seulement dans le second constructeur, mais pas dans le constructeur par défaut. Ceci signifie que si vous n'initialisez pas s et i dans readExternal(), il sera alors null (parce que le stockage pour l'objet arrive nettoyé à zéro dans la première étape de la création de l'objet). Si vous enlevez les commentaires sur les deux lignes suivant les phrases « Vous devez faire ceci » et lancez le programme, vous verrez que lorsque l'objet est récupéré, s est null et i est zéro.

Si vous l'héritage se fait depuis un objet Externalizable, vous appellerez typiquement les versions classe-de-base de writeExternal() et readExternal() pour fournir un stockage et une récupération propre des composants de classe-de-base.

Ainsi pour faire fonctionner correctement les choses vous ne devrez pas seulement écrire les données importantes depuis l'objet pendant la méthode writeExternal() (il n'y a pas de comportement par défaut qui écrit n'importe quels objets pour un objet Externalizable object), mais vous devrez aussi récupérer ces données dans la méthode readExternal(). Ceci peut être un petit peu confus au premier abord parce que le constructeur par défaut du comportement pour un objet Externalizable peut le faire ressembler à une sorte de stockage et de récupération ayant lieu automatiquement. Ce n'est pas le cas.

Le mot-clé « transient »

Quand vous contrôlez la sérialisation, il peut y avoir un sous-objet précis pour qui vous ne voulez pas que le mécanisme de sérialisation java sauve et restaure automatiquement. C'est communément le cas si le sous-objet représente des informations sensibles que vous ne désirez pas sérialiser, comme un mot de passe. Même si cette information est private dans l'objet, une fois qu'elle est sérialisée il est possible pour quelqu'un d'y accéder en lisant un fichier ou en interceptant une transmission réseau.

Une manière de prévenir les parties sensibles de votre objet d'être sérialisé est d'implémenter votre classe comme Externalizable, comme montré précédemment. Ainsi rien n'est sérialisé automatiquement et vous pouvez sérialiser explicitement seulement les parties nécessaires dans writeExternal().

Si vous travaillez avec un objet Serializable, néanmoins, toutes les sérialisation arrivent de façon automatique. Pour contrôler ceci, vous pouvez fermer la sérialisation sur une base de champ-par-champ en utilisant le mot transient, lequel dit « Ne t'embarrasse pas a sauver ou restaurer ceci — Je me charge de ça. »

Par exemple, considérons un objet Login qui conserve les informations à propos d'un login de session particulier. Supposez que, dès que vous vérifiez le login, vous désirez stocker les données, mais sans le mot de passe. La manière la plus simple pour réaliser ceci est en d'implémentant Serializable et en marquant le champ password comme transient. Voici ce à quoi cela ressemble :

//: c11:Logon.java
// Explique le mot « transient. »
import java.io.*;
import java.util.*;

class Logon implements Serializable {
  private Date date = new Date();
  private String username;
  private transient String password;
  Logon(String name, String pwd) {
    username = name;
    password = pwd;
  }
  public String toString() {
    String pwd =      (password == null) ? "(n/a)" : password;
    return "logon info: \n   " +
      "username: " + username +
      "\n   date: " + date +
      "\n   password: " + pwd;
  }
  public static void main(String[] args)
  throws IOException, ClassNotFoundException {
    Logon a = new Logon("Hulk", "myLittlePony");
    System.out.println( "logon a = " + a);
      ObjectOutputStream o =        new ObjectOutputStream(
          new FileOutputStream("Logon.out"));
    o.writeObject(a);
    o.close();
    // Délai :
    int seconds = 5;
    long t = System.currentTimeMillis()
           + seconds * 1000;
    while(System.currentTimeMillis() < t)
      ;
    // Maintenant faites les revenir :
    ObjectInputStream in =      new ObjectInputStream(
        new FileInputStream("Logon.out"));
    System.out.println(
      "Recovering object at " + new Date());
    a = (Logon)in.readObject();
    System.out.println( "logon a = " + a);
  }
} ///:~

Vous pouvez voir que les champs date et username sont normaux (pas transient), et ils sont ainsi sérialisés automatiquement. Pourtant, password est transient, et donc n'est pas stocké sur le disque; aussi le mécanisme de sérialisation ne fait aucune tentative pour le récupérer. La sortie donne :

logon a = logon info:
   username: Hulk
   date: Sun Mar 23 18:25:53 PST 1997
   password: myLittlePony
Recovering object at Sun Mar 23 18:25:59 PST 1997
logon a = logon info:
   username: Hulk
   date: Sun Mar 23 18:25:53 PST 1997
   password: (n/a)

Lorsque l'objet est récupéré, le champ de password est null. Notez que toString() est obligé de contrôler la valeur null de password parcequ'il essaye d'assembler un objet String utilisant l'opérateur surchargé ‘ + ’, et que cet opérateur est confronté à une référence de type null, on aurait donc un NullPointerException. (Les nouvelles versions de Java contiendront peut être du code pour résoudre ce problème.)

Vous pouvez aussi voir que le champ date est stocké sur et récupéré depuis le disque et n'en génère pas une nouvelle.

Comme les objets Externalizable ne stockent pas tous leurs champs par défaut, le mot-clé transient est a employer avec les objets Serializable seulement.

Une alternative à Externalizable

Si vous n'êtes pas enthousiasmé par l'implémentation de l'interface Externalizable, il y a une autre approche. Vous pouvez implémenter l'interface Serializable et ajouter (notez que je dit « ajouter » et non pas « imposer » ou « implémenter ») des méthodes appelées writeObject() et readObject() qui seront automatiquement appelées quand l'objet est sérialisé et désérialisé, respectivement. C'est à dire, si vous fournissez ces deux méthodes elles seront employées à la place de la sérialisation par défaut.

Ces méthodes devront avoir ces signatures exactes :

private void
  writeObject(ObjectOutputStream stream)
    throws IOException;

private void
  readObject(ObjectInputStream stream)
    throws IOException, ClassNotFoundException

Ce livre a été écrit par Bruce Eckel ( télécharger la version anglaise : Thinking in java )
Ce chapitre a été traduit par Armel Fortun ( groupe de traduction )
télécharger la version francaise (PDF) | Commandez le livre en version anglaise (amazon) | télécharger la version anglaise
pages : 1 2 3 4 5 6 7 8 9 10 
Penser en Java 2nde édition - Sommaire |  Préface |  Avant-propos | Chapitre : 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15 |  Annexe : A B C D  | Tables des matières - Thinking in Java