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
PrintWriterpara arquivos. A hierarquia orientada a caracteres é onde a codificação pertence. - Use
PrintStreamquando for necessário — ou seja, quandoSystem.out/System.errfor o destino, ou quando você estiver escrevendo em umOutputStreamque 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 NPEfunciona. 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.
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 oPrintStreamrespeita 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:
printlnretornou normalmente, aIOExceptionsubjacente desapareceu, echeckError()foi a única maneira de descobrir que a escrita havia falhado. Mesmo contrato que oPrintWriter. 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 umaNullPointerException. É assim queprintln(someList)funciona mesmo quandosomeListé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. UseObjects.requireNonNullou uma verificação nula explícita na fronteira se essa distinção for importante. - Nada no exemplo chamou um
PrintWriter. ParaSystem.out, você não precisa de um —PrintStreamé o tipo que o Java já forneceu, a API é idêntica, e o comportamento de autoflush emprintlné 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.