CodeGym /Cursos /JAVA 25 SELF /Creación de excepciones personalizadas

Creación de excepciones personalizadas

JAVA 25 SELF
Nivel 24, Lección 1
Disponible

1. Introducción

La biblioteca estándar de Java ya incluye muchas excepciones: NullPointerException, IllegalArgumentException, IOException, entre otras. Pero a veces las excepciones estándar no bastan para describir de forma clara y precisa el error que se ha producido en tu programa.

Ejemplo de la vida real:
Estás desarrollando una aplicación bancaria. El usuario intenta retirar más dinero del que hay en la cuenta. Se podría lanzar IllegalArgumentException. Pero será mucho más claro si propones tu propia excepción, por ejemplo, InsufficientFundsException. Así, al leer el código, se entiende de inmediato lo que ha pasado.

Las excepciones propias son una personalización adecuada de la aplicación. Permiten ajustar finamente el trabajo con los problemas y, además, por su nombre (¡si las nombras con lógica!) queda claro al instante qué ha ocurrido. También tienen la virtud de la autodocumentación: los métodos con throws MyException en la firma indican de inmediato qué errores pueden surgir. Y, además, siempre puedes añadir campos adicionales (por ejemplo, saldo, importe de la operación, etc.).

2. ¿Cómo crear tu propia excepción?

Es muy sencillo: crea una nueva clase que herede de una de las clases estándar de excepciones.

  • Para excepciones comprobadas (checked) — hereda de Exception.
  • Para no comprobadas (unchecked) — de RuntimeException.

Ejemplo: excepción comprobada

public class InvalidCredentialsException extends Exception {
    public InvalidCredentialsException(String message) {
        super(message); // Pasamos el mensaje a la clase padre
    }
}

Ahora puedes lanzar esta excepción en tu código:

if (!login.equals("admin") || !password.equals("1234")) {
    throw new InvalidCredentialsException("Usuario o contraseña incorrectos");
}

Ejemplo: excepción no comprobada

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

¿Cuándo usar checked y cuándo unchecked?

  • Checked — si el error es esperable y se puede manejar (por ejemplo, error de validación, ausencia de archivo, datos de usuario incorrectos).
  • Unchecked — si el error está relacionado con un bug en la lógica del programa (por ejemplo, división entre cero, violación de un invariante).

3. Constructores: cómo hacer que la excepción sea informativa

Normalmente, en tu clase de excepción implementas al menos un constructor con el parámetro String message. Pero a menudo se añaden otros:

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);
    }
}

Explicación:

  • message — descripción textual del error.
  • cause — la causa (otra excepción), si quieres «envolver» un error dentro de otro.

Consejo: si no sabes qué constructores necesitas, añade al menos el que recibe una cadena.

4. Uso de excepciones propias en el código

Veamos un ejemplo: tenemos un usuario, el usuario tiene puntos, y no se puede añadir más de 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("¡Se superó el límite de puntos! Intento de agregar: " + points);
        }
        this.score += points;
    }
}

Clase de excepción:

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

Manejo:

try {
    user.addScore(60);
    user.addScore(50); // ¡Aquí se lanzará la excepción!
} catch (ScoreLimitExceededException e) {
    System.out.println("Error: " + e.getMessage());
}

Resultado:

Error: ¡Se superó el límite de puntos! Intento de agregar: 50

Seguramente te estarás preguntando: ¿y si en lugar de una excepción usamos simplemente una condición if y, por ejemplo, devolvemos false u otro valor especial para indicar que la operación no tuvo éxito? Por ejemplo:

public boolean addScore(int points) {
    if (score + points > 100) {
        return false; // O lanzar alguna RuntimeException si no se quiere manejarla
    }
    this.score += points;
    return true;
}

Aunque este enfoque puede parecer más simple, tiene desventajas cuando se trata de errores serios o violaciones de la lógica de la aplicación.

En primer lugar, devolver false u otro valor para señalar un error obliga al código llamador a comprobar siempre el valor de retorno. Si el programador olvida hacerlo, el error puede pasar desapercibido, lo que llevará a un comportamiento impredecible del programa. Las excepciones, por el contrario, fuerzan el manejo (para las excepciones comprobadas) o, al menos, señalan explícitamente el problema si no se capturan.

En segundo lugar, las excepciones transmiten la semántica del error con más claridad. Devolver false puede significar cualquier cosa: «no se pudo», «no aplicable», «no disponible». La excepción ScoreLimitExceededException dice de forma clara e inequívoca: «se superó el límite de puntos». Esto mejora la legibilidad y el mantenimiento del código.

En tercer lugar, las excepciones permiten centralizar el manejo de errores. En lugar de dispersar comprobaciones con if por todo el código donde se llama a addScore, puedes capturar la excepción en un único lugar y tomar la decisión correspondiente: mostrar un mensaje al usuario, escribir en el log o realizar un rollback de la transacción.

Por último, en el caso de problemas como superar un límite, se trata realmente de una situación excepcional (de ahí el nombre). El flujo normal de ejecución del programa supone que los puntos se añadirán con éxito. Si no es así, es una violación de la lógica de negocio o de los invariantes del objeto, lo cual es un escenario ideal para usar excepciones.

5. Añadir campos propios a las excepciones

A veces es útil añadir a tu excepción datos adicionales que ayuden a manejar el error.

Ejemplo:

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(
        "¡Se superó el límite de puntos!",
        this.score,
        points
    );
}

6. Matices útiles

¿Cómo nombrar tus excepciones?

En Java es habitual nombrar las excepciones de usuario con el sufijo Exception: InvalidUserInputException, InsufficientFundsException, ScoreLimitExceededException.

No nombres tus excepciones simplemente Error o Warning — puede llevar a confusión a otros desarrolladores (e incluso a ti mismo dentro de un par de semanas).

¿Dónde y cuándo lanzar tus excepciones?

  • Al validar datos del usuario (por ejemplo, nombre vacío, edad negativa).
  • Al violar reglas de negocio (por ejemplo, superar un límite, intentar retirar más dinero del que hay en la cuenta).
  • Ante errores al trabajar con servicios externos (por ejemplo, servicio no disponible, tiempo de espera excedido).

7. Errores típicos al crear excepciones propias

Error n.º 1: Heredar de la clase equivocada.
Hereda de Exception (o RuntimeException), y no de Throwable o Error.

Error n.º 2: No añadir un constructor con mensaje.
Sin un constructor que reciba una cadena (String message), tus excepciones serán mudas y será difícil depurarlas.

Error n.º 3: Usar excepciones estándar para la lógica de negocio.
No lances NullPointerException o IllegalArgumentException donde necesitas tu propia excepción «expresiva».

Error n.º 4: Abusar de las excepciones propias.
No crees una clase de excepción separada para cada minucia. Si el error no es único de tu dominio, utiliza excepciones estándar.

Error n.º 5: Falta de serialización (raro, pero pasa).
Si tu excepción se va a enviar por la red o a persistir, conviene implementar implements Serializable. Sin embargo, para aplicaciones sencillas no es crítico.

1
Tarea
JAVA 25 SELF, nivel 24, lección 1
Bloqueada
Guardián de la puerta del juego: Verificación de la puntuación del jugador
Guardián de la puerta del juego: Verificación de la puntuación del jugador
1
Tarea
JAVA 25 SELF, nivel 24, lección 1
Bloqueada
Fortaleza de la contraseña: Protección de los datos del usuario
Fortaleza de la contraseña: Protección de los datos del usuario
1
Tarea
JAVA 25 SELF, nivel 24, lección 1
Bloqueada
Control de edad en el parque de atracciones: mensajes de error flexibles
Control de edad en el parque de atracciones: mensajes de error flexibles
1
Tarea
JAVA 25 SELF, nivel 24, lección 1
Bloqueada
Gestión del almacén de artefactos mágicos: Detalles precisos del desbordamiento
Gestión del almacén de artefactos mágicos: Detalles precisos del desbordamiento
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION