Throw e Throws em Java
Em Java, a execução pode falhar por condições excepcionais. Em muitas situações, a própria plataforma gera a exceção, como na divisão por zero. Também existe a possibilidade de gerar uma exceção manualmente com o operador throw, além de declarar no cabeçalho de um método, por meio de throws, que ele pode gerar exceções que o método que faz a chamada precisará tratar.
Lançando exceções com o operador throw
O operador throw cria e lança um objeto de exceção. Em termos gerais, ele recebe uma instância de um tipo que representa uma exceção. A hierarquia padrão começa em Throwable e segue por Exception e Error. Muitos tipos de exceção têm construtor que aceita uma mensagem descritiva, a mesma que será retornada por getMessage() e exibida quando a exceção for mostrada no console.
Exemplo que lê um nome e lança uma exceção quando a entrada não atende a uma condição de validade:
import java.util.Scanner;
class Program {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.print("Enter name: ");
String name = in.nextLine();
// se o nome tiver menos de 2 caracteres, lança uma exceção
if (name.length() < 2) throw new Throwable("Name is too short");
System.out.println("Hello, " + name);
}
}O compilador sinaliza erro ao compilar esse código, informando que a exceção do tipo Throwable não foi tratada nem declarada. Isso ocorre porque Throwable é considerado um tipo de exceção verificada. Para esse caso, é necessário tratar no próprio método com try..catch ou declarar no cabeçalho que o método pode lançar a exceção.
Exceções verificadas e não verificadas
As exceções em Java são geralmente classificadas em dois grupos. As não verificadas englobam RuntimeException e Error, bem como suas subclasses, e não exigem tratamento nem declaração com throws. As verificadas abrangem as demais subclasses de Exception e exigem tratamento ou declaração. No exemplo anterior foi usado Throwable, que o compilador trata como verificada, por isso a necessidade de tratar ou declarar. No contexto de validação de argumentos, costuma-se preferir uma exceção não verificada como IllegalArgumentException, embora o exemplo a seguir mostre primeiro a abordagem com try..catch.
Tratamento com try..catch
A parte do código que pode lançar a exceção é colocada em um bloco try. Se a condição for violada, a exceção é lançada e o fluxo é desviado para o bloco catch, onde a mensagem é exibida.
import java.util.Scanner;
class Program {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.print("Enter name: ");
String name = in.nextLine();
try {
// se o nome tiver menos de 2 caracteres, lança uma exceção
if (name.length() < 2) throw new Throwable("Name is too short");
System.out.println("Hello, " + name);
}
catch (Throwable ex) {
System.out.println("Error: " + ex.getMessage());
}
}
}Ao submeter um único caractere, a execução cai no catch e exibe a mensagem. A mesma ideia vale para qualquer trecho em que a condição de validade precise ser garantida durante a execução.
Exemplo com outro tipo de exceção
Segue um exemplo com IllegalArgumentException, adequado para indicar que um parâmetro recebido por método ou construtor é inválido. O exemplo mantém a mesma lógica, com a checagem de faixa de idade no construtor.
class Program {
public static void main(String[] args) {
var tom = new Person("Tom", -20);
tom.print();
}
}
class Person {
private String name;
private int age;
Person(String name, int age) {
if (age < 1 || age > 110) throw new IllegalArgumentException("Invalid age: " + age);
this.name = name;
this.age = age;
}
void print() { System.out.printf("Name: %s; Age: %d%n", name, age); }
}Neste caso, IllegalArgumentException é uma exceção não verificada. O compilador não exige tratamento ou declaração para esse cenário, e a execução encerrará com erro se a condição for violada. Quando se deseja capturar a mensagem e continuar o fluxo, o tratamento pode ser feito no método que faz a chamada, como no trecho abaixo:
class Program {
public static void main(String[] args) {
try {
var tom = new Person("Tom", -20);
tom.print();
}
catch (IllegalArgumentException ex) {
System.out.println(ex.getMessage());
}
}
}
class Person {
private String name;
private int age;
Person(String name, int age) {
if (age < 1 || age > 110) throw new IllegalArgumentException("Invalid age: " + age);
this.name = name;
this.age = age;
}
void print() { System.out.printf("Name: %s; Age: %d%n", name, age); }
}O operador throws
Quando um método pode gerar uma exceção verificada e não realiza o tratamento local, o cabeçalho do método deve informar isso por meio de throws. A declaração é escrita após a lista de parâmetros. O método que faz a chamada decide tratar no próprio escopo ou, por sua vez, também declarar com throws.
Exemplo em que a leitura do nome lança uma exceção verificada quando a entrada não atende a uma condição de validade.
import java.util.Scanner;
class Program {
public static void main(String[] args) {
try {
printName(); // esta chamada pode lançar uma exceção
}
catch (Throwable ex) {
System.out.println(ex.getMessage());
}
}
static void printName() throws Throwable {
Scanner in = new Scanner(System.in);
System.out.print("Enter name: ");
String name = in.nextLine();
// se o nome tiver menos de 2 caracteres, lança uma exceção
if (name.length() < 2) throw new Throwable("Name length is insufficient: " + name.length());
System.out.println("Hello, " + name);
}
}Outra estratégia possível é propagar para o método main, declarando throws também no cabeçalho dele. O código a seguir compila com sucesso e permite que a exceção chegue ao topo:
import java.util.Scanner;
class Program {
public static void main(String[] args) throws Throwable {
printName(); // esta chamada pode lançar uma exceção
}
static void printName() throws Throwable {
Scanner in = new Scanner(System.in);
System.out.print("Enter name: ");
String name = in.nextLine();
// se o nome tiver menos de 2 caracteres, lança uma exceção
if (name.length() < 2) throw new Throwable("O comprimento do nome é insuficiente: " + name.length());
System.out.println("Olá, " + name);
}
}Em ambos os exemplos, a lógica da verificação permanece igual, com a diferença de onde fica o tratamento. Quando o método chamado não trata a exceção verificada, o método que faz a chamada precisa tratá-la ou declará-la também. Vale lembrar que um método pode declarar múltiplos tipos de exceção separados por vírgula em throws. Para exceções não verificadas como IllegalArgumentException, a declaração não é obrigatória, já que o compilador não faz verificação delas.
Observação: embora os exemplos usem
Throwableapenas para fins didáticos, a prática comum no desenvolvimento em Java evita lançar ou capturar esse tipo diretamente. Em validações de entrada, é comum empregarIllegalArgumentExceptionou criar um tipo específico de exceção de domínio quando se deseja diferenciar claramente o erro.
Resumo
throwlança explicitamente um objeto de exceção; a mensagem pode ser fornecida no construtor.- Exceções verificadas exigem tratamento com
try..catchou declaração comthrows; exceções não verificadas não exigem isso. IllegalArgumentExceptioné adequada para parâmetros inválidos, enquantoThrowablenão costuma ser lançada em código de aplicação.throwsdeclara no cabeçalho que o método pode lançar exceções, delegando a decisão de tratamento ao método que faz a chamada.- A mesma exceção pode ser tratada localmente com
try..catchou propagada; declarar vários tipos emthrowsé possível quando necessário.