W3docs

Java PrintWriter

Escreva texto formatado em streams com a classe PrintWriter do Java — print, println, printf, format.

O capítulo sobre streams de caracteres apresentou Writer e seu método principal, write(String). Isso é suficiente para qualquer coisa, mas não é ergonômico — imprimir um número exige w.write(Integer.toString(n)), imprimir uma linha significa lembrar de acrescentar o terminador de linha manualmente, e a saída formatada requer String.format em cada chamada. PrintWriter é o decorator que corrige a ergonomia: ele adiciona print, println, printf e format sobre qualquer Writer ou OutputStream subjacente.

É o equivalente no lado de arquivos ao System.out que você usa desde o primeiro capítulo — mesma superfície de API, escrevendo em um arquivo em vez do console.

O que PrintWriter adiciona

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)

Além dos métodos herdados de Writer (write, flush, close). O ponto são as sobrecargas tipadas: você pode escrever qualquer primitivo ou objeto diretamente e o PrintWriter chama String.valueOf para você.

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
}

O close() do try-with-resources libera o buffer; sem ele, a armadilha do buffer final descrita no capítulo sobre streams com buffer se aplica igualmente ao PrintWriter.

Construtores

Os mais úteis, em ordem de preferência:

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

O construtor Path + Charset é o mais simples para "abrir este arquivo e escrever nele":

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

Ele abre o arquivo, envolve-o em um OutputStreamWriter com o charset fornecido, envolve isso em um BufferedWriter e lhe entrega um PrintWriter. A pilha de quatro camadas que você montava manualmente se reduz a uma linha.

Sempre passe um charset explícito. Os construtores sem charset — new PrintWriter("file.txt"), new PrintWriter(outputStream) — recorrem à codificação padrão da JVM, que é o mesmo risco de portabilidade descrito no capítulo sobre streams de caracteres. UTF-8 é o padrão correto.

O IOException engolido

PrintWriter difere de todos os outros Writer em um aspecto importante: ele não lança IOException. Nenhum de print, println, printf ou write o declara. Se uma chamada de I/O subjacente falhar, o PrintWriter engole a exceção e define um sinalizador interno de "erro".

Isso é conveniente — você pode escrever um longo bloco de chamadas println sem try/catch em cada uma — mas significa que uma escrita com falha é silenciosa. Você precisa perguntar:

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

checkError() é a única maneira de descobrir. Não há como recuperar o IOException original — quando você verifica, ele já foi perdido. Portanto:

  • Para saída no console (o caso de uso do System.out), engolir a exceção é aceitável: ninguém trata uma escrita com falha em um terminal.
  • Para arquivos onde uma escrita parcial é um problema real (um arquivo de log, um arquivo de salvamento, um relatório gerado), verifique checkError() ao final do bloco, ou use um BufferedWriter e chame write você mesmo para que a exceção se propague.

Autoflush

O argumento autoFlush do construtor controla se cada chamada a println, printf ou format chama flush() depois. O padrão é desligado:

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

print e write nunca fazem autoflush, mesmo com o sinalizador ativado — somente os métodos de linha e formato o fazem. É por isso que System.out.print("waiting...") pode ficar invisível enquanto o próximo cálculo é executado, e System.out.println("waiting...") aparece imediatamente.

Para arquivos, deixe o autoflush desligado e deixe close() (via try-with-resources) cuidar disso. Para um log interativo que você está monitorando, ative-o ou chame flush() após cada lote.

println e o separador de linha da plataforma

println() escreve System.lineSeparator()\n no Unix e macOS, \r\n no Windows. O mesmo vale para o especificador %n no printf. Isso é uma funcionalidade para saída em terminal e um bug para arquivos de dados; a discussão no capítulo sobre streams com buffer (em BufferedWriter.newLine) aplica-se aqui da mesma forma:

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

Quando a saída será lida por qualquer coisa além da máquina local, escreva \n explicitamente.

Comparação com PrintStream

PrintStream é o irmão orientado a bytes do PrintWriter — mesma API, ancestral diferente. System.out e System.err são instâncias de PrintStream. Para saída em arquivo, prefira PrintWriter: ele obriga você a pensar na codificação de caracteres (porque o construtor recebe um Charset), enquanto PrintStream silenciosamente usará a codificação padrão para qualquer caractere não ASCII que você imprimir.

O próximo capítulo, Java PrintStream, detalha as diferenças.

Exemplo prático: escrevendo um pequeno arquivo CSV

O programa abaixo abre um arquivo temporário com um PrintWriter, escreve um pequeno CSV (cabeçalho + linhas), demonstra a formatação com printf, chama checkError() para confirmar que as escritas foram bem-sucedidas e, por fim, mostra o comportamento de engolir exceções escrevendo em um writer cujo stream subjacente foi fechado.

java— editable, runs on the server

O que observar na execução:

  • O CSV saiu exatamente como o formato printf especificou. %.2f arredondou o preço para duas casas decimais; %-10s alinhou à esquerda em uma coluna de 10 caracteres; \n (não %n) manteve o terminador de linha portável. As strings de formato são o principal motivo para escolher PrintWriter em vez de Writer simples.
  • O primeiro try-with-resources comprimiu a pilha de quatro camadas — PathFileOutputStreamOutputStreamWriter(UTF-8)BufferedWriterPrintWriter — em uma única chamada de construtor. O construtor (File, Charset) é o que você quer para "abrir este arquivo, escrever texto nele, fechá-lo corretamente."
  • A verificação com checkError() foi feita antes do close. Depois que close() é executado, verificar o sinalizador é mais difícil de agir — você já saiu do bloco try. Dentro do bloco é o lugar certo para verificar.
  • O PrintWriter com autoflush ativado envolvendo System.out imprimiu o relatório alinhado em colunas porque cada printf terminou com %n, o que acionou o flush. Ele não está envolvido em um try-with-resources — fechar um PrintWriter que decora System.out fecharia o próprio System.out e silenciaria tudo impresso depois, então o exemplo usa flush em vez disso.
  • O quarto bloco construiu um writer cujo write sempre lança exceção e apontou um PrintWriter para ele. O println retornou normalmente — sem exceção a capturar. checkError() retornou true, que era a única maneira de descobrir que a escrita havia falhado. Esse é o compromisso do design de engolir e sinalizar: conveniente para código casual, perigoso se você não verificar.

O que vem a seguir

PrintWriter é o escritor de arquivos orientado a caracteres. Seu irmão, Java PrintStream, é o orientado a bytes que alimenta System.out e System.err — mesma API, ancestral diferente, e a razão pela qual a saída no terminal funciona para qualquer caractere que caiba no charset padrão da plataforma.

Prática

Prática
Você escreve um arquivo de 10.000 linhas com `PrintWriter` e nunca chama `checkError()`. Três linhas no meio falharam ao ser escritas por causa de disco cheio. Como fica o arquivo resultante e o que o programa reporta?
Você escreve um arquivo de 10.000 linhas com `PrintWriter` e nunca chama `checkError()`. Três linhas no meio falharam ao ser escritas por causa de disco cheio. Como fica o arquivo resultante e o que o programa reporta?
Was this page helpful?