Java try e catch
Na aula O que são Exceções, você viu o programa "quebrar": uma exceção não tratada encerra tudo. Agora você vai aprender a tratar a exceção: dizer ao Java "tente fazer isto; se der este problema, faça aquilo" e deixar o programa seguir em frente. As palavras para isso são try e catch.
- Escrever um bloco
try/catche entender o caminho que o programa percorre. - Capturar uma exceção específica e o que acontece com as outras.
- Usar vários
catch, e por que a ordem entre eles importa. - Juntar exceções num
catchsó com o multi-catch (|). - Tirar informação da exceção:
getMessage(),toString()eprintStackTrace(). - Por que um
catchvazio ("engolir a exceção") é um dos piores hábitos em Java.
A ideia antes do código
Pense num trapezista de circo. Ele tenta o salto. Quase sempre dá certo. Mas, se ele cair, há uma rede de proteção embaixo que o pega, e o espetáculo continua. Sem a rede, uma queda encerra o show.
No Java:
try("tente") é o salto: o código que pode dar problema.catch("pegue", "capture") é a rede: o que fazer se uma exceção de certo tipo for lançada dentro dotry.
O primeiro try/catch
A Ana recebe idades como texto (de um formulário, de um arquivo) e precisa transformá-las em número com Integer.parseInt. Se o texto não for um número, o parseInt lança NumberFormatException. Desta vez, o programa vai estar preparado:
public class Main {
public static void main(String[] args) {
String texto = "29 anos";
try {
int idade = Integer.parseInt(texto);
System.out.println("Idade: " + idade);
} catch (NumberFormatException e) {
System.out.println("Isso não é um número: " + texto);
}
System.out.println("Fim do programa");
}
}
- try {
- Abre o bloco protegido. Tudo entre estas chaves roda normalmente, mas "vigiado".
- int idade = Integer.parseInt(texto);
- A linha que pode lançar a exceção.
- System.out.println("Idade: " + idade);
- Só roda se a linha de cima deu certo.
- } catch (
- Fecha o
trye abre a "rede". Os parênteses dizem que tipo de exceção esta rede pega. - NumberFormatException e
- O tipo da exceção e um nome de variável para ela. Por costume, o nome é
e(de exception), mas pode ser qualquer um. Dentro docatch,eé o objeto da exceção, com a mensagem e o stack trace. - { … }
- O que fazer quando a exceção acontece: aqui, avisar com uma mensagem amigável.
$ java Main.java Isso não é um número: 29 anos Fim do programa $ echo $? 0
Compare com a aula anterior: nada de stack trace, o Fim do programa apareceu e o código de saída é 0 ("terminou bem"). A exceção aconteceu do mesmo jeito, mas foi tratada.
O caminho do programa
Repare que Idade: … não apareceu. Quando uma linha dentro do try lança uma exceção, o Java abandona o resto do try e pula direto para o catch. Depois do catch, segue com o que vem depois do bloco todo. Os dois caminhos possíveis:
| Linha | Com "29 anos" (exceção) | Com "29" (tudo certo) |
|---|---|---|
5 parseInt(texto) | lança NumberFormatException | roda: idade = 29 |
6 println("Idade: …") | pulada | roda |
8 println("Isso não é…") | roda | pulada (sem exceção, o catch não roda) |
10 println("Fim…") | roda | roda |
Trocando "29 anos" por "29", a saída real é:
$ java Main.java Idade: 29 Fim do programa
Missão: rode o programa e veja-o quebrar. Depois proteja as duas linhas de dentro com try e um catch (NumberFormatException e) que mostre Isso não é um número. O Fim do programa deve aparecer.
public class Main {
public static void main(String[] args) {
String texto = "29 anos";
int idade = Integer.parseInt(texto);
System.out.println("Idade: " + idade);
System.out.println("Fim do programa");
}
}
public class Main {
public static void main(String[] args) {
String texto = "29 anos";
try {
int idade = Integer.parseInt(texto);
System.out.println("Idade: " + idade);
} catch (NumberFormatException e) {
System.out.println("Isso não é um número: " + texto);
}
System.out.println("Fim do programa");
}
}
O catch só pega o tipo que você pediu
A rede do catch tem formato: catch (NumberFormatException e) pega só NumberFormatException (e as classes filhas dela). Se outra exceção acontecer no try, ela passa direto pela rede:
public class Main {
public static void main(String[] args) {
int soma = 22;
int provas = 0;
try {
System.out.println("Média: " + soma / provas);
} catch (NumberFormatException e) {
System.out.println("Número inválido");
}
System.out.println("Fim do programa");
}
}
$ java Main.java Exception in thread "main" java.lang.ArithmeticException: / by zero at Main.main(Main.java:6)
A divisão lançou ArithmeticException, que não é uma NumberFormatException. O catch foi ignorado, a exceção continuou "caindo" e encerrou o programa, como se o try nem existisse.
Pegar a classe mãe pega as filhas
Lembra da hierarquia? ArithmeticException e NumberFormatException são filhas de RuntimeException. Um catch da classe mãe pega qualquer filha:
} catch (RuntimeException e) {
System.out.println("Problema: " + e);
}
$ java Main.java Problema: java.lang.ArithmeticException: / by zero Fim do programa
Subindo mais, catch (Exception e) pega quase tudo. Parece prático, mas é perigoso: você passa a tratar do mesmo jeito problemas muito diferentes, inclusive os que você nem imaginava (um NullPointerException causado por um defeito no seu código, por exemplo). A regra dos programadores experientes: capture a exceção mais específica que você sabe tratar.
Vários catch
Um try pode ter vários catch, um para cada tipo de problema, cada um com sua resposta. O Java testa os catch de cima para baixo e usa o primeiro que combinar:
public class Main {
public static void main(String[] args) {
String[] textos = {"10", "abc", "0"};
for (String t : textos) {
try {
int n = Integer.parseInt(t);
System.out.println("100 / " + n + " = " + (100 / n));
} catch (NumberFormatException e) {
System.out.println("\"" + t + "\" não é número");
} catch (ArithmeticException e) {
System.out.println("Não dá para dividir por zero");
}
}
}
}
$ java Main.java 100 / 10 = 10 "abc" não é número Não dá para dividir por zero
O for-each passa pelos três textos. Cada volta tem o seu próprio try: "10" dá certo; "abc" cai no primeiro catch; "0" passa pelo parseInt, mas a divisão cai no segundo. E, como o try está dentro do laço, um problema numa volta não impede as outras.
A ordem importa
Como o Java usa o primeiro catch que combina, um catch da classe mãe antes de uma filha deixaria a filha inalcançável. O javac percebe isso e recusa o programa:
public class Main {
public static void main(String[] args) {
String texto = "abc";
try {
int n = Integer.parseInt(texto);
System.out.println(100 / n);
} catch (Exception e) {
System.out.println("Algo deu errado");
} catch (NumberFormatException e) {
System.out.println("Não é número");
}
}
}
$ java Main.java
Main.java:9: error: exception NumberFormatException has already been caught
} catch (NumberFormatException e) {
^
1 error
error: compilation failed
"A exceção NumberFormatException já foi capturada": o catch (Exception e) da linha 7 já pega qualquer NumberFormatException, então o da linha 9 nunca seria usado. A última linha, error: compilation failed ("a compilação falhou"), é do java Main.java avisando que nem chegou a rodar. Regra: do mais específico (filha) para o mais geral (mãe). Inverter os dois catch resolve.
Missão: o programa trata texto inválido, mas quebra no "0". Acrescente um segundo catch, para ArithmeticException, que mostre Não dá para dividir por zero, e faça o Fim aparecer.
public class Main {
public static void main(String[] args) {
String[] textos = {"10", "abc", "0"};
for (String t : textos) {
try {
int n = Integer.parseInt(t);
System.out.println("100 / " + n + " = " + (100 / n));
} catch (NumberFormatException e) {
System.out.println("\"" + t + "\" não é número");
}
}
System.out.println("Fim");
}
}
public class Main {
public static void main(String[] args) {
String[] textos = {"10", "abc", "0"};
for (String t : textos) {
try {
int n = Integer.parseInt(t);
System.out.println("100 / " + n + " = " + (100 / n));
} catch (NumberFormatException e) {
System.out.println("\"" + t + "\" não é número");
} catch (ArithmeticException e) {
System.out.println("Não dá para dividir por zero");
}
}
System.out.println("Fim");
}
}
Multi-catch: vários tipos num catch só Java 7+
Quando a resposta é a mesma para dois ou mais tipos, você pode juntá-los com uma barra vertical |, que aqui se lê "ou":
} catch (NumberFormatException | ArithmeticException e) {
System.out.println("Entrada inválida: " + e.getMessage());
}
- NumberFormatException | ArithmeticException
- "Se for uma
NumberFormatExceptionou umaArithmeticException…". - e
- Uma variável só, que pode guardar qualquer um dos tipos. Você pode chamar nela os métodos comuns a todas as exceções, como
getMessage().
Um programa completo, que divide a conta do restaurante entre os amigos da Ana (ele lê do teclado com o Scanner):
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner teclado = new Scanner(System.in);
System.out.print("Total da conta: ");
String total = teclado.nextLine();
System.out.print("Pessoas: ");
String pessoas = teclado.nextLine();
try {
int cada = Integer.parseInt(total) / Integer.parseInt(pessoas);
System.out.println("Cada um paga " + cada);
} catch (NumberFormatException | ArithmeticException e) {
System.out.println("Entrada inválida: " + e.getMessage());
}
}
}
$ java Main.java Total da conta: 90 Pessoas: 0 Entrada inválida: / by zero $ java Main.java Total da conta: noventa Pessoas: 3 Entrada inválida: For input string: "noventa"
Não dá para juntar no multi-catch dois tipos em que um é filho do outro: seria redundante, e o javac recusa.
} catch (NumberFormatException | RuntimeException e) {
$ java Main.java
Main.java:7: error: Alternatives in a multi-catch statement cannot be related by subclassing
} catch (NumberFormatException | RuntimeException e) {
^
Alternative NumberFormatException is a subclass of alternative RuntimeException
1 error
error: compilation failed
"As alternativas de um multi-catch não podem ter relação de herança. A alternativa NumberFormatException é subclasse da alternativa RuntimeException." Deixe só a mais geral (RuntimeException) ou troque por tipos sem parentesco.
O que fazer com a exceção capturada
A variável e é um objeto, e tem métodos úteis. Veja todos lado a lado:
public class Main {
public static void main(String[] args) {
try {
int n = Integer.parseInt("12x");
} catch (NumberFormatException e) {
System.out.println("getMessage(): " + e.getMessage());
System.out.println("toString(): " + e);
System.out.println("Só o nome: " + e.getClass().getSimpleName());
System.out.println("--- printStackTrace() ---");
e.printStackTrace();
System.out.println("--- o programa continua ---");
}
}
}
$ javac Main.java $ java Main getMessage(): For input string: "12x" toString(): java.lang.NumberFormatException: For input string: "12x" Só o nome: NumberFormatException --- printStackTrace() --- java.lang.NumberFormatException: For input string: "12x" at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67) at java.base/java.lang.Integer.parseInt(Integer.java:565) at java.base/java.lang.Integer.parseInt(Integer.java:662) at Main.main(Main.java:4) --- o programa continua ---
| Método | Devolve / faz | Quando usar |
|---|---|---|
e.getMessage() | Só a mensagem: For input string: "12x". Pode ser null (exceções sem mensagem, como InputMismatchException). | Para mostrar ao usuário um detalhe do problema. |
e.toString() ou "" + e | Nome completo da classe + mensagem. | Para registrar o problema numa linha. |
e.getClass().getSimpleName() | Só o nome da classe, sem o pacote. | Mensagens curtas. |
e.printStackTrace() | Escreve o stack trace completo na saída de erro (System.err), sem encerrar o programa. | Enquanto você está desenvolvendo, para saber exatamente onde foi. |
Repare: o printStackTrace() mostra o mesmo relatório de uma exceção não tratada, só que sem o Exception in thread "main", e o programa continua.
javac + java aqui?
Rodando com java Main.java, o printStackTrace() mostra, no fim, mais cinco linhas at do próprio lançador (jdk.internal.reflect…, SourceLauncher…), que é quem compila e chama o seu main nesse modo. Elas não fazem parte do seu programa. Com javac e depois java Main, o rastro fica limpo.
Um uso clássico: pedir de novo até acertar
Juntando try/catch com um while, o programa insiste até a pessoa digitar um número válido:
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner teclado = new Scanner(System.in);
int idade = -1;
while (idade < 0) {
System.out.print("Sua idade: ");
String linha = teclado.nextLine();
try {
idade = Integer.parseInt(linha);
} catch (NumberFormatException e) {
System.out.println("\"" + linha + "\" não é um número. Tente de novo.");
}
}
System.out.println("Ano que vem você terá " + (idade + 1) + " anos.");
}
}
$ java Main.java Sua idade: vinte "vinte" não é um número. Tente de novo. Sua idade: 29 Ano que vem você terá 30 anos.
A idade começa em -1 (um valor impossível) para o laço rodar pelo menos uma vez. Se o parseInt falhar, a atribuição idade = … não acontece, a idade continua -1 e o laço repete. Repare também que o programa lê a linha inteira com nextLine() e converte com parseInt, em vez de usar nextInt(): assim o texto inválido não fica "preso" no Scanner.
Não engula exceções
"Engolir" uma exceção é capturá-la e não fazer nada. O javac aceita, o programa não quebra… e é exatamente esse o problema. A Ana somou os preços de uma lista:
public class Main {
public static void main(String[] args) {
String[] precos = {"10", "25", "7,50", "8"};
int total = 0;
for (String p : precos) {
try {
total += Integer.parseInt(p);
} catch (NumberFormatException e) {
}
}
System.out.println("Total: " + total);
}
}
$ java Main.java Total: 43
O total certo seria 50,50. O "7,50" lançou NumberFormatException, o catch vazio "engoliu", e o programa mostrou um número errado sem avisar ninguém. Um programa que quebra é chato; um programa que dá a resposta errada em silêncio é muito pior, porque ninguém percebe.
Se você capturou, faça pelo menos uma destas coisas:
- Avise:
System.out.println("Preço inválido ignorado: " + p); - Corrija: use um valor padrão que faça sentido, ou peça de novo.
- Registre:
e.printStackTrace();enquanto desenvolve. - Não capture: se você não sabe o que fazer com a exceção ali, deixe-a subir para quem sabe (veja throw e throws).
Formas diferentes, qual usar
| Situação | Forma |
|---|---|
| Um tipo de problema, uma resposta | try { … } catch (TipoException e) { … } |
| Vários problemas, respostas diferentes | Vários catch, do mais específico ao mais geral |
| Vários problemas, a mesma resposta | Multi-catch: catch (A | B e) |
| Família inteira de problemas | catch da classe mãe (com cuidado) |
| Algo que precisa rodar com ou sem exceção | finally ou try-with-resources (próxima aula) |
| Você não sabe tratar ali | Não capture; deixe subir (throws) |
Dá para evitar com um if | Prefira o if: if (provas != 0) é mais claro que capturar ArithmeticException |
Quando dá errado
public class Main {
public static void main(String[] args) {
try {
int idade = Integer.parseInt("29");
} catch (NumberFormatException e) {
System.out.println("Não é número");
}
System.out.println("Idade: " + idade);
}
}
$ java Main.java
Main.java:8: error: cannot find symbol
System.out.println("Idade: " + idade);
^
symbol: variable idade
location: class Main
1 error
error: compilation failed
O que aconteceu: as chaves do try são um bloco, e uma variável criada dentro de um bloco só existe lá dentro (veja Escopo). Na linha 8, idade já não existe.
Como resolver: declare a variável antes do try, com um valor inicial (int idade = 0;), e só faça a atribuição dentro dele (idade = Integer.parseInt("29");). Ou coloque o println dentro do try.
O que aconteceu: um catch de uma classe mãe (Exception, RuntimeException) veio antes do catch de uma filha. O de baixo nunca seria usado.
Como resolver: coloque os catch do mais específico para o mais geral.
O que aconteceu: a exceção lançada não é do tipo do catch (nem filha dele). Leia a primeira linha do stack trace: o tipo que aparece ali é o que você precisa capturar. Também pode ser que a linha que lançou esteja fora do try: confira o número da linha.
Como resolver: acrescente um catch para esse tipo, ou mova a linha para dentro do try.
O que aconteceu: provavelmente há um catch vazio engolindo exceções. Procure por catch (…) { } no código.
Como resolver: coloque pelo menos um aviso ou e.printStackTrace() dentro dele e rode de novo.
Referência
| Sintaxe | O que faz |
|---|---|
try { … } | Bloco vigiado: se algo lança exceção, o resto dele é pulado |
catch (Tipo e) { … } | Roda se o try lançou Tipo ou uma filha dele |
catch (A e) { … } catch (B e) { … } | Vários tratamentos; o primeiro que combina é usado |
catch (A | B e) { … } | Multi-catch: o mesmo tratamento para A ou B (sem parentesco) |
e.getMessage() | A mensagem (pode ser null) |
e.toString() | pacote.Classe: mensagem |
e.printStackTrace() | Mostra o stack trace em System.err e continua |
error: exception X has already been caught | catch de filha depois do da mãe |
error: Alternatives in a multi-catch statement cannot be related by subclassing | Multi-catch com mãe e filha juntas |
Exercícios
1. A palavra que falta
Complete para capturar o problema do parseInt:
2. Só a mensagem
Complete para mostrar só a mensagem da exceção (sem o nome da classe):
3. Multi-catch
Complete com o símbolo que junta os dois tipos no mesmo catch:
4. O que este trecho mostra?
try {
System.out.println("A");
int x = Integer.parseInt("x");
System.out.println("B");
} catch (NumberFormatException e) {
System.out.println("C");
}
System.out.println("D");
5. Um try lança ArithmeticException e o único catch é catch (NumberFormatException e). O que acontece?
6. Qual ordem de catch compila?
Missão final: o programa divide a conta, mas quebra com 0 pessoas (e quebraria com texto). Proteja a conta com try e um multi-catch de NumberFormatException e ArithmeticException que mostre Entrada inválida: seguido de e.getMessage(). Teste com 90 e 0.
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner teclado = new Scanner(System.in);
System.out.print("Total da conta: ");
String total = teclado.nextLine();
System.out.print("Pessoas: ");
String pessoas = teclado.nextLine();
int cada = Integer.parseInt(total) / Integer.parseInt(pessoas);
System.out.println("Cada um paga " + cada);
}
}
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner teclado = new Scanner(System.in);
System.out.print("Total da conta: ");
String total = teclado.nextLine();
System.out.print("Pessoas: ");
String pessoas = teclado.nextLine();
try {
int cada = Integer.parseInt(total) / Integer.parseInt(pessoas);
System.out.println("Cada um paga " + cada);
} catch (NumberFormatException | ArithmeticException e) {
System.out.println("Entrada inválida: " + e.getMessage());
}
}
}
Resumo
trymarca o código que pode lançar exceção;catch (Tipo e)diz o que fazer se ela vier.- Quando uma linha do
trylança, o resto dotryé pulado, ocatchroda e o programa continua depois do bloco. - O
catchpega o tipo pedido e as classes filhas; outros tipos passam direto. - Vários
catch: do mais específico ao mais geral, senãoexception … has already been caught. - Multi-catch
catch (A | B e): mesma resposta para tipos sem parentesco. getMessage()dá a mensagem;printStackTrace()mostra o rastro completo sem encerrar o programa.- Nunca deixe um
catchvazio: avise, corrija, registre ou não capture.