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 

Dans main(), une ArrayList est employée pour tenir les objets de Class et les autres pour tenir les formes. Si vous ne fournissez pas un argument en ligne de commande la shapeTypes ArrayList est créé et les objets Class sont ajoutés, et ensuite l'ArrayList de shapes est créé et les objets Shape sont ajoutés. Puis, toutes les valeurs static de couleur (color) sont établies à GREEN, et tout est sérialisé vers le fichier CADState.out.

Si l'on fournit un argument en ligne de commande (vraisemblablement CADState.out), ce fichier est ouvert et utilisé pour restituer l'état du programme. Dans les deux situations, l'ArrayList de Shapes est affichée. Le résultat d'une exécution donne :

>java CADState
[class Circle color[3] xPos[-51] yPos[-99] dim[38]
, class Square color[3] xPos[2] yPos[61] dim[-46]
, class Line color[3] xPos[51] yPos[73] dim[64]
, class Circle color[3] xPos[-70] yPos[1] dim[16]
, class Square color[3] xPos[3] yPos[94] dim[-36]
, class Line color[3] xPos[-84] yPos[-21] dim[-35]
, class Circle color[3] xPos[-75] yPos[-43] dim[22]
, class Square color[3] xPos[81] yPos[30] dim[-45]
, class Line color[3] xPos[-29] yPos[92] dim[17]
, class Circle color[3] xPos[17] yPos[90] dim[-76]
]

>java CADState CADState.out
[class Circle color[1] xPos[-51] yPos[-99] dim[38]
, class Square color[0] xPos[2] yPos[61] dim[-46]
, class Line color[3] xPos[51] yPos[73] dim[64]
, class Circle color[1] xPos[-70] yPos[1] dim[16]
, class Square color[0] xPos[3] yPos[94] dim[-36]
, class Line color[3] xPos[-84] yPos[-21] dim[-35]
, class Circle color[1] xPos[-75] yPos[-43] dim[22]
, class Square color[0] xPos[81] yPos[30] dim[-45]
, class Line color[3] xPos[-29] yPos[92] dim[17]
, class Circle color[1] xPos[17] yPos[90] dim[-76]
]

Vous pouvez voir que les valeurs d'xPos, yPos, et dim sont toutes stockées et récupérées avec succès, mais qu'il y a quelque chose d'anormal dans la récupération d'informations static. Les « 3 » rentrent bien, mais ne sortent pas de la même manière. Les Circles ont une valeur de 1 (RED, ce qui est la définition), et les Squares ont une valeur de 0 (rappelez-vous, ils sont initialisés dans le constructeur). C'est comme si les statics n'étaient pas sérialisées du tout ! Ce qui est correct — malgré que la class Class est Serializable, cela n'agit pas comme on pouvait l'espérer. Donc si vous désirez sérialiser des statics, vous devrez le faire vous-même.

C'est à quoi les méthodes static serializeStaticState() et deserializeStaticState() dans Line servent. Vous pouvez voir qu'elles sont appelés explicitement comme une partie du processus de stockage et de récupération. (Notez que l'ordre d'écriture vers le fichier sérialisé et de sa relecture doit être conservé.) Ainsi pour que CADState.java s'exécute correctement vous devez :

  1. Ajouter un serializeStaticState() et un deserializeStaticState() aux figures [shapes].
  2. Enlever ArrayList shapeTypes et tout le code s'y rattachant.
  3. Ajouter des appels aux nouvelles méthodes statiques de sérialisation et de sérialisation dans les figures [shapes].

Un autre sujet auquel vous devez penser est la sécurité, vu que la sérialisation sauve aussi les données private. Si vous vous avez orientation de sécurité, ces champs doivent être marqués comme transient. Mais alors vous devez concevoir une manière sûre pour stocker cette information de sorte que quand vous faites une restauration vous pouvez remettre à l'état initial [reset] ces variables privates.

Tokenizer l'entrée

Tokenizing est le processus de casser une séquence de caractères en un séquence de « tokens, » qui sont des morceaux de texte délimités par quoi que vous vous choisissez. Par exemple, vos jetons [tokens] peuvent être des mots, et ensuite ils pourront être délimités par un blanc et de la ponctuation. Il y a deux classes fournies dans la librairie standard de Java qui peuvent être employées pour la tokenisation : StreamTokenizer and StringTokenizer.

StreamTokenizer

Bien que StreamTokenizer ne soit pas dérivé d'InputStream ou OutputStream, il ne fonctionne qu'avec les objets InputStreams, donc il appartient à juste titre à la partie d'E/S de la librairie.

Considérons un programme qui compte les occurrences de mots dans un fichier texte :

//: c11:WordCount.java
// Compte les mots dans un fichier, produit
// les résultats dans un formulaire classé.
import java.io.*;
import java.util.*;

class Counter {
  private int i = 1;
  int read() { return i; }
  void increment() { i++; }
}

public class WordCount {
  private FileReader file;
  private StreamTokenizer st;
  // Un TreeMap conserve les clés dans un ordre classé :
  private TreeMap counts = new TreeMap();
  WordCount(String filename)
    throws FileNotFoundException {
    try {
      file = new FileReader(filename);
      st = new StreamTokenizer(
        new BufferedReader(file));
      st.ordinaryChar('.');
      st.ordinaryChar('-');
    } catch(FileNotFoundException e) {
      System.err.println(
        "Could not open " + filename);
      throw e;
    }
  }
  void cleanup() {
    try {
      file.close();
    } catch(IOException e) {
      System.err.println(
        "file.close() unsuccessful");
    }
  }
  void countWords() {
    try {
      while(st.nextToken() !=        StreamTokenizer.TT_EOF) {
        String s;
        switch(st.ttype) {
          case StreamTokenizer.TT_EOL:
            s = new String("EOL");
            break;
          case StreamTokenizer.TT_NUMBER:
            s = Double.toString(st.nval);
            break;
          case StreamTokenizer.TT_WORD:
            s = st.sval; // Déjà un String
            break;
          default: // un seul caractère dans type
            s = String.valueOf((char)st.ttype);
        }
        if(counts.containsKey(s))
          ((Counter)counts.get(s)).increment();
        else
          counts.put(s, new Counter());
      }
    } catch(IOException e) {
      System.err.println(
        "st.nextToken() unsuccessful");
    }
  }
  Collection values() {
    return counts.values();
  }
  Set keySet() { return counts.keySet(); }
  Counter getCounter(String s) {
    return (Counter)counts.get(s);
  }
  public static void main(String[] args)
  throws FileNotFoundException {
    WordCount wc =      new WordCount(args[0]);
    wc.countWords();
    Iterator keys = wc.keySet().iterator();
    while(keys.hasNext()) {
      String key = (String)keys.next();
      System.out.println(key + ": "
               + wc.getCounter(key).read());
    }
    wc.cleanup();
  }
} ///:~

Présenter les mots sous une forme classée est facile à faire en stockant les données dans un TreeMap, qui organise automatiquement ces clés dans un ordre classé (voir le Chapitre 9). Quand vous avez un jeu de clés en utilisant keySet() , elles seront automatiquement rangées dans l'ordre.

Pour ouvrir le fichier, un FileReader est utilisé, et pour changer le fichier en mots un StreamTokenizer est créé depuis le FileReader enveloppé dans un BufferedReader. Dans StreamTokenizer, il y a une liste par défaut de séparateurs, et vous pouvez en ajouter d'autres avec un jeu de méthodes. Ici, ordinaryChar() est utilisé pour dire « Ce caractère n'a aucune signification pour que je m'y intéresse, » donc l'analyseur ne les incluras pas comme des parties de tous les mots qu'il va créer. Par exemple, dire st.ordinaryChar('.') veut dire que les points ne seront pas inclus comme des parties des mots qui seront analysés. Vous pourrez trouver plus d'information dans la documentation HTML du JDK à <java.sun.com>.

Dans countWords(), les tokens sont tirés un à un depuis le stream, et l'information ttype est utilisée pour déterminer ce qu'il faut faire avec chaque token, vu qu'un token peut être une fin de ligne, un nombre, une chaîne de caractère [string], ou un simple caractère.

Une fois qu'un token est trouvé, le counts de TreeMap est appelé pour voir si il contient déjà le token sous la forme d'une clé. Si c'est le cas, l'objet Counter correspondant est incrémenté pour indiquer qu'une autre instance de ce mots a été trouvée. Si ce n'est pas le cas, un nouveau Counter est créé — vu que le constructeur de Counter initialise sa valeur à un, ceci agit aussi pour compter le mot.

WordCount n'est pas un type de TreeMap, donc il n'est pas hérité. Il accompli un type de fonctionnalité spécifique, ainsi bien que les méthodes keys() et values() doivent être re-exposées, cela ne veut pas forcément dire que l'héritage doit être utilisé vu qu'un bon nombre de méthodes de TreeMap sont ici inappropriés. En plus, les autres méthodes comme getCounter(), qui prend le Counter pour un String particulier, et sortedKeys(), qui produit un Iterator, terminant le changement dans la forme d'interface de WordCount..

Dans main() vous pouvez voir l'emploi d'un WordCount pour ouvrir et compter les mots dans un fichier — cela ne prend que deux lignes de code. Puis un Iterator est extrait vers une liste triée de clés (mots), et est employé pour retirer chaque clé et Count associés. L'appel à cleanup() est nécessaire pour s'assurer que le fichier est fermé.

StringTokenizer

Bien qu'il ne fasse pas partie de la bibliothèque d'E/S, le StringTokenizer possède des fonctions assez similaires a StreamTokenizer comme il sera décrit ici.

Le StringTokenizer renvoie les tokens dans une chaîne de caractère un par un. Ces tokens sont des caractères consécutifs délimités par des tabulations, des espaces, et des nouvelles lignes. Ainsi, les tokens de la chaîne de caractère « Où est mon chat ? » sont « Où, » « est, » « mon, » « chat, » « ?. » Comme le StreamTokenizer, vous pouvez appeler le StringTokenizer pour casser l'entrée de n'importe quelle manière, mais avec StringTokenizer vous effectuez cela en passant un second argument au constructeur, qui est un String des délimiteurs que vous utiliserez. En général, si vous désirez plus de sophistication, employez un StreamTokenizer.

Vous appelez un objet StringTokenizer pour le prochain token dans la chaîne de caractère en utilisant la méthode nextToken(), qui renvoie soit un token ou une chaîne de caractère vide pour indiquer qu'il ne reste pas de tokens.

Comme exemple, le programme suivant exécute une analyse limitée d'une phrase, cherchant des phrases clés pour indiquer si la joie ou la tristesse sont sous-entendues.

//: c11:AnalyzeSentence.java
// Cherche des séries particulières dans les phrases.
import java.util.*;

public class AnalyzeSentence {
  public static void main(String[] args) {
    analyze("I am happy about this");
    analyze("I am not happy about this");
    analyze("I am not! I am happy");
    analyze("I am sad about this");
    analyze("I am not sad about this");
    analyze("I am not! I am sad");
    analyze("Are you happy about this?");
    analyze("Are you sad about this?");
    analyze("It's you! I am happy");
    analyze("It's you! I am sad");
  }
  static StringTokenizer st;
  static void analyze(String s) {
    prt("\nnew sentence >> " + s);
    boolean sad = false;
    st = new StringTokenizer(s);
    while (st.hasMoreTokens()) {
      String token = next();
      // Cherche jusqu'à ce l'on trouve un des
      // deux tokens de départ :
      if(!token.equals("I") &&
         !token.equals("Are"))
        continue; // Haut de la boucle while
      if(token.equals("I")) {
        String tk2 = next();
        if(!tk2.equals("am")) // Doit être après I
          break; // Sortie de la boucle while
        else {
          String tk3 = next();
          if(tk3.equals("sad")) {
            sad = true;
            break; // Sortie de la boucle while
          }
          if (tk3.equals("not")) {
            String tk4 = next();
            if(tk4.equals("sad"))
              break; // Laisse sad faux
            if(tk4.equals("happy")) {
              sad = true;
              break;
            }
          }
        }
      }
      if(token.equals("Are")) {
        String tk2 = next();
        if(!tk2.equals("you"))
          break; // Doit être après Are
        String tk3 = next();
        if(tk3.equals("sad"))
          sad = true;
        break; // Sortie de la boucle while
      }
    }
    if(sad) prt("Sad detected");
  }
  static String next() {
    if(st.hasMoreTokens()) {
      String s = st.nextToken();
      prt(s);
      return s;
    }
    else
      return "";
  }
  static void prt(String s) {
    System.out.println(s);
  }
} ///:~

Pour que chaque chaîne de caractère soit analysée, une boucle while est entrée et les tokens sont poussés hors de la chaîne de caractères. Notez la première déclaration de if, qui dit de continuer (retourne au début de la boucle et recommence encore) si le token est ni un « I » ou un « Are. » Ceci signifie qu'il attrapera les tokens que si un « I » ou un « Are » est trouvé. Vous pourriez penser utiliser le == à la place de la méthode equals(), mais cela ne fonctionne pas correctement, comme == compare les références de valeur tandis qu'equals() compare les contenus.

La logique du reste de la méthode analyze() est que le pattern (motif) dont on recherche la présence est « I am sad, » « I am not happy, » or « Are you sad? » Sans la déclaration break, le code réalisant ça serait bien plus confus qu'il ne l'est. Vous devez être conscient qu'un parseur typique (ceci en est un exemple primitif) possède normalement une table de ces tokens et un morceau de code qui bouge de l'un à l'autre des statuts dans la table quand les nouveaux tokens sont lus.

Vous pourrez voir le StringTokenizer seulement comme un raccourci pour un type de StreamTokenizer simple et spécifique. Cependant, si vous avez un String que vous désirez tokenizer et que StringTokenizer est trop limité, tout ce que vous avez à faire est de le retourner dans un flux avec StringBufferInputStream puis de l'utiliser pour créer un StreamTokenizer beaucoup plus puissant.

Vérifier le style de capitalization

Dans cette partie on observera un exemple un peut plus complet d'utilisation du Java E/S. Ce projet est directement utile puisqu'il accomplit une vérification de style pour être sur que la capitalisation se conforme au style Java comme décrit surjava.sun.com/docs/codeconv/index.html. Il ouvre chaque fichier .java dans le répertoire courant et extrait tous les noms de classe et d'identifiants, puis nous indique si affiche l'un d'entre eux ne correspond pas au style Java.

Afin que le programme s'exécute correctement, on devra d'abord construire un dépôt de noms de classes pour conserver tous les noms de classes dans la bibliothèque standard de Java. On réalise ceci en se déplaçant dans tous les sous-répertoires du code source de la bibliothèque standard Java et en exécutant ClassScanner dans chaque sous répertoire. Donnant comme arguments le nom du fichier dépositaire (utilisant le même chemin et nom chaque fois) et l'option -lign de commande -a pour indiquer que les noms de classes devront être ajoutés au dépôt.

Afin d'utiliser le programme pour vérifier votre code, indiquez lui le chemin et le nom du dépôt à employer. Il vérifiera toutes les classes et les identifiants du répertoire courant et vous vous dira lesquels ne suivent pas le style de capitalisation Java.

Vous devrez être conscient que ce programme n'est pas parfait ; il y a quelques fois ou il signalera ce qui lui semble être un problème mais en regardant le code vous verrez qu'il n'y a rien à changer. C'est quelque peut agaçant, mais c'est tellement plus simple que d'essayer de trouver tous ces cas en vérifiant de vos yeux votre code.

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