Java finally e try-with-resources
Às vezes existe uma tarefa que precisa acontecer de qualquer jeito, tenha dado certo ou errado: fechar o arquivo, desligar a conexão, apagar a luz ao sair. Para isso o Java tem o bloco finally e uma forma moderna e mais segura, o try-with-resources, que fecha as coisas sozinho.
- O que é o bloco
finallye em que situações ele roda (spoiler: quase todas, até depois de umreturn). - O que é um recurso e por que ele precisa ser fechado.
- Usar o try-with-resources com um
Scannerlendo um arquivo. - Criar uma classe própria que funciona no try-with-resources, com
AutoCloseable. - A ordem em que tudo acontece e o que são as exceções suprimidas.
Esta aula continua try e catch; se ainda não leu, comece por lá.
A ideia antes do código
A Ana é a última a sair da loja. O combinado é: ao sair, apagar as luzes. Não importa se o dia foi ótimo, se faltou troco, se ela teve que sair correndo: a luz tem que ser apagada. Ela não vai escrever "apagar a luz" no fim de cada possível dia; ela cola um bilhete na porta: "ao sair, finalmente, apague a luz".
O finally ("finalmente") é esse bilhete. É um bloco que vem depois do try (e dos catch, se houver) e roda sempre que o programa sai do try: terminando normalmente, por causa de uma exceção ou por um return.
O bloco finally
public class Main {
static void converter(String texto) {
try {
int n = Integer.parseInt(texto);
System.out.println("Convertido: " + n);
} catch (NumberFormatException e) {
System.out.println("Inválido: " + texto);
} finally {
System.out.println("finally rodou");
}
}
public static void main(String[] args) {
converter("42");
System.out.println("---");
converter("abc");
}
}
- try { … }
- O código que pode dar problema, como na aula anterior.
- catch (NumberFormatException e) { … }
- O tratamento, se o problema acontecer.
- finally { … }
- O que roda no fim, em qualquer caso. Vem sempre por último, depois de todos os
catch.
$ java Main.java Convertido: 42 finally rodou --- Inválido: abc finally rodou
Com "42", o caminho foi try → finally. Com "abc", foi try (interrompido) → catch → finally. Nos dois, o finally rodou.
Mesmo sem catch, e mesmo se o programa quebrar
O catch é opcional quando há finally: try { … } finally { … } é válido. E se a exceção não for tratada, o finally roda antes de o programa encerrar:
public class Main {
public static void main(String[] args) {
int provas = 0;
try {
System.out.println("Calculando...");
System.out.println(30 / provas);
} finally {
System.out.println("finally rodou");
}
System.out.println("Fim");
}
}
$ java Main.java Calculando... finally rodou Exception in thread "main" java.lang.ArithmeticException: / by zero at Main.main(Main.java:6) $ echo $? 1
Repare na ordem: o finally rodou aparece antes do stack trace. A exceção foi lançada, o Java executou o finally no caminho e só então a exceção continuou "caindo" até encerrar o programa. O Fim, que está fora do bloco, não rodou.
Até com return
Este é o teste mais impressionante. O return encerra o método na hora… mas, se ele estiver dentro de um try, o finally roda antes de o método realmente terminar:
public class Main {
static String buscar(String[] frutas, String procurada) {
try {
for (String f : frutas) {
if (f.equals(procurada)) {
return "achei " + f;
}
}
return "não achei " + procurada;
} finally {
System.out.println("(busca encerrada)");
}
}
public static void main(String[] args) {
String[] frutas = {"maçã", "uva", "pera"};
System.out.println(buscar(frutas, "uva"));
System.out.println(buscar(frutas, "kiwi"));
}
}
$ java Main.java (busca encerrada) achei uva (busca encerrada) não achei kiwi
Acompanhe a primeira chamada: o laço acha "uva" e executa return "achei uva". O valor de retorno fica "guardado", o finally roda (mostrando (busca encerrada)) e só então o método devolve "achei uva" para o main, que o mostra. Por isso a linha do finally aparece antes do resultado.
System.exit
System.exit(0) não "sai do try": ele desliga a JVM inteira na hora. Aí o finally não roda:
public class Main {
public static void main(String[] args) {
try {
System.out.println("Saindo com System.exit");
System.exit(0);
} finally {
System.out.println("finally rodou");
}
}
}
$ java Main.java Saindo com System.exit
O mesmo vale se o computador desligar ou o processo for morto. "Sempre" quer dizer: sempre que o programa sair do try por um caminho normal do Java.
Missão: a padaria deve dizer Próximo cliente! depois de todo pedido, válido ou não. Hoje, com o pedido "x", a frase não aparece. Mova o println("Próximo cliente!") para um bloco finally.
public class Main {
public static void main(String[] args) {
String[] pedidos = {"3", "x"};
for (String p : pedidos) {
try {
int qtd = Integer.parseInt(p);
System.out.println("Pedido de " + qtd + " pães");
System.out.println("Próximo cliente!");
} catch (NumberFormatException e) {
System.out.println("Pedido inválido: " + p);
}
}
}
}
public class Main {
public static void main(String[] args) {
String[] pedidos = {"3", "x"};
for (String p : pedidos) {
try {
int qtd = Integer.parseInt(p);
System.out.println("Pedido de " + qtd + " pães");
} catch (NumberFormatException e) {
System.out.println("Pedido inválido: " + p);
} finally {
System.out.println("Próximo cliente!");
}
}
}
}
Recursos: coisas que precisam ser fechadas
Um recurso é algo que o programa pega emprestado do sistema e precisa devolver: um arquivo aberto, uma conexão com um banco de dados, uma conexão de rede. O sistema operacional tem um limite de arquivos abertos ao mesmo tempo; um programa que abre e nunca fecha acaba esgotando esse limite, e dados que estavam "a caminho" de um arquivo podem não ser gravados. Por isso, todo recurso aberto deve ser fechado com o método close() ("fechar").
Um exemplo de recurso é o Scanner lendo um arquivo em vez do teclado. A Ana quer ler a planilha Documentos/alunos.csv. Antes do Java 7, o jeito certo de garantir o close() era este:
import java.io.IOException;
import java.nio.file.Path;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner leitor = null;
try {
leitor = new Scanner(Path.of("Documentos/alunos.csv"));
System.out.println("Primeira linha: " + leitor.nextLine());
} catch (IOException e) {
System.out.println("Não consegui ler o arquivo: " + e);
} finally {
if (leitor != null) {
leitor.close();
}
}
}
}
$ java Main.java Primeira linha: nome,idade,cidade,nota
Funciona, mas repare quanta cerimônia: declarar a variável fora com null, abrir dentro do try, e no finally conferir se abriu mesmo (se o arquivo não existisse, leitor continuaria null, e leitor.close() daria NullPointerException). É fácil esquecer um pedaço. Sobre o IOException e o Path.of, não se preocupe agora: eles têm aulas próprias, Checked × Unchecked e Ler e Escrever Arquivos.
try-with-resources Java 7+
O try-with-resources ("try com recursos") faz tudo isso sozinho. Você abre o recurso entre parênteses, logo depois do try, e o Java garante o close() no fim:
import java.io.IOException;
import java.nio.file.Path;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
try (Scanner leitor = new Scanner(Path.of("Documentos/alunos.csv"))) {
while (leitor.hasNextLine()) {
String linha = leitor.nextLine();
System.out.println("Linha: " + linha);
}
} catch (IOException e) {
System.out.println("Não consegui ler o arquivo: " + e);
}
}
}
- try (
- Os parênteses logo depois do
trysão a marca do try-with-resources. - Scanner leitor = new Scanner(Path.of("Documentos/alunos.csv"))
- A declaração do recurso: uma variável, criada aqui mesmo. Ela só existe dentro do bloco.
- ) {
- Fecha os parênteses e abre o bloco, como num
trycomum. - while (leitor.hasNextLine())
- "Enquanto houver uma próxima linha", lê e mostra.
- }
- Ao sair do bloco, por qualquer caminho, o Java chama
leitor.close()sozinho. Não háfinallynemnull. - catch (IOException e)
- Opcional: trata o problema de abrir ou ler o arquivo.
$ java Main.java Linha: nome,idade,cidade,nota Linha: Ana,29,Recife,9.5 Linha: Bruno,34,Curitiba,7.0 Linha: Carla,22,Salvador,8.5 Linha: Diego,41,Manaus,6.0 Linha: Elisa,27,Porto Alegre,10.0
O programa roda na pasta pessoal da Ana (/home/ana), por isso o caminho Documentos/alunos.csv funciona. Trocando o nome do arquivo por um que não existe (alunos2.csv), o construtor do Scanner lança a exceção, o catch a trata e não há nada para fechar:
$ java Main.java Não consegui ler o arquivo: java.nio.file.NoSuchFileException: Documentos/alunos2.csv
Scanner do teclado?
O new Scanner(System.in) também tem close(), mas fechá-lo fecha o próprio teclado (System.in) para o resto do programa. Em programas pequenos, que leem o teclado até o fim, o costume é não fechar. Já um Scanner de arquivo deve sempre ser fechado, e o try-with-resources é a forma certa.
Vários recursos e a ordem de fechamento
Dá para abrir vários recursos no mesmo try, separados por ;. Eles são fechados na ordem inversa da abertura (o último aberto é o primeiro fechado), como empilhar e desempilhar caixas. Para ver isso acontecendo, precisamos de recursos que avisem quando abrem e fecham: vamos criar o nosso.
Seu próprio recurso: AutoCloseable
O try-with-resources aceita qualquer objeto cuja classe implemente a interface AutoCloseable ("que pode ser fechado automaticamente"). Uma interface é um contrato; o contrato do AutoCloseable tem uma única cláusula: ter um método public void close(). O Scanner, os leitores e escritores de arquivo e as conexões de banco de dados cumprem esse contrato.
A Ana vai representar o caixa da loja como um recurso: ele é aberto no começo do dia e precisa ser fechado no fim, aconteça o que acontecer.
class Caixa implements AutoCloseable {
private String nome;
Caixa(String nome) {
this.nome = nome;
System.out.println("Abrindo o " + nome);
}
void registrar(String valor) {
int v = Integer.parseInt(valor);
System.out.println(nome + ": venda de R$ " + v);
}
@Override
public void close() {
System.out.println("Fechando o " + nome);
}
}
public class Main {
public static void main(String[] args) {
try (Caixa c = new Caixa("caixa 1")) {
c.registrar("10");
c.registrar("dez");
c.registrar("5");
} catch (NumberFormatException e) {
System.out.println("Erro: " + e.getMessage());
} finally {
System.out.println("Fim do expediente");
}
}
}
- class Caixa implements AutoCloseable
- "A classe
Caixacumpre o contratoAutoCloseable". Sem isso, ojavacnão deixa usá-la notry ( ). - @Override public void close()
- O método do contrato.
@Overrideavisa que ele vem de fora (veja Sobrescrita);publicé obrigatório. - try (Caixa c = new Caixa("caixa 1"))
- Abre o caixa como recurso: o
close()será chamado no fim do bloco.
$ java Main.java Abrindo o caixa 1 caixa 1: venda de R$ 10 Fechando o caixa 1 Erro: For input string: "dez" Fim do expediente
Leia a ordem com atenção, porque ela é a regra do try-with-resources:
- O recurso é criado (
Abrindo o caixa 1). - O bloco roda até a exceção: a venda de 10 dá certo, a de
"dez"lançaNumberFormatException. A venda de 5 nunca acontece. - O recurso é fechado (
Fechando o caixa 1), antes docatch. - O
catchroda (Erro: …). - O
finallyroda por último (Fim do expediente).
Agora, dois recursos no mesmo try:
try (Caixa a = new Caixa("caixa 1");
Caixa b = new Caixa("caixa 2")) {
System.out.println("Atendendo clientes");
}
$ java Main.java Abrindo o caixa 1 Abrindo o caixa 2 Atendendo clientes Fechando o caixa 2 Fechando o caixa 1
Abriu 1, 2; fechou 2, 1. Isso importa quando um recurso depende do outro (um leitor construído em cima de um arquivo, por exemplo): o que depende é fechado primeiro.
try Java 9+
Se a variável já existe e não muda de valor depois de criada (é final ou "efetivamente final"), você pode só citá-la: Caixa c = new Caixa("caixa 1"); try (c) { … }. No Java 7 e 8, a declaração precisava ficar dentro dos parênteses.
Exceções suprimidas
E se o close() também lançar uma exceção, quando o bloco já tinha lançado outra? Duas exceções ao mesmo tempo, e o Java só pode lançar uma. A regra do try-with-resources: a exceção do bloco é a principal (foi o problema original); a do close() é anexada a ela como suprimida (suppressed), para não se perder. Aqui, o close() usa throw para lançar uma exceção de propósito (você vai aprender o throw em throw e throws):
class Conexao implements AutoCloseable {
@Override
public void close() {
System.out.println("Fechando a conexão...");
throw new IllegalStateException("falha ao fechar a conexão");
}
}
public class Main {
public static void main(String[] args) {
int provas = 0;
try (Conexao c = new Conexao()) {
System.out.println("Média: " + 30 / provas);
}
}
}
$ javac Main.java $ java Main Fechando a conexão... Exception in thread "main" java.lang.ArithmeticException: / by zero at Main.main(Main.java:13) Suppressed: java.lang.IllegalStateException: falha ao fechar a conexão at Conexao.close(Main.java:5) at Main.main(Main.java:12)
O stack trace mostra a principal (ArithmeticException, na linha 13) e, recuada, uma seção Suppressed: com a do close() e o rastro dela. Dentro de um catch, as suprimidas ficam disponíveis no método getSuppressed(), que devolve um array:
} catch (ArithmeticException e) {
System.out.println("Principal: " + e.getMessage());
for (Throwable s : e.getSuppressed()) {
System.out.println("Suprimida: " + s.getMessage());
}
}
$ java Main.java Fechando a conexão... Principal: / by zero Suprimida: falha ao fechar a conexão
No dia a dia você raramente vai mexer nisso; o importante é saber ler a linha Suppressed: quando ela aparecer num stack trace. Com o finally manual do jeito antigo, a exceção do close() substituiria a original, e você perderia a pista do problema real. É mais um motivo para preferir o try-with-resources.
Formas diferentes, qual usar
| Você quer… | Use |
|---|---|
Fechar um arquivo, Scanner de arquivo, conexão | try-with-resources (sempre que o objeto for AutoCloseable) |
| Fazer uma limpeza que não é "fechar um recurso" (mostrar uma mensagem, desligar um aviso, registrar o fim) | finally |
| Tratar a exceção e também fechar | try ( … ) { … } catch ( … ) { … } |
| Fechar, mas deixar a exceção subir | try ( … ) { … } sem catch |
| Manter código antigo (Java 6) | try/finally com if (x != null) x.close() |
Quando dá errado
public class Main {
public static void main(String[] args) {
try (String nome = "Ana") {
System.out.println(nome);
}
}
}
$ java Main.java
Main.java:3: error: incompatible types: try-with-resources not applicable to variable type
try (String nome = "Ana") {
^
(String cannot be converted to AutoCloseable)
1 error
error: compilation failed
O que aconteceu: "try-with-resources não se aplica a esse tipo de variável": String não é um recurso, não tem close(). A última linha explica: String não pode ser convertida em AutoCloseable.
Como resolver: só coloque nos parênteses objetos que precisam ser fechados. Se a classe for sua, acrescente implements AutoCloseable e o método public void close().
return dentro do finally "some" com o resultado
public class Main {
static int numero() {
try {
return 1;
} finally {
return 2;
}
}
public static void main(String[] args) {
System.out.println(numero());
}
}
$ java Main.java
2
$ javac -Xlint Main.java
Main.java:7: warning: [finally] finally clause cannot complete normally
}
^
1 warning
O que aconteceu: o return 1 guardou o valor, o finally rodou e o return 2 dele substituiu o primeiro. Um return (ou throw) no finally também faz uma exceção do try desaparecer sem deixar rastro. O javac só avisa se você pedir os avisos extras com -Xlint: "a cláusula finally não pode terminar normalmente".
Como resolver: nunca use return dentro de finally. O finally serve para limpar, não para decidir o resultado.
finally não rodou
O que aconteceu: provavelmente há um System.exit(…) no try ou no catch, que encerra a JVM na hora. Ou o processo foi interrompido de fora (Ctrl+C).
Como resolver: evite System.exit no meio do programa; prefira deixar o main terminar normalmente.
Referência
| Sintaxe | O que faz |
|---|---|
try { … } finally { … } | O finally roda sempre que o try termina (normal, exceção ou return) |
try { … } catch (X e) { … } finally { … } | Ordem: try → catch (se houve X) → finally |
try (Tipo r = new Tipo()) { … } | Try-with-resources: chama r.close() ao sair do bloco |
try (A a = …; B b = …) { … } | Vários recursos; fechados na ordem inversa (b, depois a) |
try (r) { … } | Recurso já declarado, efetivamente final (Java 9+) |
class X implements AutoCloseable | Faz a classe X funcionar no try-with-resources; exige public void close() |
e.getSuppressed() | Array com as exceções suprimidas (as do close()) |
Suppressed: no stack trace | Exceção do close() anexada à principal |
Exercícios
1. A palavra do bilhete
Complete o bloco que roda sempre:
2. O contrato
Complete para a classe Arquivo poder ser usada no try-with-resources:
3. O método do contrato
Qual método o try-with-resources chama no fim do bloco? (só o nome, com parênteses)
4. Num try-with-resources com catch e finally, o bloco lança uma exceção. Qual a ordem?
5. O que este método devolve, e o que aparece na tela?
static int dobro(int x) {
try {
return x * 2;
} finally {
System.out.println("calculado");
}
}
// no main: System.out.println(dobro(4));
6. Recursos a, b e c foram abertos nessa ordem no mesmo try ( ). Em que ordem são fechados?
Missão final: o programa abre a torneira, quebra no meio e a torneira fica aberta (o fechar() nunca roda). Transforme a Torneira num recurso: faça-a implementar AutoCloseable, troque fechar() por public void close() e use try-with-resources no main. A exceção vai continuar acontecendo (não precisa de catch), mas Fechando a torneira deve aparecer antes dela.
class Torneira {
void abrir() {
System.out.println("Abrindo a torneira");
}
void encher(int litros) {
System.out.println("Enchendo " + (10 / litros) + " baldes");
}
void fechar() {
System.out.println("Fechando a torneira");
}
}
public class Main {
public static void main(String[] args) {
Torneira t = new Torneira();
t.abrir();
t.encher(0);
t.fechar();
}
}
class Torneira implements AutoCloseable {
void abrir() {
System.out.println("Abrindo a torneira");
}
void encher(int litros) {
System.out.println("Enchendo " + (10 / litros) + " baldes");
}
@Override
public void close() {
System.out.println("Fechando a torneira");
}
}
public class Main {
public static void main(String[] args) {
try (Torneira t = new Torneira()) {
t.abrir();
t.encher(0);
}
}
}
Resumo
finallyroda sempre que o programa sai dotry: sem problema, com exceção (tratada ou não) e até depois dereturn. SóSystem.exit(ou matar o processo) o impede.- Nunca coloque
returnnofinally: ele substitui o resultado e esconde exceções. - Recursos (arquivos, conexões,
Scannerde arquivo) precisam declose(). - Try-with-resources:
try (Tipo r = …) { … }chamaclose()sozinho, antes docatche dofinally. Vários recursos fecham na ordem inversa. - Qualquer classe que implemente
AutoCloseable(compublic void close()) funciona no try-with-resources. - Se o
close()também falhar, essa exceção vira suprimida (Suppressed:,getSuppressed()) e a original é preservada.