1. Introdução ao bloco finally
Quando você trabalha com recursos — arquivos, conexões de rede, bancos de dados — é importante garantir que eles serão fechados ou liberados sempre, mesmo que ocorra um erro durante a execução. Em Java há um bloco específico para isso — finally.
Como o finally funciona?
O bloco finally é parte da construção try-catch-finally. O código dentro de finally é sempre executado (se ele está presente) — independentemente de ter havido exceção ou não. Mesmo que em try haja um return ou uma exceção seja lançada — finally ainda assim será executado (a menos que o computador seja desligado ou que o programa seja encerrado à força via System.exit(0)).
Sintaxe:
try {
// Código onde uma exceção pode ocorrer
} catch (ExceptionType e) {
// Tratamento do erro
} finally {
// Este código sempre será executado!
}
Exemplo
try {
System.out.println("Início da execução");
int result = 10 / 0; // aqui ocorrerá um erro
System.out.println("Resultado: " + result);
} catch (ArithmeticException e) {
System.out.println("Erro: divisão por zero");
} finally {
System.out.println("Este código será executado de qualquer forma");
}
Resultado da execução:
Início da execução
Erro: divisão por zero
Este código será executado de qualquer forma
O que está acontecendo?
- No try tentamos dividir dois números e obtemos um erro.
- Se ocorrer um erro na divisão — o catch irá tratá-lo.
- Mas! Em qualquer caso, finally será executado e escreverá uma mensagem no console.
2. finally sem catch
Podem existir 3 variantes da construção:
- Completa: try-catch-finally
- Sem finally: try-catch
- Sem catch: try-finally
A terceira variante é usada quando quem irá capturar e tratar o erro é um método em um nível acima. Mas o bloco finally é necessário para garantir a execução de determinado código:
- Fechamento de arquivos, conexões de rede, bancos de dados.
- Liberação de quaisquer recursos (por exemplo, locks).
- Registro em log: gravação de informações sobre a conclusão da operação.
Exemplo:
try {
System.out.println("Dividindo números");
int result = 10 / 0; // erro!
System.out.println("Resultado: " + result);
} finally {
System.out.println("Bloco finally executado");
}
Resultado:
Dividindo números
Bloco finally executado
Exception in thread "main" java.lang.ArithmeticException: / by zero
Quando o finally NÃO é executado?
Quase sempre é executado. As exceções são quando:
- O programa é encerrado à força por meio de: System.exit(0).
- A thread onde o finally está sendo executado é “morta” à força.
- O computador é desligado.
3. Operador throw: como gerar uma exceção manualmente
Às vezes o próprio Java “lança” exceções (por exemplo, divisão por zero, acesso fora dos limites de um array). Mas há situações em que você quer declarar explicitamente: “Isso é um erro! Não posso continuar a execução!”. Para isso, o Java possui o operador throw.
Analogia: Se você está em uma loja e vê um produto vencido — você “lança” uma reclamação. Da mesma forma no código: se algo está errado — você lança uma exceção.
Sintaxe de throw
throw new ExceptionType("Mensagem de erro");
ExceptionType é qualquer classe que herde de Throwable (geralmente Exception ou RuntimeException). Entre parênteses vai a mensagem que ajuda a entender o que deu errado.
Exemplo: validação de argumentos do método
public static int safeDivide(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("O divisor não pode ser igual a zero");
}
return a / b;
}
Uso:
public static void main(String[] args) {
try {
int result = safeDivide(10, 0);
System.out.println("Resultado: " + result);
} catch (IllegalArgumentException e) {
System.out.println("Erro: " + e.getMessage());
}
}
Resultado:
Erro: O divisor não pode ser igual a zero
Quando usar throw?
- Validação de argumentos do método (por exemplo, se veio null ou dados inválidos).
- Verificação do estado do objeto (por exemplo, tentar sacar dinheiro de uma conta com 0 euros).
- Dentro de catch — quando você quer “relançar” a exceção adiante (por exemplo, adicionando informação adicional).
4. Combinando try-catch-finally e throw
Às vezes essas construções trabalham juntas. Por exemplo, você captura um erro, mas depois decide lançar outro, mais informativo.
public static int parseAndDivide(String text, int divisor) {
try {
int number = Integer.parseInt(text);
if (divisor == 0) {
throw new IllegalArgumentException("O divisor não pode ser igual a zero");
}
return number / divisor;
} catch (NumberFormatException e) {
throw new IllegalArgumentException("A string '" + text + "' não é um número");
} finally {
System.out.println("Tentativa de processar a string: " + text);
}
}
Uso:
try {
int result = parseAndDivide("42a", 2);
System.out.println("Resultado: " + result);
} catch (IllegalArgumentException e) {
System.out.println("Erro: " + e.getMessage());
}
Resultado:
Tentativa de processar a string: 42a
Erro: A string '42a' não é um número
Um detalhe importante: return e finally
Mesmo que haja um return no bloco try, o finally ainda será executado!
public static int getValue() {
try {
return 10;
} finally {
System.out.println("o finally ainda será executado!");
}
}
A chamada getValue() exibirá:
o finally ainda será executado!
5. Erros comuns ao usar finally e throw
Erro nº 1: esqueceu de fechar o recurso sem finally.
Problema muito comum: abriu um arquivo, não o fechou — gerou vazamento de recursos. Sempre use finally (ou try-with-resources, sobre o qual falaremos depois).
Erro nº 2: lançou uma exceção, mas não a tratou.
Se você lança uma exceção com throw, mas não a captura em lugar nenhum (não há try-catch), o programa terminará de forma abrupta. Sempre pense em quem irá capturar sua exceção.
Erro nº 3: return dentro de finally.
Se por engano você escrever um return dentro de finally, ele irá “sobrepor” todos os return ou throw anteriores. Isso pode levar a bugs muito difíceis de detectar. Não é recomendado de forma alguma!
public int tricky() {
try {
return 1;
} finally {
return 2; // PERIGOSO: retornará 2, e não 1!
}
}
Resultado: será retornado 2, embora no try fosse 1.
Erro nº 4: perda de informação sobre a exceção.
Se você captura uma exceção e, em seguida, lança outra sem preservar a informação da anterior (e), você perde a stack trace, o que dificulta a depuração. É melhor escrever assim:
catch (NumberFormatException e) {
throw new IllegalArgumentException("Erro de conversão", e);
}
GO TO FULL VERSION