1. Introdução
Trabalhar com arquivos em Java (e não só!) é sempre trabalhar com recursos externos. Quando você abre um arquivo, o sistema operacional aloca para o seu programa um “descritor” — um identificador especial que permite ler e escrever no arquivo. A quantidade desses descritores é limitada: se você não fechar os arquivos, o programa pode rapidamente “consumir” todos os recursos disponíveis e começar a lançar erros enigmáticos do tipo "Too many open files".
Além disso, se o arquivo não for fechado, ele pode permanecer bloqueado para outros programas. Por exemplo, você abriu um arquivo para escrita, esqueceu de fechá-lo e agora nem você nem outra pessoa conseguem alterá-lo ou excluí-lo. Uma espécie de “sequestro eterno” no mundo dos sistemas de arquivos.
Exemplo do mundo real
FileInputStream fis = new FileInputStream("data.txt");
int b = fis.read();
// ... fazemos algo e depois esquecemos de chamar fis.close()
Se o método close() não for chamado, o arquivo ficará “pendurado” até o término do programa. Em aplicações grandes, isso pode levar a vazamentos de recursos e até à queda da aplicação.
2. Método antigo: finally + close()
Até o Java 7, a maneira clássica de garantir o fechamento de um arquivo era assim:
FileInputStream fis = null;
try {
fis = new FileInputStream("data.txt");
// lendo o arquivo
int b = fis.read();
// ...
} catch (IOException e) {
System.out.println("Erro ao ler o arquivo: " + e.getMessage());
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
System.out.println("Erro ao fechar o arquivo: " + e.getMessage());
}
}
}
Desvantagens dessa abordagem
- É fácil esquecer o bloco finally e acabar com vazamento de recursos.
- Muito código desnecessário, especialmente se houver vários streams.
- Se ocorrer uma exceção ao fechar, também é preciso tratá-la separadamente.
- O código fica pesado e menos legível.
3. Abordagem moderna: try-with-resources
Felizmente, no Java 7 surgiu uma sintaxe que resolve esses problemas de forma elegante e automática — try-with-resources.
Como é
try (FileInputStream fis = new FileInputStream("data.txt")) {
int b = fis.read();
// trabalhando com o arquivo
} catch (IOException e) {
System.out.println("Erro ao trabalhar com o arquivo: " + e.getMessage());
}
// Aqui fis já está fechado automaticamente!
O principal: todos os recursos declarados entre parênteses após try serão fechados automaticamente após o término do bloco — mesmo que uma exceção ocorra no meio. Não é preciso escrever finally, nem capturar erros de fechamento separados — o Java faz tudo por você.
Quais classes podem ser usadas em try-with-resources?
Qualquer classe que implemente a interface AutoCloseable (ou a veterana Closeable). Isso abrange praticamente todos os streams padrão de I/O: FileInputStream, FileOutputStream, BufferedReader, BufferedWriter, Scanner, PrintWriter, entre muitos outros.
4. Sintaxe do try-with-resources: detalhes e exemplos
Um recurso
try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"))) {
String line = reader.readLine();
System.out.println(line);
} catch (IOException e) {
System.out.println("Erro: " + e.getMessage());
}
// reader é fechado automaticamente!
Vários recursos
Você pode declarar vários recursos de uma vez separados por ponto e vírgula:
try (
BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))
) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
}
} catch (IOException e) {
System.out.println("Erro ao copiar: " + e.getMessage());
}
// ambos os streams foram fechados!
Ordem de fechamento: os recursos são fechados na ordem inversa à sua declaração. Primeiro será chamado writer.close(), depois reader.close(). Isso é importante se um stream depender do outro.
Uso com classes personalizadas
Se você estiver escrevendo sua própria classe que trabalha com recursos, basta implementar a interface AutoCloseable:
class MyResource implements AutoCloseable {
public void doSomething() {
System.out.println("Trabalhando com o recurso!");
}
@Override
public void close() {
System.out.println("Recurso fechado!");
}
}
try (MyResource res = new MyResource()) {
res.doSomething();
}
// Após sair do bloco, será impresso: "Recurso fechado!"
5. Como funciona: diagrama
flowchart TD
A[Abertura do recurso no try-with-resources] --> B{Ocorreu uma exceção no bloco try?}
B -- Não --> C[O recurso é fechado automaticamente]
B -- Sim --> D[O recurso é fechado automaticamente]
D --> E[A exceção é propagada adiante]
C --> F[O programa continua a execução]
Conclusão: independentemente de ter havido erro, o recurso sempre será fechado!
6. Exemplos: reescrevendo o código “do jeito novo”
Antes (estilo antigo):
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("input.txt"));
String line = reader.readLine();
System.out.println(line);
} catch (IOException e) {
System.out.println("Erro: " + e.getMessage());
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
System.out.println("Erro ao fechar: " + e.getMessage());
}
}
}
Depois (try-with-resources):
try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"))) {
String line = reader.readLine();
System.out.println(line);
} catch (IOException e) {
System.out.println("Erro: " + e.getMessage());
}
// Pronto, sem nenhum finally!
7. O que acontece com um erro durante o fechamento?
Às vezes, a própria operação de fechamento do recurso pode lançar uma exceção (por exemplo, se o arquivo desaparecer de repente). Em try-with-resources, tais exceções não se perdem: se já houve uma exceção no bloco try e, ao fechar o recurso, surgir uma segunda, ela será adicionada como “suprimida” (suppressed exception) à exceção principal. Isso pode ser visto com o método Throwable.getSuppressed().
Exemplo
try (MyResource res = new MyResource()) {
throw new IOException("Erro no bloco try");
} catch (IOException e) {
System.out.println("Erro principal: " + e.getMessage());
for (Throwable suppressed : e.getSuppressed()) {
System.out.println("Exceção suprimida: " + suppressed.getMessage());
}
}
8. Quais classes suportam try-with-resources?
É simples: qualquer classe que implemente a interface AutoCloseable. Aqui estão algumas das padrão:
| Classe | Finalidade |
|---|---|
|
Leitura de bytes de um arquivo |
|
Gravação de bytes em arquivo |
|
Leitura/gravação de texto |
|
Bufferização de streams |
|
Gravação de texto com formatação |
|
Leitura de dados de arquivo/console |
|
Serialização/desserialização |
|
Trabalho com arquivos ZIP |
|
Conexões de rede |
Se você usa bibliotecas de terceiros — consulte a documentação: se houver um método close(), é bem provável que a classe suporte try-with-resources.
9. Dicas e nuances úteis
Você pode declarar variáveis fora do try (a partir do Java 9): é possível usar recursos já declarados, se eles forem final ou “effectively final”:
BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
try (reader) {
// ...
}
Funciona não só com arquivos: try-with-resources é útil para quaisquer recursos: conexões de rede, bancos de dados, quaisquer objetos com método close().
Não ignore as exceções: mesmo com try-with-resources, lembre-se de capturar e tratar exceções — não é uma panaceia, apenas uma forma prática de evitar vazamentos.
Não feche o recurso manualmente dentro do try: isso não é necessário — o Java fará isso por você! Se você chamar close() manualmente e depois o bloco try terminar, haverá uma tentativa de fechar um recurso já fechado. Geralmente é seguro, mas pode confundir.
10. Erros comuns ao usar try-with-resources
Erro nº 1: esquecer de usar try-with-resources. Se você ainda escreve finally { resource.close(); } — ou você está em 2011, ou não leu esta aula! Use a sintaxe moderna.
Erro nº 2: declarar o recurso fora do try e apenas usá-lo dentro. Esse código não fechará o recurso automaticamente:
BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
try {
// ... usamos reader
} finally {
// Mas aqui esquecemos de fechar!
}
Erro nº 3: chamar close() manualmente dentro do bloco try. Não é crítico, mas é redundante e pode levar a fechamento duplo. Simplesmente confie no Java.
Erro nº 4: capturar apenas Exception, ignorando a especificidade de E/S. É melhor capturar exceções específicas (FileNotFoundException, IOException) para fornecer mensagens claras ao usuário.
Erro nº 5: não tratar as exceções suprimidas. Se ocorrer um erro ao fechar o recurso, ele pode ser “suprimido”. Se você analisa erros, não se esqueça de getSuppressed().
GO TO FULL VERSION