Les méthodes read et write sont les plus primitives. Il est généralement nécessaire, tant par commodité que par efficacité, de recourir à d'autres méthodes.
Par exemple, pour imprimer une représentation textuelle d'une donnée quelconque sur un flot d'octets, on invoque print ou println, qui ne sont définies que dans PrintStream, qui est une sous-classe de OutputStream. Si l'on veut imprimer ces représentations dans un fichier, on ne peut pas utiliser simplement FileOutputStream, car cette classe est dépourvue de ces méthodes d'impression. Comment faire pour disposer à la fois des fonctionnalités de PrintStream et de FileOutputStream ? Il n'est pas possible, en Java, de définir une classe qui soit sous-classe directe de deux classes : le mécanisme d'extension, souvent utilisé pour ajouter des fonctionnalités à une classe est ici insuffisant. On peut par contre procéder par délégation : on construit une instance de PrintStream, qui délègue à une instance de FileOutputStream les écritures dans un fichier. L'objet délégué apparaît comme argument du constructeur de PrintStream ; l'argument true permet de vider automatiquement le tampon d'écriture à la fin des lignes :
PrintStream out = new PrintStream( // décorateur new FileOutputStream("out.txt"), true); // délégué out.println(2); out.println(new Integer(2));
Ce mécanisme est très courant parmi les classes du paquet java.io : le constructeur prend en argument un flot auquel il délègue certaines opérations et lui ajoute des fonctionnalités. Il s'agit d'un pattern dit de décoration, également très employé dans les classes graphiques (par exemple, pour décorer une fenêtre).
Les classes suivantes (sous-classes de FilterOutputStream, elle-même sous-classe de OutputStream) ont toutes un constructeur qui prend en argument un OutputStream et lui ajoutent des fonctionnalités ; ce sont des classes de décoration :
Chaque opération de lecture ou d'écriture peut être très coûteuse sur
certains flots ; c'est notamment le cas des accès à un fichier, ou pire,
des accès à l'Internet. Pour éviter des opérations individuelles (sur un
octet à la fois), on préfère souvent travailler sur un tampon
(anglais buffer). Par exemple, pour écrire sur un fichier, on
écrira sur un flot-tampon, lequel délègue les écritures à un flot
sur un fichier. Les classes qui mettent en uvre ces
tampons sont :
L'exemple suivant, qui empile trois constructeurs, permet d'ajouter successivement un tampon d'écriture et les méthodes d'impression textuelle à un flot d'écriture sur fichier :
PrintStream out = new PrintStream( // décorateur new BufferedOutputStream( // décorateur new FileOutputStream("out.txt"))); // délégué out.println(2); out.println(new Integer(2));