Il y a quatre classes "mères", abstraites, pour traiter les flots de données héritant directement d'Object :
Certaines classes héritées de InputStream (resp. Reader) servent essentiellement à extraire, à partir d'une certaine source, des données pour le programme sous forme d'octets ou de tableaux d'octets (resp. de caractères ou de tableaux de caractères) ; certaines classes héritées de OutputStream (resp. Writer) servent à extraire des données se présentant sous forme d'octets ou de tableaux d'octets (resp. de caractères ou de tableaux de caractères) à partir du programme pour une certaine destination ; l'ensemble de ces classes forme les classes d'extraction.
Il ne serait pas très pratique de ne disposer que des fonctionnalités offertes par les classes d'extraction ; il est par exemple utile de demander d'écrire un double, de lire une ligne de caractères, etc. D'autres classes font appel aux classes d'extraction pour leur ajouter des fonctionnalités ; nous les appellerons classes de fonctionnalités.
Prenons un exemple. La classe de fonctionnalités PrintWriter admet un constructeur ayant un paramètre de type Writer. Les classes FileWriter et StringWriter, qui héritent de Writer, sont deux classes d'extraction qui servent à écrire des caractères issus des données d'un programme (un par un ou à partir d'un tableau ou d'une chaîne) dans un fichier pour la première ou dans une chaîne de caractères pour la seconde. On pourra écrire :
FileWriter sortieFichier = new FileWriter("fichier.txt");
PrintWriter out1 = new PrintWriter(sortieFichier);
ou
StringWriter sortieChaine = new StringWriter(); PrintWriter out2 = new PrintWriter(sortieChaine);
Une telle composition d'une instance de l'une de ces deux classes avec une instance de la classe de fonctionnalités PrintWriter permet de disposer de multiples fonctionnalités ; par exemple, la classe PrintWriter dispose d'une méthode println(int i) permettant d'écrire un int sous forme d'une chaîne de caractères ; cette méthode effectue grosso modo :
String s = String.valueOf(i);
sortie.write(s, 0, s.length());
où :
Ainsi, si i est de type int
Nous verrons dans nos exemples qu'on traite très fréquemment un flux de données en construisant une classe de fonctionnalités en association avec une classe d'extraction. Les classes d'extraction sont relatives au « récipient » (fichier, chaîne de caractères, socket etc.) dans lequel on lit ou on écrit les octets ; les classes de fonctionnalités sont relatives à la façon de traiter, par rapport à ces lectures ou écritures, les données du programme (écrire une ligne, lire un entier...). Ce découpage donne beaucoup de souplesse et diminue le nombre de classes nécessaires en « factorisant » les fonctionnalités.
Les traitements de flux de données sont schématisés par deux les figures suivantes.
Nous avons retenu trois façons de bipartitionner les classes de java.io : selon qu'elles servent à lire ou à écrire, selon que cela concerne des flux de caractères ou non, selon qu'il s'agit d'une classe d'extraction ou de fonctionnalités ; la figure ci-dessous schématise ces partitionnements. L'essentiel des classes du paquetage java.io appartiennent à l'un des huit sous-ensembles ainsi définis.
Nous ne traiterons que des classes qui servent dans les applications les plus courantes, et en particulier de celles qui permettent d'utiliser des fichiers. On pourra consulter la documentation en ligne de Java pour connaître l'étendue des possibilités. Nous indiquons ci-dessous les lignées d'une bonne partie des classes du paquetage java.io. Les cases grisées représentent des classes d'extraction et les cases blanches des classes de fonctionnalités.
Les classes héritant directement de InputStream et quelques autres classes
Les classes héritant directement de OutputStream et quelques autres classes
Les classes héritant de Reader
Les classes héritant de Writer
Nous illustrerons aussi dans notre dernier exemple l'utilisation de la classe File.
© Irène Charon, Télécom-ParisTech 2011