W3docs

Java PrintStream

Come PrintStream alimenta System.out e System.err e come usarlo per output formattato orientato ai byte.

PrintStream è la classe che opera sotto il tuo codice fin dal capitolo 1. System.out è un PrintStream. System.err è un PrintStream. Ogni System.out.println(...) che hai mai scritto è passato attraverso questa classe.

Ha la stessa interfaccia del PrintWriter che hai appena incontrato — print, println, printf, format — e lo stesso comportamento di assorbimento delle eccezioni. La differenza sta in ciò su cui si basa: PrintStream estende OutputStream (byte), mentre PrintWriter estende Writer (caratteri). Per l'output su file, la distinzione byte/carattere vista in precedenza in questa parte si applica ancora: caratteri in ingresso, caratteri in uscita, e la codifica risiede al confine.

Perché due classi con la stessa API?

È storia. Java 1.0 aveva PrintStream ma nessuna gerarchia Writer — ogni "print" andava a un byte stream. Java 1.1 ha introdotto la gerarchia Reader/Writer per una corretta gestione dei caratteri e ha aggiunto PrintWriter in modo che il codice di scrittura su file potesse usare la stessa API sui caratteri. PrintStream non poteva essere rimosso perché System.out e System.err erano già tipizzati come PrintStream nelle API pubblicate, e cambiarli avrebbe rotto ogni programma nel mondo.

Quindi esistono entrambi. La regola pratica:

  • Usa PrintWriter per i file. La gerarchia orientata ai caratteri è dove appartiene la codifica.
  • Usa PrintStream quando devi — cioè, quando System.out/System.err è la destinazione, o quando stai scrivendo su un OutputStream che non vuoi wrappare.

I casi di "devi" sono limitati. La maggior parte delle volte puoi fare così:

PrintWriter out = new PrintWriter(System.out, true, StandardCharsets.UTF_8);
out.println("hello");

e dimenticarti che PrintStream esiste.

L'API

Identica a PrintWriter:

void print(boolean | char | int | long | float | double | String | Object);
void println(...);                                  // adds the platform line separator
PrintStream printf(String format, Object... args);
PrintStream format(String format, Object... args);
PrintStream append(CharSequence s);

Più i metodi OutputStream ereditati (write(int), write(byte[]), flush, close). La stessa trappola di BufferedWriter e PrintWriter si applica: println scrive System.lineSeparator(), che è \r\n su Windows. Scrivi \n esplicitamente quando l'output deve essere portabile.

Costruttori

new PrintStream(OutputStream out);                                   // platform default charset
new PrintStream(OutputStream out, boolean autoFlush, Charset cs);    // explicit charset
new PrintStream(File file, Charset charset);                          // open a file
new PrintStream(String filename, Charset charset);

Come con PrintWriter, i costruttori senza charset ricadono sulla codifica predefinita della JVM — lo stesso rischio di portabilità descritto nel capitolo sugli stream di caratteri. Passa sempre un charset.

Il flag autoFlush ha la stessa semantica di PrintWriter: quando attivo, println, printf, format e write(byte[], int, int) su un newline attivano un flush. print non lo fa. Disattivo per impostazione predefinita.

La IOException inghiottita (ancora)

Stesso design di PrintWriter. Nessuno dei metodi print/println/printf lancia IOException. Una scrittura fallita imposta un flag di errore che si legge con checkError(). Il compromesso è lo stesso: comodo per codice occasionale, pericoloso se non verifichi.

Per System.out/System.err specificamente, inghiottire è la scelta giusta — non c'è nulla di utile da fare quando una scrittura sul terminale fallisce. Per un PrintStream su file, preferisci PrintWriter, o controlla checkError() prima di chiudere.

System.out e System.err

Questi due sono istanze di PrintStream create durante l'avvio della JVM. Wrappano i descrittori di file stdout e stderr del sistema operativo. La loro codifica dei caratteri segue stdout.encoding (Java 18+) o file.encoding (versioni più vecchie), motivo per cui l'output reindirizzato tramite pipe a volte produce mojibake su una console Windows — la code page della console non corrisponde all'idea di codifica della JVM.

Puoi sostituirli con System.setOut(PrintStream) e System.setErr(PrintStream), il che è occasionalmente utile per catturare l'output nei test:

ByteArrayOutputStream captured = new ByteArrayOutputStream();
PrintStream original = System.out;
System.setOut(new PrintStream(captured, true, StandardCharsets.UTF_8));
try {
  runTheCodeUnderTest();
  assertEquals("expected\n", captured.toString(StandardCharsets.UTF_8));
} finally {
  System.setOut(original);
}

Per il codice di produzione, lasciali stare. I framework di logging (java.util.logging, SLF4J/Logback) adottano un approccio diverso e strutturato per scrivere output diagnostico.

print(Object) e null

Un comportamento sottile condiviso con PrintWriter: print(Object o) chiama String.valueOf(o), che restituisce la stringa di quattro caratteri "null" per un riferimento null invece di lanciare NullPointerException. Questo è il motivo per cui

System.out.println(maybeNullList);                  // prints "null", not NPE

funziona. Comodo per il logging occasionale; fuorviante se stai scrivendo la stringa in un file di dati che rileggerai in seguito — "null" come stringa è indistinguibile dalla parola letterale "null."

write(int) scrive un byte, non un carattere

PrintStream è un OutputStream. Il metodo ereditato write(int b) scrive il byte di ordine basso:

System.out.write(65);                              // writes 'A' — the byte 0x41
System.out.write('é');                              // writes a single byte 0xE9 — NOT UTF-8 for 'é'

La seconda riga è sbagliata su un terminale UTF-8 — 'é' è due byte in UTF-8 (0xC3 0xA9), e ne hai scritto uno solo. Non usare write(int) su un PrintStream per i caratteri; usa print/println, che passano attraverso il charset configurato.

Un esempio concreto: System.out reindirizzato e ispezionato

Il programma seguente cattura System.out in un ByteArrayOutputStream per vedere esattamente quali byte emette la JVM quando chiami println. Esegue la stessa println("Café") con due charset diversi per rendere concreto il comportamento della codifica, dimostra checkError() su uno stream fallimentare, e infine mostra la differenza tra print(Object) per un riferimento null e un controllo null esplicito.

java— editable, runs on the server

Cosa trarre dall'esecuzione:

  • System.setOut(new PrintStream(buffer, ...)) ha catturato ciò che altrimenti sarebbe andato alla console. I test usano questo pattern continuamente. Ripristina l'originale prima di stampare il report — altrimenti il report va nel buffer, e ne deriva confusione.
  • La riga "Café" ha emesso 5 byte in UTF-8 (43 61 66 C3 A9) e 4 byte in ISO-8859-1 (43 61 66 E9). Stesso input, larghezze in byte diverse, entrambi corretti — la codifica è la mappatura byte → carattere, e PrintStream rispetta il charset fornito al suo costruttore. Il costruttore senza charset avrebbe scelto quello in uso dalla JVM in quel momento.
  • Il blocco con lo stream rotto ha dimostrato l'inghiottimento: println è tornato normalmente, la IOException sottostante è scomparsa, e checkError() era l'unico modo per scoprire che la scrittura era fallita. Stesso contratto di PrintWriter. Se ti interessa il fallimento, devi chiedere.
  • La stampa del riferimento null ha prodotto la stringa di quattro caratteri null, non una NullPointerException. È così che println(someList) funziona anche quando someList è null — comodo, ma significa che non puoi distinguere il testo letterale "null" da un riferimento null una volta che è su disco. Usa Objects.requireNonNull o un controllo null esplicito al confine se quella distinzione è importante.
  • Nulla nell'esempio ha chiamato un PrintWriter. Per System.out, non ne hai bisogno — PrintStream è il tipo che Java ti ha già dato, l'API è identica, e il comportamento di autoflush su println è quello che vuoi al terminale.

Cosa viene dopo

I primi tredici capitoli di questa parte hanno coperto ogni forma di I/O in streaming: byte, caratteri, buffering, primitivi, testo formattato. Trasmettono tutti contenuto — byte e char. Il prossimo capitolo, Java Serialization, riguarda la trasmissione di grafi di oggetti — un'intera struttura collegata di riferimenti, scritta su uno stream e ricostruita dall'altro lato, con una singola annotazione sulla classe.

Pratica

Pratica
`PrintWriter` e `PrintStream` hanno API quasi identiche (`print`, `println`, `printf`). Quando si scrive testo su un file, quale si dovrebbe generalmente preferire, e perché?
`PrintWriter` e `PrintStream` hanno API quasi identiche (`print`, `println`, `printf`). Quando si scrive testo su un file, quale si dovrebbe generalmente preferire, e perché?
Was this page helpful?