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.

Você vai aprender
  • O que é o bloco finally e em que situações ele roda (spoiler: quase todas, até depois de um return).
  • O que é um recurso e por que ele precisa ser fechado.
  • Usar o try-with-resources com um Scanner lendo 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.

A exceção: 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 try sã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 try comum.
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á finally nem null.
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
E o 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 Caixa cumpre o contrato AutoCloseable". Sem isso, o javac não deixa usá-la no try ( ).
@Override public void close()
O método do contrato. @Override avisa 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:

  1. O recurso é criado (Abrindo o caixa 1).
  2. O bloco roda até a exceção: a venda de 10 dá certo, a de "dez" lança NumberFormatException. A venda de 5 nunca acontece.
  3. O recurso é fechado (Fechando o caixa 1), antes do catch.
  4. O catch roda (Erro: …).
  5. O finally roda 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.

Recurso criado antes do 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ãotry-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 fechartry ( … ) { … } catch ( … ) { … }
Fechar, mas deixar a exceção subirtry ( … ) { … } sem catch
Manter código antigo (Java 6)try/finally com if (x != null) x.close()

Quando dá errado

"try-with-resources not applicable to variable type"
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().

Um 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.

O 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

SintaxeO 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 AutoCloseableFaz 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 traceExceção do close() anexada à principal

Exercícios

1. A palavra do bilhete

Complete o bloco que roda sempre:

try { … } catch (Exception e) { … } { System.out.println("Tchau"); }

2. O contrato

Complete para a classe Arquivo poder ser usada no try-with-resources:

class Arquivo implements { … }

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

  • finally roda sempre que o programa sai do try: sem problema, com exceção (tratada ou não) e até depois de return. Só System.exit (ou matar o processo) o impede.
  • Nunca coloque return no finally: ele substitui o resultado e esconde exceções.
  • Recursos (arquivos, conexões, Scanner de arquivo) precisam de close().
  • Try-with-resources: try (Tipo r = …) { … } chama close() sozinho, antes do catch e do finally. Vários recursos fecham na ordem inversa.
  • Qualquer classe que implemente AutoCloseable (com public void close()) funciona no try-with-resources.
  • Se o close() também falhar, essa exceção vira suprimida (Suppressed:, getSuppressed()) e a original é preservada.