W3docs

Java PrintStream

Como PrintStream alimenta System.out e System.err e como usá-lo para saída formatada orientada a bytes.

PrintStream é a classe que existe por baixo do seu código desde o capítulo 1. System.out é um PrintStream. System.err é um PrintStream. Cada System.out.println(...) que você já escreveu passou por essa classe.

Ela tem a mesma superfície que o PrintWriter que você acabou de conhecer — print, println, printf, format — e o mesmo comportamento de engolir exceções. A diferença está no que ela está em cima: PrintStream estende OutputStream (bytes), enquanto PrintWriter estende Writer (caracteres). Para saída em arquivo, a distinção byte/caractere abordada anteriormente nesta parte ainda se aplica: caracteres entram, caracteres saem, e a codificação vive na fronteira.

Por que duas classes com a mesma API?

História. O Java 1.0 tinha PrintStream mas não tinha hierarquia de Writer — toda operação de "print" ia para um fluxo de bytes. O Java 1.1 introduziu a hierarquia Reader/Writer para o tratamento adequado de caracteres e adicionou PrintWriter para que o código de escrita em arquivos pudesse usar a mesma API com caracteres. O PrintStream não pôde ser descontinuado porque System.out e System.err já estavam tipados como PrintStream nas APIs publicadas, e alterá-los teria quebrado todos os programas do mundo.

Por isso ambos existem. A regra prática:

  • Use PrintWriter para arquivos. A hierarquia orientada a caracteres é onde a codificação pertence.
  • Use PrintStream quando for necessário — ou seja, quando System.out/System.err for o destino, ou quando você estiver escrevendo em um OutputStream que não quer envolver.

Os casos de "necessidade" são raros. Na maioria das vezes você pode fazer isso:

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

e esquecer que PrintStream existe.

A API

Idêntica à do 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);

Mais os métodos herdados de OutputStream (write(int), write(byte[]), flush, close). A mesma armadilha do BufferedWriter e do PrintWriter se aplica: println escreve System.lineSeparator(), que é \r\n no Windows. Escreva \n explicitamente quando a saída precisar ser portável.

Construtores

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

Assim como no PrintWriter, os construtores sem charset recorrem à codificação padrão da JVM — o mesmo risco de portabilidade descrito no capítulo de fluxos de caracteres. Sempre passe um charset.

O sinalizador autoFlush tem a mesma semântica que o PrintWriter: quando ativado, println, printf, format e write(byte[], int, int) em uma nova linha disparam um flush. print não dispara. Desativado por padrão.

A IOException engolida (ainda)

Mesmo design que o PrintWriter. Nenhum dos métodos print/println/printf lança IOException. Uma escrita com falha define um sinalizador de erro que você lê com checkError(). A compensação é a mesma: conveniente para código casual, perigoso se você não verificar.

Para System.out/System.err especificamente, engolir a exceção é a escolha certa — não há nada útil a fazer quando uma escrita no terminal falha. Para um PrintStream apoiado em arquivo, prefira PrintWriter, ou verifique checkError() antes de fechar.

System.out e System.err

Esses dois são instâncias de PrintStream criadas durante a inicialização da JVM. Eles encapsulam os descritores de arquivo stdout e stderr do sistema operacional. Sua codificação de caracteres segue stdout.encoding (Java 18+) ou file.encoding (versões anteriores), razão pela qual a saída redirecionada por pipe às vezes apresenta mojibake em um console Windows — a página de código do console não corresponde à ideia de codificação da JVM.

Você pode substituí-los com System.setOut(PrintStream) e System.setErr(PrintStream), o que é ocasionalmente útil para capturar saída em testes:

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

Para código de produção, deixe-os como estão. Frameworks de logging (java.util.logging, SLF4J/Logback) adotam uma abordagem diferente e estruturada para escrever saída de diagnóstico.

print(Object) e null

Um comportamento sutil compartilhado com PrintWriter: print(Object o) chama String.valueOf(o), que retorna a string de quatro caracteres "null" para uma referência nula em vez de lançar NullPointerException. É por isso que

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

funciona. Conveniente para logging casual; enganoso se você estiver escrevendo a string de volta em um arquivo de dados que vai reanalisar depois — "null" como string é indistinguível da palavra literal "null".

write(int) escreve um byte, não um caractere

PrintStream é um OutputStream. O write(int b) herdado escreve o byte de ordem inferior:

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

A segunda linha está errada em um terminal UTF-8 — 'é' tem dois bytes em UTF-8 (0xC3 0xA9), e você escreveu apenas um. Não use write(int) em um PrintStream para caracteres; use print/println, que passam pelo charset configurado.

Um exemplo prático: System.out redirecionado e inspecionado

O programa abaixo captura System.out em um ByteArrayOutputStream para que você possa ver exatamente quais bytes a JVM emite ao chamar println. Ele executa o mesmo println("Café") com dois charsets diferentes para tornar o comportamento de codificação concreto, demonstra checkError() em um fluxo com falha e, por fim, mostra a diferença entre print(Object) para uma referência null e uma verificação nula deliberada.

java— editable, runs on the server

O que extrair da execução:

  • System.setOut(new PrintStream(buffer, ...)) capturou o que de outra forma teria ido para o console. Os testes usam esse padrão o tempo todo. Restaure o original antes de imprimir seu relatório — caso contrário, o relatório também vai para o buffer e a confusão segue.
  • A linha "Café" emitiu 5 bytes em UTF-8 (43 61 66 C3 A9) e 4 bytes em ISO-8859-1 (43 61 66 E9). Mesma entrada, larguras de byte diferentes, ambas corretas — a codificação é o mapeamento byte → caractere, e o PrintStream respeita o charset fornecido ao seu construtor. O construtor sem charset escolheria qualquer um desses que a JVM estivesse usando no momento.
  • O bloco do fluxo quebrado provou o engolimento: println retornou normalmente, a IOException subjacente desapareceu, e checkError() foi a única maneira de descobrir que a escrita havia falhado. Mesmo contrato que o PrintWriter. Se você se importa com a falha, deve perguntar.
  • A impressão de referência nula produziu a string de quatro caracteres null, não uma NullPointerException. É assim que println(someList) funciona mesmo quando someList é null — conveniente, mas significa que você não consegue distinguir o texto literal "null" de uma referência nula uma vez que está no disco. Use Objects.requireNonNull ou uma verificação nula explícita na fronteira se essa distinção for importante.
  • Nada no exemplo chamou um PrintWriter. Para System.out, você não precisa de um — PrintStream é o tipo que o Java já forneceu, a API é idêntica, e o comportamento de autoflush em println é o que você quer no terminal.

O que vem a seguir

Os primeiros treze capítulos desta parte cobriram todas as formas de I/O por streaming: bytes, caracteres, buffering, primitivos, texto formatado. Todos transmitem conteúdo — bytes e chars. O próximo capítulo, Java Serialization, é sobre transmitir grafos de objetos — uma estrutura encadeada inteira de referências, escrita em um fluxo e reconstruída do outro lado, com apenas uma anotação na classe.

Prática

Prática
`PrintWriter` e `PrintStream` têm APIs quase idênticas (`print`, `println`, `printf`). Ao escrever texto em um arquivo, qual você geralmente deve preferir e por quê?
`PrintWriter` e `PrintStream` têm APIs quase idênticas (`print`, `println`, `printf`). Ao escrever texto em um arquivo, qual você geralmente deve preferir e por quê?
Was this page helpful?