Una librería con libros holográficos hechos de código fuente.

Creación y Manejo de Excepciones Propias en Java

Introducción a las Excepciones en Java

Las excepciones son eventos que interrumpen el flujo normal de un programa debido a errores o condiciones inesperadas. Piensa en las excepciones como «alertas rojas» que nos avisan de que algo salió mal en nuestro programa. Ya conocemos algunas excepciones estándar (como NullPointerException o ArrayIndexOutOfBoundsException). Java permite manejar estas excepciones mediante bloques try-catch, pero ¿qué pasa si necesitamos una alerta específica para nuestro programa? Java también ofrece la posibilidad de crear excepciones personalizadas para adaptarse a las necesidades específicas de tu aplicación.

En esta guía, aprenderás a crear tus propias excepciones, cuándo es apropiado hacerlo y cuándo delegar su manejo con la palabra clave throws.

Recuerda sobre las excepciones.

  • Definición: Una excepción es un evento que ocurre durante la ejecución de un programa que interrumpe el flujo normal de las instrucciones.
  • Importancia:
    • Manejo de Errores: Permiten detectar y responder a situaciones inesperadas (ej: falta de conexión a internet, datos inválidos).
    • Robustez: Hacen que el programa sea más resistente a fallos, evitando que se bloquee o termine inesperadamente.
    • Claridad: Facilitan la identificación y corrección de errores, ya que proporcionan información sobre qué salió mal y dónde.

¿Qué son las Excepciones Propias?

Una excepción propia (o custom exception) es una clase que hereda de Exception (para excepciones verificadas) o RuntimeException (para excepciones no verificadas). Estas excepciones se utilizan para representar errores específicos de tu aplicación que no están cubiertos por las excepciones estándar de Java.

Para crear una excepción propia, necesitamos:

  1. Crear una Clase: La clase debe extender Exception (para checked exceptions) o RuntimeException (para unchecked exceptions).
  2. Constructores: Proporcionar al menos un constructor que acepte un mensaje descriptivo del error.

Ejemplos

Saldo insuficiente:

public class SaldoInsuficienteException extends Exception { // Checked exception
    public SaldoInsuficienteException(String mensaje) {
        super(mensaje); // Llama al constructor de la clase padre (Exception)
    }
}

Explicación:

  • public class SaldoInsuficienteException extends Exception: Define la clase SaldoInsuficienteException que hereda de Exception. Esto la convierte en una checked exception.
  • public SaldoInsuficienteException(String mensaje): Define el constructor que recibe un mensaje descriptivo del error.
  • super(mensaje): Llama al constructor de la clase padre (Exception) y le pasa el mensaje. Esto permite que la excepción tenga un mensaje que se pueda leer fácilmente.

Edad inválida:

// Excepción personalizada para errores de edad inválida
public class EdadInvalidaException extends Exception {
    // Constructor con mensaje
    public EdadInvalidaException(String mensaje) {
        super(mensaje);
    }
}

Lanzando Excepciones Propias (throw)

Para lanzar una excepción propia, utilizamos la palabra clave throw seguida de una instancia de la excepción.

Ejemplo: Lanzando SaldoInsuficienteException

public class CuentaBancaria {
    private double saldo;

    public CuentaBancaria(double saldoInicial) {
        this.saldo = saldoInicial;
    }

    public void retirar(double cantidad) throws SaldoInsuficienteException {
        if (cantidad > saldo) {
            throw new SaldoInsuficienteException("Saldo insuficiente para retirar " + cantidad +
                    ". Saldo actual: " + saldo); // Lanza la excepción
        }
        saldo -= cantidad;
    }

    public double getSaldo() {
        return saldo;
    }

    public static void main(String[] args) {
        CuentaBancaria cuenta = new CuentaBancaria(1000);
        try {
            cuenta.retirar(1500); // Intenta retirar más dinero del que hay
        } catch (SaldoInsuficienteException e) {
            System.err.println("Error: " + e.getMessage()); // Captura la excepción y muestra el mensaje
        }
    }
}

Explicación:

  • public void retirar(double cantidad) throws SaldoInsuficienteException: Declara que el método retirar() puede lanzar una SaldoInsuficienteException. Esto es necesario porque SaldoInsuficienteException es una checked exception.
  • if (cantidad > saldo) { throw new SaldoInsuficienteException(...) }: Si la cantidad a retirar es mayor que el saldo, lanza una nueva instancia de SaldoInsuficienteException con un mensaje descriptivo.
  • try { cuenta.retirar(1500) } catch (SaldoInsuficienteException e): Encierra la llamada a retirar() en un bloque try-catch para capturar la excepción si se lanza.

¿Cuándo Crear una Excepción Propia?

  1. Situaciones Específicas del Dominio: 
    • Cuando se produce un error que es específico del dominio de tu programa y que no se puede representar adecuadamente con las excepciones estándar.
    • Ejemplos: ProductoNoEncontradoException en una tienda online, SaldoInsuficienteException en una aplicación bancaria.
  2. Cuando necesitas un error específico de tu aplicación:
    • Ejemplo: Validar que la edad de un usuario no sea negativa.
    • Las excepciones estándar como IllegalArgumentException son genéricas y pueden no transmitir suficiente información.
  3. Información Adicional: 
    • Cuando necesitas proporcionar información adicional sobre el error que no se puede obtener con las excepciones estándar.
    • Ejemplos: un código de error específico, incluir el ID del usuario que intentó realizar la operación.
  4. Control Más Fino: 
    • Cuando necesitas un control más fino sobre cómo se manejan los errores.
    • Ejemplos: quieres registrar el error en un archivo o enviar una notificación por correo electrónico.
  5. Excepciones «Checked» versus «Unchecked»:
    • Crea excepciones «checked» (que extienden directamente de Exception) cuando la condición de excepción es algo que el código cliente debería razonablemente considerar y recuperarse. Por ejemplo, un archivo que no se encuentra. El programador debe estar preparado para esta eventualidad.
    • Crea excepciones «unchecked» (que extienden de RuntimeException) cuando la condición de excepción es normalmente el resultado de un error de programación. Por ejemplo, acceder a un array fuera de sus límites. No es razonable esperar que el código cliente maneje esto.

¿Cuándo Delegar una Excepción con throws?

La palabra clave throws se utiliza en la firma de un método para indicar que no manejará una excepción verificada (checked exception) y que la responsabilidad de manejar el error recae en el código que llama al método. Esto es útil cuando:

  1. Responsabilidad: 
    • El método actual no puede manejar la excepción directamente, ya sea porque no tiene la información suficiente o la capacidad para manejar la excepción de forma adecuada.
    • Ejemplo: Un método que lee un archivo y no sabe qué hacer si el archivo no existe.
  2. Abstracción: 
    • Cuando manejar la excepción en el método actual rompería la abstracción o complicaría demasiado el código.
  3. Centralización: 
    • Cuando quieres centralizar el manejo de excepciones en un solo lugar .
    • Ejemplo: quieres manejar todas las excepciones en el método main() o en un manejador de excepciones global.
  4. Quieres propagar el error a un nivel superior:
    • Ejemplo: Una capa de acceso a datos que delega el manejo de errores a la capa de presentación.

Ejemplo Práctico: Creación y Uso de una Excepción Personalizada

1. Crear la Excepción

public class EdadInvalidaException extends Exception {
    private int edad;

    public EdadInvalidaException(String mensaje, int edad) {
        super(mensaje);
        this.edad = edad;
    }

    public int getEdad() {
        return edad;
    }
}

2. Lanzar la Excepción

public class Persona {
    private String nombre;
    private int edad;

    public void setEdad(int edad) throws EdadInvalidaException {
        if (edad < 0) {
            throw new EdadInvalidaException("La edad no puede ser negativa: " + edad, edad);
        }
        this.edad = edad;
    }
}

3. Capturar la Excepción

public class Main {
    public static void main(String[] args) {
        Persona persona = new Persona();
        try {
            persona.setEdad(-5); // Lanza EdadInvalidaException
        } catch (EdadInvalidaException e) {
            System.out.println("Error: " + e.getMessage());
            System.out.println("Edad inválida: " + e.getEdad());
        }
    }
}

Salida:

Error: La edad no puede ser negativa: -5
Edad inválida: -5

Ejemplo: Método que delega una excepción

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class LectorArchivos {
    // Delegar la excepción FileNotFoundException
    public String leerArchivo(String ruta) throws FileNotFoundException {
        File archivo = new File(ruta);
        Scanner lector = new Scanner(archivo);
        StringBuilder contenido = new StringBuilder();
        while (lector.hasNextLine()) {
            contenido.append(lector.nextLine());
        }
        lector.close();
        return contenido.toString();
    }
}

public class Main {
    public static void main(String[] args) {
        LectorArchivos lector = new LectorArchivos();
        try {
            String texto = lector.leerArchivo("archivo.txt");
            System.out.println(texto);
        } catch (FileNotFoundException e) {
            System.out.println("Archivo no encontrado: " + e.getMessage());
        }
    }
}

Explicación:

  • El método leerArchivo declara throws FileNotFoundException, delegando el manejo del error al método que lo llama (main).
  • El método main captura la excepción y muestra un mensaje al usuario.

Buenas Prácticas para Crear y Usar Excepciones Propias

  1. Usa nombres descriptivos: 
    • Haz que los nombres de tus excepciones indiquen claramente qué tipo de error representan.
    • Ejemplo: ProductoNoEncontradoException en lugar de Error123Exception.
  2. Mensajes claros: 
    • Proporciona mensajes de error claros y concisos que ayuden a los programadores a entender qué salió mal y cómo solucionarlo.
  3. Jerarquía de Excepciones: 
    • Si tienes varias excepciones relacionadas, considera crear una jerarquía de excepciones. Esto te permitirá capturar excepciones más generales y manejar grupos de errores relacionados.
  4. No Abuses de las Excepciones: 
    • Las excepciones deben usarse para situaciones excepcionales, no para el flujo normal del programa. Evita usarlas como un mecanismo de control de flujo (por ejemplo: usar una excepción para salir de un bucle).
  5. Hereda de la clase correcta:
    • Usa Exception para excepciones verificadas (obligan a usar try-catch o throws).
    • Usa RuntimeException para excepciones no verificadas (errores que no se espera que ocurran).
  6. Documenta las excepciones:
    • Documenta tus excepciones explicando cuándo se lanzan y cómo se deben manejar.
    • Usa @throws en JavaDoc para indicar qué excepciones puede lanzar un método.

Ejemplo de Documentación:

/**
 * Establece la edad de la persona.
 * @param edad La edad a establecer.
 * @throws EdadInvalidaException Si la edad es negativa.
 */
public void setEdad(int edad) throws EdadInvalidaException {
    // Código del método
}

Convenciones Importantes

  1. No abuses de las excepciones no verificadas (RuntimeException):
    • Solo úsalas para errores graves que no deberían ocurrir en condiciones normales.
  2. Evita capturar excepciones genéricas:
    • No uses catch (Exception e) a menos que sea necesario. Es mejor capturar excepciones específicas.
  3. No ignores excepciones:
    • Un catch vacío o que solo imprime un mensaje sin manejar el error puede ocultar problemas graves.

Conclusión

Crear excepciones propias y delegar su manejo con throws son técnicas esenciales para escribir código robusto y mantenible en Java. Al seguir las buenas prácticas y convenciones, podrás manejar errores de manera clara y eficiente, mejorando la calidad de tus aplicaciones. ¡Practica con los ejemplos y domina este concepto!


Ejercicios para practicar

  1. Crea la clase coche, que tenga un número de marchas. Crea una excepción MarchaIncorrectaException cuando se trate de cambiar a una marcha que no existe.
  2. Añade a Persona un atributo teléfono, comprueba en el setter que el teléfono tiene 9 dígitos y si no lanza la excepción TelefonoInvalidoException.
  3. Crea la clase Teléfono con un atributo para la capacidad del teléfono. Si se trata de asignar esa capacidad 0 o negativa, que lance la excepción CapacidadInvalidaException.
  4. Para la clase DNI, separa el número de la letra. Si se trata de asignar una letra inválida, lanza la excepción DniIncorrectoException.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.