CodeGym /Cursos /JAVA 25 SELF /Criando exceções personalizadas

Criando exceções personalizadas

JAVA 25 SELF
Nível 24 , Lição 1
Disponível

1. Introdução

Na biblioteca padrão do Java já existem diversas exceções: NullPointerException, IllegalArgumentException, IOException e outras. Mas às vezes as exceções padrão não são suficientes para descrever de forma clara e compreensível o erro ocorrido no seu programa.

Exemplo do mundo real:
Você está escrevendo um aplicativo bancário. O usuário tenta sacar mais dinheiro do que há na conta. Você pode lançar IllegalArgumentException. Mas será muito mais claro se você definir sua própria exceção, por exemplo, InsufficientFundsException. Assim, pelo código fica imediatamente visível o que aconteceu.

Exceções personalizadas — são uma forma correta de personalizar a aplicação. Elas permitem ajustar finamente o tratamento de problemas e, pelo próprio nome (se você nomear de forma lógica!), já fica claro o que ocorreu. Além disso, possuem auto-documentação: métodos com throws MyException na assinatura informam de imediato quais erros podem ocorrer. E ainda — sempre é possível adicionar campos adicionais (por exemplo, saldo, valor da operação etc.).

2. Como criar sua própria exceção?

É bem simples: crie uma nova classe que estenda uma das classes padrão de exceções.

  • Para verificadas (checked) — estenda Exception.
  • Para não verificadas (unchecked) — estenda RuntimeException.

Exemplo: exceção verificada

public class InvalidCredentialsException extends Exception {
    public InvalidCredentialsException(String message) {
        super(message); // Passamos a mensagem para a classe pai
    }
}

Agora você pode lançar essa exceção no seu código:

if (!login.equals("admin") || !password.equals("1234")) {
    throw new InvalidCredentialsException("Login ou senha inválidos");
}

Exemplo: exceção não verificada

public class NegativeBalanceException extends RuntimeException {
    public NegativeBalanceException(String message) {
        super(message);
    }
}

Quando usar checked e quando usar unchecked?

  • Checked — quando o erro é esperado e pode ser tratado (por exemplo, erro de validação, ausência de arquivo, dados incorretos do usuário).
  • Unchecked — quando o erro está relacionado a um bug na lógica do programa (por exemplo, divisão por zero, violação de invariante).

3. Construtores: como tornar a exceção informativa

Normalmente, em sua classe de exceção você implementa ao menos um construtor com o parâmetro String message. Mas costuma-se adicionar também outros:

public class ScoreLimitExceededException extends Exception {
    public ScoreLimitExceededException() {
        super();
    }
    public ScoreLimitExceededException(String message) {
        super(message);
    }
    public ScoreLimitExceededException(String message, Throwable cause) {
        super(message, cause);
    }
    public ScoreLimitExceededException(Throwable cause) {
        super(cause);
    }
}

Explicação:

  • message — descrição textual do erro.
  • cause — a causa (outra exceção), se você quiser “embrulhar” um erro em outro.

Dica: se você não souber quais construtores serão necessários — adicione pelo menos o que aceita uma string.

4. Uso de exceções personalizadas no código

Vamos considerar um exemplo: temos um usuário, o usuário tem pontos, e não se pode adicionar mais que 100.

public class User {
    private String name;
    private int score;

    public User(String name) {
        this.name = name;
        this.score = 0;
    }

    public void addScore(int points) throws ScoreLimitExceededException {
        if (score + points > 100) {
            throw new ScoreLimitExceededException("Limite de pontos excedido! Tentativa de adicionar: " + points);
        }
        this.score += points;
    }
}

Classe da exceção:

public class ScoreLimitExceededException extends Exception {
    public ScoreLimitExceededException(String message) {
        super(message);
    }
}

Tratamento:

try {
    user.addScore(60);
    user.addScore(50); // Aqui será lançada a exceção!
} catch (ScoreLimitExceededException e) {
    System.out.println("Erro: " + e.getMessage());
}

Resultado:

Erro: Limite de pontos excedido! Tentativa de adicionar: 50

Talvez você esteja se perguntando: e se, em vez de lançar uma exceção, simplesmente usar a condição if e, por exemplo, retornar false ou outro valor especial para indicar que a operação falhou? Por exemplo:

public boolean addScore(int points) {
    if (score + points > 100) {
        return false; // Ou lançar algum RuntimeException, se não houver intenção de tratar
    }
    this.score += points;
    return true;
}

Embora essa abordagem possa parecer mais simples, ela tem desvantagens quando se trata de erros sérios ou de violações da lógica da aplicação.

Em primeiro lugar, retornar false ou outro valor para indicar erro obriga o código chamador a sempre verificar o valor retornado. Se o programador esquecer de fazer isso, o erro pode passar despercebido, o que levará a um comportamento imprevisível do programa. As exceções, ao contrário, forçam o tratamento (para exceções verificadas) ou, pelo menos, sinalizam explicitamente o problema, se não forem capturadas.

Em segundo lugar, as exceções transmitem de forma mais clara a semântica do erro. Retornar false pode significar qualquer coisa: “não foi possível”, “não se aplica”, “indisponível”. A exceção ScoreLimitExceededException deixa claro e inequívoco: “o limite de pontos foi excedido”. Isso melhora a legibilidade e a manutenibilidade do código.

Em terceiro lugar, as exceções permitem centralizar o tratamento de erros. Em vez de espalhar verificações com if por todo o código onde addScore é chamado, você pode capturar a exceção em um único lugar e tomar a decisão adequada: exibir uma mensagem ao usuário, registrar em log ou realizar o rollback de uma transação.

Por fim, no caso de problemas como exceder um limite, isso é realmente uma situação excepcional (daí o nome). O fluxo normal de execução do programa pressupõe que os pontos serão adicionados com sucesso. Se isso não acontecer — é uma violação da lógica de negócio ou dos invariantes do objeto, o que é um cenário ideal para o uso de exceções.

5. Adicionando campos próprios às exceções

Às vezes, é útil adicionar à sua exceção dados adicionais que ajudem a tratar o erro.

Exemplo:

public class ScoreLimitExceededException extends Exception {
    private int currentScore;
    private int attemptedAdd;

    public ScoreLimitExceededException(String message, int currentScore, int attemptedAdd) {
        super(message);
        this.currentScore = currentScore;
        this.attemptedAdd = attemptedAdd;
    }

    public int getCurrentScore() { 
        return currentScore; 
    }
    public int getAttemptedAdd() { 
        return attemptedAdd; 
    }
}

Uso:

if (score + points > 100) {
    throw new ScoreLimitExceededException(
        "Limite de pontos excedido!",
        this.score,
        points
    );
}

6. Detalhes úteis

Como nomear suas exceções?

Em Java é comum nomear exceções personalizadas com o sufixo Exception: InvalidUserInputException, InsufficientFundsException, ScoreLimitExceededException.

Não nomeie suas exceções simplesmente como Error ou Warning — isso pode confundir outros desenvolvedores (e até você mesmo daqui a algumas semanas).

Onde e quando lançar suas exceções?

  • Ao validar dados do usuário (por exemplo, nome vazio, idade negativa).
  • Ao violar regras de negócio (por exemplo, exceder um limite, tentar sacar mais dinheiro do que há na conta).
  • Ao lidar com serviços externos (por exemplo, serviço indisponível, tempo de espera excedido).

7. Erros comuns ao criar exceções personalizadas

Erro nº 1: Herança do tipo errado.
Estenda Exception (ou RuntimeException), e não Throwable ou Error.

Erro nº 2: Construtor com mensagem não incluído.
Sem um construtor que aceite uma string (String message), suas exceções serão silenciosas e será difícil depurá-las.

Erro nº 3: Uso de exceções padrão para lógica de negócio.
Evite lançar NullPointerException ou IllegalArgumentException onde é necessário uma exceção “descritiva” própria.

Erro nº 4: Abusar de exceções personalizadas.
Não crie uma classe de exceção separada para cada detalhe trivial. Se o erro não for exclusivo do seu domínio, use exceções padrão.

Erro nº 5: Ausência de serialização (raro, mas acontece).
Se sua exceção for transmitida pela rede ou persistida, vale implementar implements Serializable. No entanto, para aplicações simples, isso não é crítico.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION