W3docs

Java PrintWriter

Scrivi testo formattato su stream in Java con la classe PrintWriter — print, println, printf, format.

Il capitolo sugli stream di caratteri ha introdotto Writer e il suo metodo principale, write(String). È sufficiente per qualsiasi cosa, ma non è ergonomico — stampare un numero significa w.write(Integer.toString(n)), stampare una riga significa ricordarsi di aggiungere manualmente il terminatore di riga, e l'output formattato richiede String.format a ogni chiamata. PrintWriter è il decoratore che risolve il problema dell'ergonomia: aggiunge print, println, printf e format sopra qualsiasi Writer o OutputStream sottostante.

È la controparte lato file di System.out che hai usato fin dal primo capitolo — stessa superficie API, ma scritta su un file invece che sulla console.

Cosa aggiunge 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)

Più i metodi Writer ereditati (write, flush, close). Il punto è la presenza di overload tipizzati: puoi scrivere qualsiasi primitiva o oggetto direttamente e PrintWriter chiama String.valueOf al posto tuo.

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
}

Il close() del try-with-resources svuota il buffer; senza di esso, la trappola del buffer di coda descritta nel capitolo sugli stream bufferizzati si applica altrettanto a PrintWriter.

Costruttori

I più utili, in ordine di preferenza:

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);

Il costruttore Path + Charset è il più semplice per "apri questo file e scrivi su di esso":

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

Apre il file, lo avvolge in un OutputStreamWriter con il charset indicato, avvolge quest'ultimo in un BufferedWriter e ti restituisce un PrintWriter. Lo stack a quattro livelli che un tempo dovevi assemblare manualmente si riduce a una sola riga.

Passa sempre un charset esplicito. I costruttori senza charset — new PrintWriter("file.txt"), new PrintWriter(outputStream) — usano la codifica predefinita della JVM, che rappresenta lo stesso rischio di portabilità descritto nel capitolo sugli stream di caratteri. UTF-8 è il valore predefinito corretto.

L'IOException inghiottita

PrintWriter differisce da tutti gli altri Writer per un aspetto importante: non lancia IOException. Nessuno tra print, println, printf o write la dichiara. Se una chiamata I/O sottostante fallisce, PrintWriter inghiotte l'eccezione e imposta un flag interno di "errore".

Questo è comodo — puoi scrivere un lungo blocco di chiamate println senza try/catch attorno a ciascuna — ma significa che una scrittura fallita è silenziosa. Devi verificare esplicitamente:

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

checkError() è l'unico modo per scoprirlo. Non esiste alcun modo per recuperare l'IOException originale — quando la controlli, è già scomparsa. Quindi:

  • Per l'output su console (il caso d'uso di System.out), inghiottire l'eccezione va bene: nessuno gestisce una scrittura fallita su un terminale.
  • Per i file in cui una scrittura parziale è un problema reale (un file di log, un file di salvataggio, un report generato), controlla checkError() alla fine del blocco, oppure usa un BufferedWriter e chiama write direttamente così l'eccezione si propaga.

Autoflush

L'argomento del costruttore autoFlush controlla se ogni chiamata a println, printf o format invoca flush() a conclusione. Il valore predefinito è disattivato:

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

print e write non eseguono mai l'autoflush, nemmeno con il flag attivo — solo i metodi di riga e formato lo fanno. Ecco perché System.out.print("waiting...") può rimanere invisibile mentre il calcolo successivo è in esecuzione, mentre System.out.println("waiting...") appare immediatamente.

Per i file, lascia l'autoflush disattivato e lascia che sia close() (tramite try-with-resources) a gestirlo. Per un log interattivo che stai monitorando in tempo reale, attivalo oppure chiama flush() dopo ogni batch.

println e il separatore di riga della piattaforma

println() scrive System.lineSeparator()\n su Unix e macOS, \r\n su Windows. Lo stesso vale per l'identificatore %n in printf. Questo è un vantaggio per l'output su terminale e un problema per i file di dati; la discussione nel capitolo sugli stream bufferizzati (sotto BufferedWriter.newLine) si applica qui verbatim:

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

Quando l'output verrà letto da qualcosa che non sia il computer locale, scrivi \n esplicitamente.

Confronto con PrintStream

PrintStream è il fratello orientato ai byte di PrintWriter — stessa API, antenato diverso. System.out e System.err sono istanze di PrintStream. Per l'output su file, preferisci PrintWriter: ti costringe a ragionare sulla codifica dei caratteri (poiché il costruttore accetta un Charset), mentre PrintStream utilizzerà silenziosamente la codifica predefinita per qualsiasi carattere non ASCII che stampi.

Il capitolo successivo, Java PrintStream, approfondisce le differenze in dettaglio.

Un esempio pratico: scrivere un piccolo file CSV

Il programma seguente apre un file temporaneo con un PrintWriter, scrive un piccolo CSV (intestazione + righe), mostra la formattazione con printf, chiama checkError() per verificare che le scritture siano andate a buon fine, e infine illustra il comportamento di inghiottimento dell'eccezione scrivendo su uno writer il cui stream sottostante è stato chiuso.

java— editable, runs on the server

Cosa osservare dall'esecuzione:

  • Il CSV è uscito esattamente come specificato dal formato printf. %.2f ha arrotondato il prezzo a due decimali; %-10s ha allineato a sinistra entro una colonna di 10 caratteri; \n (non %n) ha mantenuto il terminatore di riga portabile. Le stringhe di formato sono il motivo principale per scegliere PrintWriter rispetto a un semplice Writer.
  • Il primo try-with-resources ha preso lo stack a quattro livelli — PathFileOutputStreamOutputStreamWriter(UTF-8)BufferedWriterPrintWriter — e lo ha collassato in una sola chiamata al costruttore. Il costruttore (File, Charset) è quello che vuoi per "apri questo file, scrivi testo su di esso, chiudilo in modo pulito."
  • Il controllo checkError() è stato eseguito prima della chiusura. Una volta eseguito close(), verificare il flag è più difficile da tradurre in azioni — hai già lasciato il blocco try. All'interno del blocco è il posto giusto per controllare.
  • Il PrintWriter con autoflush attivo che avvolge System.out ha stampato il report con colonne allineate perché ogni printf terminava con %n, il che ha scatenato il flush. Non è avvolto in un try-with-resources — chiudere un PrintWriter che decora System.out chiuderebbe System.out stesso e silenzerebbe tutto ciò che viene stampato in seguito, quindi l'esempio usa invece flush.
  • Il quarto blocco ha costruito uno writer il cui write lancia sempre un'eccezione e ha puntato un PrintWriter su di esso. La println è tornata normalmente — nessuna eccezione da catturare. checkError() ha restituito true, che era l'unico modo per scoprire che la scrittura era fallita. Questo è il compromesso del design swallow-and-flag: comodo per il codice informale, pericoloso se non si controlla.

Cosa viene dopo

PrintWriter è il file writer orientato ai caratteri. Il suo fratello, Java PrintStream, è quello orientato ai byte che alimenta System.out e System.err — stessa API, antenato diverso, e la ragione per cui l'output su terminale funziona per qualsiasi carattere che rientra nel charset predefinito della piattaforma.

Esercitazione

Pratica
Scrivi un file da 10.000 righe con `PrintWriter` e non chiami mai `checkError()`. Tre righe nel mezzo non sono state scritte a causa di un disco pieno. Com'è il file risultante e cosa riporta il programma?
Scrivi un file da 10.000 righe con `PrintWriter` e non chiami mai `checkError()`. Tre righe nel mezzo non sono state scritte a causa di un disco pieno. Com'è il file risultante e cosa riporta il programma?
Was this page helpful?