W3docs

Java PrintWriter

Écrivez du texte formaté dans des flux Java avec la classe PrintWriter — print, println, printf, format.

Le chapitre sur les flux de caractères a présenté Writer et sa méthode principale, write(String). C'est suffisant pour tout faire, mais ce n'est pas pratique — imprimer un nombre nécessite w.write(Integer.toString(n)), imprimer une ligne impose de se souvenir d'ajouter soi-même le terminateur de ligne, et la sortie formatée exige String.format à chaque appel. PrintWriter est le décorateur qui corrige l'ergonomie : il ajoute print, println, printf et format par-dessus n'importe quel Writer ou OutputStream sous-jacent.

C'est l'équivalent côté fichier du System.out que vous utilisez depuis le premier chapitre — même surface d'API, écrit dans un fichier plutôt que dans la console.

Ce qu'ajoute PrintWriter

void  print(boolean | char | int | long | float | double | String | Object);
void  println(...);                                  // same overloads, plus the line terminator
PrintWriter printf(String format, Object... args);   // String.format under the hood
PrintWriter format(String format, Object... args);   // alias for printf
PrintWriter append(CharSequence s);                  // returns this (for chaining)

Plus les méthodes héritées de Writer (write, flush, close). L'intérêt réside dans les surcharges typées : vous pouvez écrire n'importe quel primitif ou objet directement, et PrintWriter appelle String.valueOf pour vous.

try (PrintWriter w = new PrintWriter(Files.newBufferedWriter(path))) {
  w.println("header");
  w.printf("count = %d%n", 42);
  w.printf("rate  = %.2f%%%n", 0.875 * 100);
  w.println();                                       // blank line
}

Le close() du try-with-resources vide le tampon ; sans lui, le piège du tampon de fin décrit dans le chapitre sur les flux bufférisés s'applique tout autant à PrintWriter.

Constructeurs

Les plus utiles, par ordre de préférence :

PrintWriter(Path file, Charset charset);             // Java 10+, opens the file with the charset
PrintWriter(Writer out);                             // wrap any Writer (typical: a BufferedWriter)
PrintWriter(Writer out, boolean autoFlush);          // same, with autoFlush on println/printf
PrintWriter(OutputStream out, boolean autoFlush, Charset charset);

Le constructeur Path + Charset est le plus simple pour « ouvrir ce fichier et y écrire » :

try (PrintWriter w = new PrintWriter(path.toFile(), StandardCharsets.UTF_8)) {
  w.println("hello");
}

Il ouvre le fichier, l'enveloppe dans un OutputStreamWriter avec le jeu de caractères fourni, enveloppe celui-ci dans un BufferedWriter, et vous donne un PrintWriter. La pile de quatre couches que vous assembliez à la main se réduit à une seule ligne.

Passez toujours un jeu de caractères explicite. Les constructeurs sans jeu de caractères — new PrintWriter("file.txt"), new PrintWriter(outputStream) — se rabattent sur l'encodage par défaut de la JVM, ce qui constitue le même problème de portabilité décrit dans le chapitre sur les flux de caractères. UTF-8 est la valeur par défaut appropriée.

L'IOException avalée

PrintWriter diffère de tout autre Writer sur un point important : il ne lève pas IOException. Ni print, ni println, ni printf, ni write ne la déclarent. Si un appel I/O sous-jacent échoue, PrintWriter avale l'exception et positionne un indicateur d'erreur interne.

C'est pratique — vous pouvez écrire un long bloc d'appels println sans try/catch autour de chacun — mais cela signifie qu'une écriture ratée est silencieuse. Vous devez interroger :

if (w.checkError()) {
  // something went wrong; the underlying IOException was swallowed
  throw new IOException("write to " + path + " failed");
}

checkError() est le seul moyen de le découvrir. Il n'existe aucun moyen de récupérer l'IOException d'origine — au moment où vous vérifiez, elle a disparu. Ainsi :

  • Pour la sortie console (le cas d'usage de System.out), l'avalage est acceptable : personne ne gère une écriture échouée vers un terminal.
  • Pour les fichiers où une écriture partielle est un vrai problème (un fichier journal, un fichier de sauvegarde, un rapport généré), appelez checkError() à la fin du bloc, ou utilisez un BufferedWriter et appelez write vous-même afin que l'exception se propage.

Autoflush

L'argument autoFlush du constructeur contrôle si chaque appel à println, printf ou format appelle flush() ensuite. Par défaut, c'est désactivé :

new PrintWriter(out, false)                          // explicit close/flush only
new PrintWriter(out, true)                           // flush after every println/printf/format

print et write ne déclenchent jamais d'autoflush, même avec le drapeau activé — seules les méthodes de ligne et de formatage le font. C'est pourquoi System.out.print("waiting...") peut rester invisible pendant que le calcul suivant s'exécute, tandis que System.out.println("waiting...") s'affiche immédiatement.

Pour les fichiers, laissez l'autoflush désactivé et laissez close() (via try-with-resources) s'en charger. Pour un journal interactif que vous surveillez en temps réel, activez-le ou appelez flush() après chaque lot.

println et le séparateur de ligne de la plateforme

println() écrit System.lineSeparator()\n sur Unix et macOS, \r\n sur Windows. Idem pour le spécificateur %n dans printf. C'est une fonctionnalité pour la sortie terminal et un bug pour les fichiers de données ; la discussion du chapitre sur les flux bufférisés (sous BufferedWriter.newLine) s'applique ici mot pour mot :

w.printf("row,%d%n", 1);                             // platform-dependent terminator
w.printf("row,%d\n", 1);                             // portable \n

Lorsque la sortie doit être lue par autre chose que la machine locale, écrivez \n explicitement.

Comparaison avec PrintStream

PrintStream est le frère orienté octets de PrintWriter — même API, ancêtre différent. System.out et System.err sont des instances de PrintStream. Pour la sortie vers des fichiers, préférez PrintWriter : il vous oblige à réfléchir à l'encodage des caractères (car le constructeur prend un Charset), alors que PrintStream utilisera silencieusement l'encodage par défaut pour tout caractère non-ASCII que vous imprimez.

Le chapitre suivant, Java PrintStream, détaille les différences.

Un exemple concret : écrire un petit fichier CSV

Le programme ci-dessous ouvre un fichier temporaire avec un PrintWriter, écrit un petit CSV (en-tête + lignes), illustre le formatage avec printf, appelle checkError() pour confirmer la réussite des écritures, et montre enfin le comportement d'avalage de l'exception en écrivant vers un writer dont le flux sous-jacent a été fermé.

java— editable, runs on the server

Ce que retenir de l'exécution :

  • Le CSV est sorti exactement comme le spécifiait le format printf. %.2f a arrondi le prix à deux décimales ; %-10s a aligné à gauche dans une colonne de 10 caractères ; \n (et non %n) a conservé un terminateur de ligne portable. Les chaînes de format sont la principale raison de choisir PrintWriter plutôt qu'un Writer ordinaire.
  • Le premier try-with-resources a pris la pile de quatre couches — PathFileOutputStreamOutputStreamWriter(UTF-8)BufferedWriterPrintWriter — et l'a réduite à un seul appel de constructeur. Le constructeur (File, Charset) est celui qu'il faut utiliser pour « ouvrir ce fichier, y écrire du texte, le fermer proprement ».
  • La vérification de checkError() s'est exécutée avant la fermeture. Une fois que close() s'exécute, agir sur la vérification du drapeau est plus difficile — vous avez déjà quitté le bloc try. L'intérieur du bloc est le bon endroit pour vérifier.
  • Le PrintWriter avec autoflush activé, enveloppant System.out, a imprimé le rapport avec alignement de colonnes parce que chaque printf se terminait par %n, ce qui a déclenché le vidage. Il n'est pas enveloppé dans un try-with-resources — fermer un PrintWriter qui décore System.out fermerait System.out lui-même et rendrait muet tout ce qui est imprimé ensuite ; l'exemple vide donc à la place.
  • Le quatrième bloc a construit un writer dont le write lève toujours une exception, et y a pointé un PrintWriter. Le println a retourné normalement — aucune exception à attraper. checkError() a retourné true, ce qui était le seul moyen de savoir que l'écriture avait échoué. C'est le compromis de la conception avale-et-drapeau : pratique pour le code informel, dangereux si vous ne vérifiez pas.

Et ensuite

PrintWriter est le writer de fichiers orienté caractères. Son frère, Java PrintStream, est celui orienté octets qui alimente System.out et System.err — même API, ancêtre différent, et la raison pour laquelle la sortie terminal fonctionne pour tout caractère compatible avec le jeu de caractères par défaut de la plateforme.

Entraînement

Pratique
Vous écrivez un fichier de 10 000 lignes avec `PrintWriter` et n'appelez jamais `checkError()`. Trois lignes au milieu ont échoué à s'écrire en raison d'un disque plein. À quoi ressemble le fichier résultant, et que rapporte le programme ?
Vous écrivez un fichier de 10 000 lignes avec `PrintWriter` et n'appelez jamais `checkError()`. Trois lignes au milieu ont échoué à s'écrire en raison d'un disque plein. À quoi ressemble le fichier résultant, et que rapporte le programme ?
Was this page helpful?