Java

Tutorial Java: Excepciones

Java excepciones: manejo y prevención. Aprende a manejar y prevenir excepciones en Java con ejemplos prácticos y detallados.

Aprende Java y certifícate

¿Para qué sirven las excepciones?

Las excepciones permiten gestionar situaciones anómalas o errores que pueden ocurrir durante la ejecución de un programa. En lugar de permitir que un error detenga abruptamente la aplicación, las excepciones proporcionan una forma estructurada de detectar, comunicar y manejar estos problemas.

Cuando se desarrolla software, es inevitable enfrentarse a situaciones imprevistas: archivos que no existen, conexiones de red interrumpidas, divisiones por cero o intentos de acceder a posiciones inexistentes en un array.

Propósitos principales de las excepciones

  • Separación de la lógica de error: Las excepciones permiten mantener el código principal limpio y enfocado en el flujo normal, mientras que la gestión de errores se maneja por separado.
// Sin excepciones (código mezclado con verificaciones de error)
public int dividir(int a, int b) {
    if (b == 0) {
        System.err.println("Error: División por cero");
        return 0; // ¿Qué valor devolver en caso de error?
    }
    return a / b;
}

// Con excepciones (código más limpio)
public int dividir(int a, int b) throws ArithmeticException {
    return a / b; // Java lanzará automáticamente ArithmeticException si b es 0
}
  • Propagación de errores: Las excepciones pueden propagarse a través de múltiples niveles de llamadas a métodos, permitiendo que el error se maneje en el nivel más adecuado.
public void procesarDatos() throws IOException {
    leerArchivo("datos.txt");
    // Si leerArchivo falla, la excepción se propaga automáticamente
}

public void leerArchivo(String ruta) throws IOException {
    // Código para leer archivo que puede lanzar IOException
    Files.readAllLines(Path.of(ruta));
}
  • Agrupación jerárquica de errores: Java organiza las excepciones en una jerarquía de clases, lo que permite capturar categorías enteras de errores o manejar tipos específicos según sea necesario.
try {
    // Código que podría lanzar diferentes tipos de excepciones
    procesarArchivo();
} catch (FileNotFoundException e) {
    // Manejo específico para archivo no encontrado
    System.err.println("El archivo no existe: " + e.getMessage());
} catch (IOException e) {
    // Manejo para otros errores de E/S
    System.err.println("Error de E/S: " + e.getMessage());
}
  • Información detallada sobre errores: Las excepciones contienen datos sobre el error, como mensajes descriptivos, la pila de llamadas y, potencialmente, la causa raíz.
try {
    int[] numeros = new int[5];
    numeros[10] = 50; // Esto causará ArrayIndexOutOfBoundsException
} catch (ArrayIndexOutOfBoundsException e) {
    System.err.println("Mensaje: " + e.getMessage());
    System.err.println("Tipo de excepción: " + e.getClass().getName());
    e.printStackTrace(); // Muestra la pila de llamadas completa
}

Ventajas del uso de excepciones

  • Robustez: Las aplicaciones que manejan adecuadamente las excepciones son más resistentes a fallos y pueden continuar funcionando incluso cuando ocurren errores.
public void procesarDocumentos() {
    List<String> documentos = obtenerListaDocumentos();
    
    for (String documento : documentos) {
        try {
            procesarDocumento(documento);
        } catch (Exception e) {
            // Registrar el error pero continuar con el siguiente documento
            System.err.println("Error al procesar " + documento + ": " + e.getMessage());
        }
    }
    
    System.out.println("Procesamiento completado");
}
  • Legibilidad: El código principal se mantiene enfocado en la lógica de negocio, sin mezclarse con verificaciones de errores constantes.
  • Mantenibilidad: La centralización del manejo de errores facilita los cambios en la estrategia de gestión de errores sin modificar la lógica principal.
  • Recuperación: Permite implementar estrategias de recuperación ante fallos, como reintentos o soluciones alternativas.
public void conectarServidor() {
    int intentos = 0;
    boolean conectado = false;
    
    while (!conectado && intentos < 3) {
        try {
            // Intentar conexión
            abrirConexion();
            conectado = true;
            System.out.println("Conexión establecida");
        } catch (ConexionException e) {
            intentos++;
            System.out.println("Intento " + intentos + " fallido: " + e.getMessage());
            
            if (intentos < 3) {
                // Esperar antes de reintentar
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    }
    
    if (!conectado) {
        System.err.println("No se pudo establecer conexión después de 3 intentos");
    }
}

Cuándo utilizar excepciones

Las excepciones se utilizan principalmente en situaciones excepcionales (como su nombre indica), no para controlar el flujo normal del programa. Se recomienda su uso en los siguientes casos:

  • Errores de validación que no pueden ser evitados mediante verificaciones previas.
  • Problemas con recursos externos como archivos, bases de datos o servicios web.
  • Violaciones de precondiciones importantes para el funcionamiento correcto de un método.
  • Errores de programación como índices fuera de rango o referencias nulas.
public Usuario buscarPorId(int id) throws UsuarioNoEncontradoException {
    // Buscar usuario en la base de datos
    Usuario usuario = repositorio.findById(id);
    
    if (usuario == null) {
        // Situación excepcional: el ID debería existir
        throw new UsuarioNoEncontradoException("No se encontró usuario con ID: " + id);
    }
    
    return usuario;
}

Las excepciones tienen un coste de rendimiento, por lo que no deben usarse para situaciones que forman parte del flujo normal de la aplicación. Por ejemplo, no se debe usar una excepción para detectar el final de un archivo si se está leyendo línea por línea, ya que llegar al final es un comportamiento esperado.

Tipos de excepciones: excepciones comprobadas y no comprobadas

En Java, las excepciones se dividen en dos categorías principales: excepciones comprobadas (checked exceptions) y excepciones no comprobadas (unchecked exceptions). Esta clasificación determina cómo el compilador trata cada tipo de excepción y cómo los desarrolladores deben manejarlas en su código.

La distinción entre estos dos tipos se basa en la jerarquía de clases de excepciones en Java. Todas las excepciones derivan de la clase Throwable, que tiene dos subclases directas: Error y Exception.

Jerarquía de excepciones

Throwable
├── Error                 // Errores graves del sistema (unchecked)
│   ├── OutOfMemoryError
│   ├── StackOverflowError
│   └── ...
└── Exception             // Base para todas las excepciones
    ├── IOException       // Checked exceptions
    ├── SQLException
    ├── ClassNotFoundException
    ├── ...
    └── RuntimeException  // Base para unchecked exceptions
        ├── NullPointerException
        ├── ArithmeticException
        ├── IndexOutOfBoundsException
        └── ...

Excepciones comprobadas (Checked Exceptions)

Las excepciones comprobadas son aquellas que heredan de Exception pero no de RuntimeException. Se caracterizan por:

  • El compilador obliga a manejarlas explícitamente mediante bloques try-catch o a declararlas en la firma del método con throws.
  • Representan condiciones recuperables que una aplicación debería anticipar y de las que podría recuperarse.
  • Suelen estar relacionadas con factores externos al programa, como problemas de E/S, red o bases de datos.
public void leerArchivo(String ruta) throws IOException {
    // Si no declaramos "throws IOException", el código no compilará
    Files.readAllLines(Path.of(ruta));
}

// Alternativa: manejar la excepción con try-catch
public String leerContenidoArchivo(String ruta) {
    try {
        return Files.readString(Path.of(ruta));
    } catch (IOException e) {
        System.err.println("Error al leer el archivo: " + e.getMessage());
        return ""; // Valor por defecto en caso de error
    }
}

Ejemplos comunes de excepciones comprobadas:

  • IOException: Problemas de entrada/salida
  • SQLException: Errores en operaciones de bases de datos
  • ClassNotFoundException: Clase no encontrada al cargar dinámicamente
  • ParseException: Error al analizar una cadena con formato específico

Excepciones no comprobadas (Unchecked Exceptions)

Las excepciones no comprobadas incluyen todas las que heredan de RuntimeException y también las de tipo Error. Sus características son:

  • El compilador no obliga a capturarlas o declararlas.
  • Generalmente indican errores de programación que no deberían ocurrir si el código estuviera correctamente escrito.
  • Suelen ser difíciles o imposibles de recuperar en tiempo de ejecución.
public int dividir(int a, int b) {
    // No es necesario declarar "throws ArithmeticException"
    return a / b; // Lanzará ArithmeticException si b es 0
}

// Aunque no es obligatorio, podemos manejarla si queremos
public int dividirSeguro(int a, int b) {
    try {
        return a / b;
    } catch (ArithmeticException e) {
        System.err.println("Error: División por cero");
        return 0; // Valor por defecto
    }
}

Ejemplos comunes de excepciones no comprobadas:

  • NullPointerException: Intento de acceder a un objeto nulo
  • ArithmeticException: Operaciones aritméticas inválidas como división por cero
  • IndexOutOfBoundsException: Acceso a índices fuera de los límites de un array
  • IllegalArgumentException: Argumentos inválidos en llamadas a métodos
  • ConcurrentModificationException: Modificación de una colección mientras se itera sobre ella

Errores (Error)

Los errores son un tipo especial de excepciones no comprobadas que indican problemas graves a nivel del sistema:

  • Representan situaciones catastróficas de las que normalmente no se puede recuperar la aplicación.
  • No se espera que el código los capture o maneje.
  • Suelen requerir la terminación del programa.
// Ejemplo que provoca StackOverflowError
public void causarStackOverflow() {
    causarStackOverflow(); // Llamada recursiva sin condición de salida
}

// Ejemplo que puede provocar OutOfMemoryError
public void consumirMemoria() {
    List<byte[]> memoria = new ArrayList<>();
    while (true) {
        memoria.add(new byte[1024 * 1024]); // Añadir 1MB continuamente
    }
}

Ejemplos comunes de errores:

  • OutOfMemoryError: La JVM se queda sin memoria
  • StackOverflowError: Desbordamiento de la pila de llamadas
  • NoClassDefFoundError: No se encuentra la definición de una clase
  • ExceptionInInitializerError: Excepción durante la inicialización estática

¿Cuándo usar cada tipo?

La elección entre crear excepciones comprobadas o no comprobadas depende del contexto:

  • Usa excepciones comprobadas cuando:
    • La condición de error es recuperable
    • Quieres forzar al llamador a manejar la situación
    • El error proviene de factores externos que el código no puede controlar
    • Existe una acción alternativa razonable que el llamador podría tomar
public class ArchivoConfiguracion {
    public static Properties cargar(String ruta) throws ConfiguracionException {
        try {
            Properties props = new Properties();
            try (FileInputStream fis = new FileInputStream(ruta)) {
                props.load(fis);
            }
            return props;
        } catch (IOException e) {
            throw new ConfiguracionException("No se pudo cargar la configuración", e);
        }
    }
}

// ConfiguracionException es una excepción comprobada personalizada
public class ConfiguracionException extends Exception {
    public ConfiguracionException(String mensaje, Throwable causa) {
        super(mensaje, causa);
    }
}
  • Usa excepciones no comprobadas cuando:
    • El error indica un bug en el programa
    • La recuperación es improbable o imposible
    • Forzar el manejo sería una carga innecesaria para todos los llamadores
    • El error representa una precondición violada (como argumentos inválidos)
public class CalculadoraImpuestos {
    public double calcularImpuesto(double monto, double tasa) {
        if (monto < 0 || tasa < 0 || tasa > 1) {
            throw new IllegalArgumentException(
                "Monto debe ser positivo y tasa debe estar entre 0 y 1");
        }
        return monto * tasa;
    }
}

Buenas prácticas

  • No atrapar excepciones genéricas: Evita usar catch (Exception e) o catch (Throwable e) sin una buena razón, ya que puede ocultar problemas graves.
// Mal ejemplo - atrapa todo tipo de excepciones
try {
    procesarDatos();
} catch (Exception e) {
    System.out.println("Error: " + e.getMessage());
}

// Mejor enfoque - manejo específico
try {
    procesarDatos();
} catch (IOException e) {
    System.err.println("Error de E/S: " + e.getMessage());
    // Acción específica para errores de E/S
} catch (SQLException e) {
    System.err.println("Error de base de datos: " + e.getMessage());
    // Acción específica para errores de BD
}
  • No ignorar excepciones: Siempre haz algo significativo cuando capturas una excepción, incluso si es solo registrarla.
// Mal ejemplo - excepción ignorada
try {
    archivo.close();
} catch (IOException e) {
    // No hacer nada es peligroso
}

// Mejor enfoque
try {
    archivo.close();
} catch (IOException e) {
    logger.warn("No se pudo cerrar el archivo correctamente", e);
    // Podría no ser crítico, pero al menos lo registramos
}
  • Preservar la causa original: Al lanzar una nueva excepción, incluye la original como causa.
try {
    repositorio.guardarDatos(datos);
} catch (SQLException e) {
    // Convertimos la excepción técnica en una de dominio, pero preservamos la causa
    throw new ErrorPersistenciaException("No se pudieron guardar los datos", e);
}
  • Documentar las excepciones: Usa Javadoc para documentar qué excepciones puede lanzar un método y bajo qué circunstancias.
/**
 * Transfiere fondos entre dos cuentas.
 *
 * @param origen ID de la cuenta origen
 * @param destino ID de la cuenta destino
 * @param monto Cantidad a transferir
 * @throws CuentaInexistenteException si alguna de las cuentas no existe
 * @throws SaldoInsuficienteException si la cuenta origen no tiene fondos suficientes
 * @throws MontoInvalidoException si el monto es negativo o cero
 */
public void transferir(String origen, String destino, double monto) 
    throws CuentaInexistenteException, SaldoInsuficienteException, MontoInvalidoException {
    // Implementación
}

Manejo de excepciones

Cuando ocurre una situación excepcional durante la ejecución de un programa, se necesita un mecanismo para detectar y responder adecuadamente a estos eventos. Java proporciona una estructura clara para gestionar estas situaciones mediante bloques try-catch-finally.

Estructura básica try-catch

La estructura más básica para manejar excepciones en Java se compone de un bloque try seguido de uno o más bloques catch:

try {
    // Código que podría lanzar excepciones
    int resultado = 10 / 0; // Esto lanzará ArithmeticException
} catch (ArithmeticException e) {
    // Código que se ejecuta si ocurre una ArithmeticException
    System.err.println("Error aritmético: " + e.getMessage());
}

El flujo de ejecución es el siguiente:

1. Se ejecuta el código dentro del bloque try 2. Si ocurre una excepción, la ejecución del bloque try se interrumpe inmediatamente 3. Se busca un bloque catch compatible con el tipo de excepción lanzada 4. Si se encuentra, se ejecuta ese bloque catch 5. La ejecución continúa después del último bloque catch

Captura de múltiples excepciones

Es posible manejar diferentes tipos de excepciones con múltiples bloques catch:

try {
    int[] numeros = new int[5];
    numeros[10] = 10 / 0; // Podría lanzar dos tipos de excepciones
} catch (ArithmeticException e) {
    System.err.println("Error en operación matemática: " + e.getMessage());
} catch (ArrayIndexOutOfBoundsException e) {
    System.err.println("Índice fuera de rango: " + e.getMessage());
}

Los bloques catch se evalúan en orden, por lo que es importante colocar las excepciones más específicas antes que las más generales:

try {
    // Código que podría lanzar excepciones
    procesarArchivo("datos.txt");
} catch (FileNotFoundException e) {
    // Manejo específico para archivo no encontrado
    System.err.println("No se encontró el archivo: " + e.getMessage());
} catch (IOException e) {
    // Manejo para otros errores de E/S
    System.err.println("Error de E/S: " + e.getMessage());
} catch (Exception e) {
    // Captura cualquier otra excepción no manejada anteriormente
    System.err.println("Error general: " + e.getMessage());
}

Multi-catch (desde Java 7)

Para reducir la duplicación de código, Java permite capturar múltiples tipos de excepciones en un solo bloque catch:

try {
    // Código que podría lanzar diferentes excepciones
    Object obj = obtenerObjeto();
    String texto = (String) obj;
    int valor = Integer.parseInt(texto);
} catch (ClassCastException | NumberFormatException e) {
    // Este bloque maneja ambos tipos de excepciones
    System.err.println("Error de conversión: " + e.getMessage());
}

Las excepciones unidas con el operador | deben ser disjuntas (ninguna puede ser subclase de otra).

El bloque finally

El bloque finally contiene código que se ejecuta siempre, independientemente de si ocurre una excepción o no:

FileInputStream archivo = null;
try {
    archivo = new FileInputStream("config.txt");
    // Procesar el archivo
} catch (IOException e) {
    System.err.println("Error al procesar el archivo: " + e.getMessage());
} finally {
    // Este código se ejecuta siempre
    if (archivo != null) {
        try {
            archivo.close();
        } catch (IOException e) {
            System.err.println("Error al cerrar el archivo");
        }
    }
}

El bloque finally es ideal para:

  • Liberar recursos (cerrar archivos, conexiones de red, etc.)
  • Realizar operaciones de limpieza
  • Asegurar que ciertas operaciones se completen independientemente de las excepciones

Try-with-resources (desde Java 7)

Para simplificar el manejo de recursos que deben cerrarse, Java introdujo la estructura try-with-resources:

try (FileInputStream archivo = new FileInputStream("datos.txt");
     BufferedReader lector = new BufferedReader(new InputStreamReader(archivo))) {
    // Usar los recursos
    String linea = lector.readLine();
    System.out.println(linea);
} catch (IOException e) {
    System.err.println("Error de E/S: " + e.getMessage());
}
// Los recursos se cierran automáticamente al finalizar el bloque try

Características importantes:

  • Los recursos se cierran automáticamente al finalizar el bloque try
  • Los recursos se cierran en orden inverso a su declaración
  • Se pueden declarar múltiples recursos separados por punto y coma
  • Los recursos deben implementar la interfaz AutoCloseable

Obtención de información de la excepción

Las excepciones en Java proporcionan métodos para obtener información detallada:

try {
    int resultado = 10 / 0;
} catch (ArithmeticException e) {
    // Mensaje de error
    System.err.println("Mensaje: " + e.getMessage());
    
    // Nombre completo de la clase de excepción
    System.err.println("Tipo: " + e.getClass().getName());
    
    // Traza completa de la pila de llamadas
    e.printStackTrace();
    
    // Causa original (si esta excepción fue causada por otra)
    Throwable causa = e.getCause();
    if (causa != null) {
        System.err.println("Causada por: " + causa.getMessage());
    }
}

Excepciones encadenadas

Es común encadenar excepciones para preservar la información de la causa original mientras se proporciona un contexto más específico:

public void guardarDatos(Usuario usuario) throws PersistenciaException {
    try {
        // Intentar guardar en la base de datos
        conexion.ejecutar("INSERT INTO usuarios VALUES (...)");
    } catch (SQLException e) {
        // Convertir la excepción técnica en una de dominio
        throw new PersistenciaException("Error al guardar el usuario: " + usuario.getId(), e);
    }
}

El constructor de la excepción que recibe otra excepción como segundo parámetro establece la causa original, que se puede recuperar posteriormente con getCause().

Patrones comunes de manejo de excepciones

Patrón de reintento

Útil para operaciones que pueden fallar temporalmente, como conexiones de red:

public void conectarServidor() {
    int intentos = 0;
    boolean conectado = false;
    
    while (!conectado && intentos < 3) {
        try {
            // Intentar conexión
            socket = new Socket("servidor.ejemplo.com", 8080);
            conectado = true;
            System.out.println("Conexión establecida");
        } catch (IOException e) {
            intentos++;
            System.out.println("Intento " + intentos + " fallido: " + e.getMessage());
            
            if (intentos < 3) {
                try {
                    // Esperar antes de reintentar
                    Thread.sleep(2000);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    }
    
    if (!conectado) {
        System.err.println("No se pudo establecer conexión después de 3 intentos");
    }
}

Patrón de conversión de excepciones

Convierte excepciones de bajo nivel en excepciones más significativas para la capa de aplicación:

public List<Producto> buscarProductos(String categoria) throws ServicioException {
    try {
        return repositorio.findByCategoria(categoria);
    } catch (SQLException e) {
        throw new ServicioException("Error al buscar productos de categoría: " + categoria, e);
    } catch (TimeoutException e) {
        throw new ServicioException("Tiempo de espera agotado en la búsqueda", e);
    }
}

Patrón de registro y propagación

Registra información sobre la excepción pero permite que se propague hacia arriba:

public void procesarPedido(Pedido pedido) throws ProcesoPedidoException {
    try {
        validarPedido(pedido);
        calcularTotal(pedido);
        aplicarDescuentos(pedido);
        guardarPedido(pedido);
    } catch (ValidacionException e) {
        logger.error("Error de validación en pedido " + pedido.getId(), e);
        throw new ProcesoPedidoException("Pedido inválido", e);
    } catch (CalculoException e) {
        logger.error("Error en cálculos del pedido " + pedido.getId(), e);
        throw new ProcesoPedidoException("Error en cálculos", e);
    }
}

Buenas prácticas en el manejo de excepciones

  • Ser específico con las excepciones: Captura tipos específicos de excepciones en lugar de usar Exception genérico.
// Enfoque incorrecto
try {
    // Código
} catch (Exception e) { // Demasiado genérico
    System.err.println("Error");
}

// Enfoque correcto
try {
    // Código
} catch (IOException e) {
    System.err.println("Error de E/S: " + e.getMessage());
} catch (SQLException e) {
    System.err.println("Error de base de datos: " + e.getMessage());
}
  • No ignorar excepciones: Siempre haz algo significativo cuando capturas una excepción.
// Incorrecto
try {
    conexion.close();
} catch (SQLException e) {
    // Vacío - ¡Peligroso!
}

// Correcto
try {
    conexion.close();
} catch (SQLException e) {
    logger.warn("No se pudo cerrar la conexión correctamente", e);
}
  • Usar try-with-resources para recursos que deben cerrarse.
// Antiguo enfoque (propenso a errores)
InputStream entrada = null;
try {
    entrada = new FileInputStream("archivo.txt");
    // Usar entrada
} catch (IOException e) {
    // Manejar excepción
} finally {
    if (entrada != null) {
        try {
            entrada.close();
        } catch (IOException e) {
            // ¿Qué hacer aquí?
        }
    }
}

// Enfoque moderno con try-with-resources
try (InputStream entrada = new FileInputStream("archivo.txt")) {
    // Usar entrada
} catch (IOException e) {
    // Manejar excepción
}
  • Mantener el ámbito del try lo más pequeño posible: Incluye solo el código que realmente puede lanzar la excepción.
// Incorrecto - ámbito demasiado amplio
try {
    String datos = obtenerDatos(); // Podría lanzar IOException
    procesarDatos(datos);          // No lanza excepciones
    guardarResultados(datos);      // Podría lanzar SQLException
} catch (Exception e) {
    // Difícil saber qué falló exactamente
}

// Correcto - ámbitos específicos
String datos;
try {
    datos = obtenerDatos();
} catch (IOException e) {
    System.err.println("Error al obtener datos: " + e.getMessage());
    return;
}

procesarDatos(datos);

try {
    guardarResultados(datos);
} catch (SQLException e) {
    System.err.println("Error al guardar resultados: " + e.getMessage());
}
  • Evitar anti-patrones comunes:
    • No usar excepciones para controlar el flujo normal del programa
    • No capturar Throwable (excepto en casos muy específicos)
    • No crear excepciones sin mensaje informativo
// Anti-patrón: usar excepciones para control de flujo
public boolean existeArchivo(String ruta) {
    try {
        new FileInputStream(ruta).close();
        return true;
    } catch (IOException e) {
        return false;
    }
}

// Mejor enfoque
public boolean existeArchivo(String ruta) {
    return Files.exists(Path.of(ruta));
}

Lanzamiento de excepciones

El lanzamiento de excepciones permite señalar condiciones de error o situaciones excepcionales durante la ejecución de un programa. Cuando se detecta una situación anómala que no puede ser manejada en el contexto actual, se puede lanzar una excepción para transferir el control a un código capaz de manejarla adecuadamente.

En Java, se utiliza la palabra clave throw para lanzar explícitamente una excepción. Este mecanismo permite interrumpir el flujo normal de ejecución y propagar información sobre el error ocurrido.

Sintaxis básica para lanzar excepciones

La sintaxis para lanzar una excepción es sencilla:

throw new TipoExcepcion("Mensaje descriptivo del error");

Por ejemplo, para lanzar una excepción cuando un argumento no cumple con ciertos requisitos:

public void transferir(double monto, Cuenta destino) {
    if (monto <= 0) {
        throw new IllegalArgumentException("El monto debe ser positivo");
    }
    
    if (destino == null) {
        throw new NullPointerException("La cuenta destino no puede ser nula");
    }
    
    // Continuar con la lógica de transferencia
}

Declaración de excepciones con throws

Cuando un método puede lanzar excepciones comprobadas (checked), es obligatorio declararlas en la firma del método usando la cláusula throws:

public void leerArchivo(String ruta) throws IOException {
    if (!Files.exists(Path.of(ruta))) {
        throw new FileNotFoundException("El archivo no existe: " + ruta);
    }
    
    // Código para leer el archivo
}

Para excepciones no comprobadas (unchecked), la declaración con throws es opcional, pero puede ser útil como documentación:

public int dividir(int a, int b) throws ArithmeticException {
    if (b == 0) {
        throw new ArithmeticException("División por cero no permitida");
    }
    return a / b;
}

Cuándo lanzar excepciones

Las excepciones deben lanzarse en situaciones excepcionales, no como parte del flujo normal del programa. Algunos casos apropiados para lanzar excepciones son:

  • Validación de precondiciones: Cuando los parámetros de entrada no cumplen con los requisitos necesarios.
public void registrarUsuario(String nombre, String email, int edad) {
    if (nombre == null || nombre.isBlank()) {
        throw new IllegalArgumentException("El nombre no puede estar vacío");
    }
    
    if (email == null || !email.contains("@")) {
        throw new IllegalArgumentException("Email inválido: " + email);
    }
    
    if (edad < 18) {
        throw new IllegalArgumentException("El usuario debe ser mayor de edad");
    }
    
    // Continuar con el registro
}
  • Estados inconsistentes: Cuando el objeto se encuentra en un estado que no permite realizar la operación solicitada.
public class CuentaBancaria {
    private double saldo;
    private boolean bloqueada;
    
    public void retirar(double monto) {
        if (bloqueada) {
            throw new IllegalStateException("No se puede operar con una cuenta bloqueada");
        }
        
        if (monto > saldo) {
            throw new SaldoInsuficienteException("Saldo insuficiente para retirar " + monto);
        }
        
        saldo -= monto;
    }
}
  • Errores de operación: Cuando una operación no puede completarse debido a factores externos.
public void conectarServidor(String host, int puerto) throws ConexionException {
    try {
        Socket socket = new Socket(host, puerto);
        // Configurar la conexión
    } catch (IOException e) {
        throw new ConexionException("No se pudo establecer conexión con " + host + ":" + puerto, e);
    }
}

Relanzamiento de excepciones

En ocasiones, es necesario capturar una excepción para realizar alguna acción (como registro o limpieza) y luego relanzarla para que sea manejada en un nivel superior:

public void procesarDatos() throws IOException {
    try {
        // Código que puede lanzar IOException
        leerArchivo("datos.txt");
    } catch (IOException e) {
        // Registrar el error
        logger.error("Error al procesar datos", e);
        
        // Relanzar la misma excepción
        throw e;
    }
}

También se puede transformar la excepción en otra más específica o más adecuada para el contexto:

public void cargarConfiguracion() throws ConfiguracionException {
    try {
        Properties props = new Properties();
        props.load(new FileInputStream("config.properties"));
        // Procesar propiedades
    } catch (IOException e) {
        // Convertir la excepción técnica en una de dominio
        throw new ConfiguracionException("Error al cargar archivo de configuración", e);
    }
}

Enriquecimiento de excepciones

Al lanzar excepciones, es importante proporcionar información contextual que ayude a diagnosticar y resolver el problema:

public Usuario buscarPorId(int id) throws UsuarioException {
    try {
        return repositorio.findById(id);
    } catch (SQLException e) {
        throw new UsuarioException(
            String.format("Error al buscar usuario con ID %d en la base de datos %s", 
                         id, repositorio.getNombreBaseDatos()),
            e
        );
    }
}

Un buen mensaje de excepción debe incluir:

  • Qué ocurrió
  • Dónde ocurrió
  • Por qué ocurrió (si es posible)
  • Valores relevantes que ayuden a diagnosticar el problema

Excepciones personalizadas con información adicional

Se pueden crear excepciones personalizadas que contengan información adicional relevante para el problema:

public class ValidacionException extends Exception {
    private final String campo;
    private final Object valorRechazado;
    
    public ValidacionException(String mensaje, String campo, Object valorRechazado) {
        super(mensaje);
        this.campo = campo;
        this.valorRechazado = valorRechazado;
    }
    
    public String getCampo() {
        return campo;
    }
    
    public Object getValorRechazado() {
        return valorRechazado;
    }
}

// Uso
public void validarProducto(Producto producto) throws ValidacionException {
    if (producto.getPrecio() < 0) {
        throw new ValidacionException(
            "El precio no puede ser negativo",
            "precio",
            producto.getPrecio()
        );
    }
}

Patrones comunes para lanzar excepciones

  • Validación temprana: Validar todos los parámetros al inicio del método antes de realizar cualquier operación.
public void procesarPedido(Pedido pedido, Cliente cliente) {
    // Validación temprana
    if (pedido == null) {
        throw new IllegalArgumentException("El pedido no puede ser nulo");
    }
    if (cliente == null) {
        throw new IllegalArgumentException("El cliente no puede ser nulo");
    }
    if (pedido.getItems().isEmpty()) {
        throw new PedidoVacioException("El pedido no contiene ítems");
    }
    
    // Continuar con el procesamiento
}
  • Excepciones específicas para el dominio: Crear jerarquías de excepciones que reflejen el modelo de dominio de la aplicación.
// Jerarquía de excepciones
public class NegocioException extends Exception { /* ... */ }
public class ClienteException extends NegocioException { /* ... */ }
public class ProductoException extends NegocioException { /* ... */ }
public class InventarioException extends ProductoException { /* ... */ }

// Uso
public void venderProducto(String codigoProducto, int cantidad) 
        throws ProductoException, InventarioException, ClienteException {
    
    if (!existeProducto(codigoProducto)) {
        throw new ProductoException("Producto no encontrado: " + codigoProducto);
    }
    
    if (getStock(codigoProducto) < cantidad) {
        throw new InventarioException("Stock insuficiente para " + codigoProducto);
    }
    
    // Continuar con la venta
}
  • Excepciones con códigos de error: Incluir códigos de error estandarizados para facilitar el manejo automatizado.
public class APIException extends Exception {
    private final String codigo;
    
    public APIException(String codigo, String mensaje) {
        super(mensaje);
        this.codigo = codigo;
    }
    
    public String getCodigo() {
        return codigo;
    }
}

// Uso
if (respuesta.getEstado() != 200) {
    throw new APIException(
        "API-" + respuesta.getEstado(),
        "Error en llamada a API: " + respuesta.getMensajeError()
    );
}

Creación y lanzamiento y captura de excepciones propias

Cuando las excepciones estándar no describen adecuadamente los problemas específicos de nuestro dominio, crear nuestras propias clases de excepción permite comunicar errores de manera más precisa y significativa.

Fundamentos de excepciones personalizadas

Las excepciones personalizadas son clases que extienden de alguna clase base de excepción. Dependiendo de nuestras necesidades, podemos crear:

  • Excepciones comprobadas: Extendiendo de Exception o alguna de sus subclases
  • Excepciones no comprobadas: Extendiendo de RuntimeException o alguna de sus subclases
// Excepción comprobada personalizada
public class ConfiguracionException extends Exception {
    public ConfiguracionException(String mensaje) {
        super(mensaje);
    }
}

// Excepción no comprobada personalizada
public class UsuarioNoAutorizadoException extends RuntimeException {
    public UsuarioNoAutorizadoException(String mensaje) {
        super(mensaje);
    }
}

Estructura de una excepción personalizada

Una excepción personalizada bien diseñada suele incluir:

  • Constructores adecuados para diferentes escenarios
  • Campos adicionales que proporcionen contexto específico
  • Métodos de acceso para obtener información detallada del error
public class ValidacionException extends Exception {
    private final String campo;
    private final String codigoError;
    
    // Constructor básico
    public ValidacionException(String mensaje) {
        super(mensaje);
        this.campo = null;
        this.codigoError = "VALIDACION_ERROR";
    }
    
    // Constructor con campo específico
    public ValidacionException(String mensaje, String campo) {
        super(mensaje);
        this.campo = campo;
        this.codigoError = "VALIDACION_ERROR";
    }
    
    // Constructor con causa original
    public ValidacionException(String mensaje, String campo, Throwable causa) {
        super(mensaje, causa);
        this.campo = campo;
        this.codigoError = "VALIDACION_ERROR";
    }
    
    // Constructor completo
    public ValidacionException(String mensaje, String campo, String codigoError, Throwable causa) {
        super(mensaje, causa);
        this.campo = campo;
        this.codigoError = codigoError;
    }
    
    // Métodos de acceso
    public String getCampo() {
        return campo;
    }
    
    public String getCodigoError() {
        return codigoError;
    }
    
    // Método para generar mensaje detallado
    @Override
    public String toString() {
        return "ValidacionException [código=" + codigoError + 
               ", campo=" + campo + ", mensaje=" + getMessage() + "]";
    }
}

Jerarquías de excepciones personalizadas

Para aplicaciones complejas, es recomendable crear una jerarquía de excepciones que refleje la estructura del dominio:

// Excepción base para toda la aplicación
public abstract class AplicacionException extends Exception {
    public AplicacionException(String mensaje) {
        super(mensaje);
    }
    
    public AplicacionException(String mensaje, Throwable causa) {
        super(mensaje, causa);
    }
}

// Excepciones específicas por módulo
public class SeguridadException extends AplicacionException {
    public SeguridadException(String mensaje) {
        super(mensaje);
    }
}

public class PersistenciaException extends AplicacionException {
    public PersistenciaException(String mensaje, Throwable causa) {
        super(mensaje, causa);
    }
}

// Excepciones aún más específicas
public class AutenticacionException extends SeguridadException {
    private final String usuario;
    
    public AutenticacionException(String mensaje, String usuario) {
        super(mensaje);
        this.usuario = usuario;
    }
    
    public String getUsuario() {
        return usuario;
    }
}

Esta estructura jerárquica permite capturar excepciones a diferentes niveles de granularidad según sea necesario.

Lanzamiento de excepciones personalizadas

El lanzamiento de excepciones personalizadas sigue el mismo patrón que las excepciones estándar:

public void validarEdad(int edad) throws ValidacionException {
    if (edad < 0) {
        throw new ValidacionException("La edad no puede ser negativa", "edad");
    }
    
    if (edad > 120) {
        throw new ValidacionException("La edad parece no ser válida", "edad", "EDAD_INVALIDA", null);
    }
}

Para excepciones que encapsulan otras, es importante preservar la causa original:

public void guardarUsuario(Usuario usuario) throws PersistenciaException {
    try {
        // Intentar guardar en base de datos
        repositorio.save(usuario);
    } catch (SQLException e) {
        // Convertir la excepción técnica en una de dominio
        throw new PersistenciaException(
            "Error al guardar el usuario: " + usuario.getNombre(), e);
    }
}

Captura y manejo de excepciones personalizadas

La captura de excepciones personalizadas se realiza igual que con las excepciones estándar:

try {
    servicio.procesarSolicitud(solicitud);
} catch (ValidacionException e) {
    System.err.println("Error de validación en el campo: " + e.getCampo());
    System.err.println("Código de error: " + e.getCodigoError());
    System.err.println("Mensaje: " + e.getMessage());
} catch (AutenticacionException e) {
    System.err.println("Error de autenticación para el usuario: " + e.getUsuario());
    System.err.println("Mensaje: " + e.getMessage());
} catch (AplicacionException e) {
    // Captura cualquier otra excepción de la aplicación
    System.err.println("Error general de la aplicación: " + e.getMessage());
}

La ventaja de las excepciones personalizadas es que podemos extraer información específica del contexto para manejar el error de manera más precisa.

Ejemplo práctico: Sistema de gestión bancaria

Veamos un ejemplo completo de creación, lanzamiento y captura de excepciones personalizadas en un sistema bancario:

// Jerarquía de excepciones
public class BancoException extends Exception {
    public BancoException(String mensaje) {
        super(mensaje);
    }
    
    public BancoException(String mensaje, Throwable causa) {
        super(mensaje, causa);
    }
}

public class CuentaException extends BancoException {
    private final String numeroCuenta;
    
    public CuentaException(String mensaje, String numeroCuenta) {
        super(mensaje);
        this.numeroCuenta = numeroCuenta;
    }
    
    public String getNumeroCuenta() {
        return numeroCuenta;
    }
}

public class SaldoInsuficienteException extends CuentaException {
    private final double saldoActual;
    private final double montoSolicitado;
    
    public SaldoInsuficienteException(String numeroCuenta, double saldoActual, double montoSolicitado) {
        super("Saldo insuficiente en la cuenta", numeroCuenta);
        this.saldoActual = saldoActual;
        this.montoSolicitado = montoSolicitado;
    }
    
    public double getSaldoActual() {
        return saldoActual;
    }
    
    public double getMontoSolicitado() {
        return montoSolicitado;
    }
    
    public double getSaldoFaltante() {
        return montoSolicitado - saldoActual;
    }
}

public class CuentaBloqueadaException extends CuentaException {
    private final String motivo;
    
    public CuentaBloqueadaException(String numeroCuenta, String motivo) {
        super("La cuenta está bloqueada", numeroCuenta);
        this.motivo = motivo;
    }
    
    public String getMotivo() {
        return motivo;
    }
}

Ahora, implementemos la clase CuentaBancaria que utiliza estas excepciones:

public class CuentaBancaria {
    private String numeroCuenta;
    private double saldo;
    private boolean bloqueada;
    private String motivoBloqueo;
    
    // Constructor y otros métodos...
    
    public void retirar(double monto) throws CuentaException {
        // Validar que la cuenta no esté bloqueada
        if (bloqueada) {
            throw new CuentaBloqueadaException(numeroCuenta, motivoBloqueo);
        }
        
        // Validar que el monto sea positivo
        if (monto <= 0) {
            throw new CuentaException("El monto a retirar debe ser positivo", numeroCuenta);
        }
        
        // Validar que haya saldo suficiente
        if (saldo < monto) {
            throw new SaldoInsuficienteException(numeroCuenta, saldo, monto);
        }
        
        // Realizar el retiro
        saldo -= monto;
        System.out.println("Retiro exitoso. Nuevo saldo: " + saldo);
    }
    
    public void transferir(CuentaBancaria destino, double monto) throws CuentaException {
        try {
            // Intentar retirar de esta cuenta
            this.retirar(monto);
            
            // Depositar en la cuenta destino
            try {
                destino.depositar(monto);
            } catch (Exception e) {
                // Si falla el depósito, revertir el retiro
                this.depositar(monto);
                throw new CuentaException(
                    "Error al depositar en cuenta destino. Transferencia cancelada.", 
                    numeroCuenta);
            }
            
            System.out.println("Transferencia exitosa de " + monto + 
                               " desde " + this.numeroCuenta + 
                               " hacia " + destino.numeroCuenta);
                               
        } catch (SaldoInsuficienteException e) {
            throw new CuentaException(
                "Fondos insuficientes para realizar la transferencia. " +
                "Saldo actual: " + e.getSaldoActual() + 
                ", Monto solicitado: " + e.getMontoSolicitado(),
                numeroCuenta);
        }
    }
    
    public void depositar(double monto) throws CuentaException {
        if (bloqueada) {
            throw new CuentaBloqueadaException(numeroCuenta, motivoBloqueo);
        }
        
        if (monto <= 0) {
            throw new CuentaException("El monto a depositar debe ser positivo", numeroCuenta);
        }
        
        saldo += monto;
        System.out.println("Depósito exitoso. Nuevo saldo: " + saldo);
    }
}

Finalmente, veamos cómo se utilizarían estas excepciones en un cliente:

public class SistemaBancario {
    public static void main(String[] args) {
        CuentaBancaria cuenta1 = new CuentaBancaria("123456", 1000.0, false, null);
        CuentaBancaria cuenta2 = new CuentaBancaria("789012", 500.0, false, null);
        CuentaBancaria cuenta3 = new CuentaBancaria("345678", 200.0, true, "Verificación pendiente");
        
        // Caso 1: Retiro exitoso
        try {
            cuenta1.retirar(500.0);
        } catch (CuentaException e) {
            System.err.println("Error: " + e.getMessage());
        }
        
        // Caso 2: Saldo insuficiente
        try {
            cuenta2.retirar(1000.0);
        } catch (SaldoInsuficienteException e) {
            System.err.println("Error en cuenta " + e.getNumeroCuenta() + ": " + e.getMessage());
            System.err.println("Saldo actual: " + e.getSaldoActual());
            System.err.println("Monto solicitado: " + e.getMontoSolicitado());
            System.err.println("Saldo faltante: " + e.getSaldoFaltante());
        } catch (CuentaException e) {
            System.err.println("Error general: " + e.getMessage());
        }
        
        // Caso 3: Cuenta bloqueada
        try {
            cuenta3.depositar(100.0);
        } catch (CuentaBloqueadaException e) {
            System.err.println("Error en cuenta " + e.getNumeroCuenta() + ": " + e.getMessage());
            System.err.println("Motivo de bloqueo: " + e.getMotivo());
        } catch (CuentaException e) {
            System.err.println("Error general: " + e.getMessage());
        }
        
        // Caso 4: Transferencia
        try {
            cuenta1.transferir(cuenta2, 200.0);
        } catch (CuentaException e) {
            System.err.println("Error en transferencia: " + e.getMessage());
        }
    }
}

Patrones avanzados con excepciones personalizadas

Excepciones con información de recuperación

Podemos diseñar excepciones que no solo informen del error, sino que también sugieran cómo recuperarse:

public class LimiteExcedidoException extends BancoException {
    private final double limiteActual;
    private final double limiteRecomendado;
    
    public LimiteExcedidoException(double actual, double recomendado) {
        super("Límite de operaciones excedido");
        this.limiteActual = actual;
        this.limiteRecomendado = recomendado;
    }
    
    public double getLimiteActual() {
        return limiteActual;
    }
    
    public double getLimiteRecomendado() {
        return limiteRecomendado;
    }
    
    public boolean puedeAumentarLimite() {
        return limiteRecomendado > limiteActual;
    }
}

// Uso
try {
    realizarOperacion(monto);
} catch (LimiteExcedidoException e) {
    if (e.puedeAumentarLimite()) {
        System.out.println("¿Desea aumentar su límite a " + e.getLimiteRecomendado() + "?");
        // Lógica para aumentar el límite
    } else {
        System.out.println("Ha alcanzado el límite máximo permitido.");
    }
}

Excepciones con múltiples errores

Para validaciones que pueden generar múltiples errores a la vez:

public class ValidacionMultipleException extends BancoException {
    private final List<String> errores;
    
    public ValidacionMultipleException(List<String> errores) {
        super("Se encontraron múltiples errores de validación");
        this.errores = new ArrayList<>(errores);
    }
    
    public List<String> getErrores() {
        return Collections.unmodifiableList(errores);
    }
    
    public int getCantidadErrores() {
        return errores.size();
    }
}

// Uso
public void validarFormulario(Formulario form) throws ValidacionMultipleException {
    List<String> errores = new ArrayList<>();
    
    if (form.getNombre() == null || form.getNombre().isBlank()) {
        errores.add("El nombre es obligatorio");
    }
    
    if (form.getEmail() == null || !form.getEmail().contains("@")) {
        errores.add("El email no es válido");
    }
    
    if (form.getEdad() < 18) {
        errores.add("Debe ser mayor de edad");
    }
    
    if (!errores.isEmpty()) {
        throw new ValidacionMultipleException(errores);
    }
}

// Captura
try {
    validarFormulario(formulario);
} catch (ValidacionMultipleException e) {
    System.err.println("Se encontraron " + e.getCantidadErrores() + " errores:");
    for (String error : e.getErrores()) {
        System.err.println("- " + error);
    }
}

Buenas prácticas para excepciones personalizadas

  • Nombres descriptivos: El nombre de la excepción debe describir claramente el problema (SaldoInsuficienteException en lugar de CuentaException).
  • Mensajes informativos: Incluye detalles específicos en los mensajes de error.
// Poco informativo
throw new PagoException("Error en el pago");

// Informativo
throw new PagoException("Error en el pago con tarjeta terminada en 4321: Fondos insuficientes");
  • Serialización: Las excepciones personalizadas deben ser serializables para funcionar correctamente en entornos distribuidos.
public class MiExcepcion extends Exception implements Serializable {
    private static final long serialVersionUID = 1L;
    
    // Resto de la implementación...
}
  • Documentación clara: Documenta tus excepciones personalizadas con Javadoc para facilitar su uso.
/**
 * Excepción lanzada cuando una operación excede el límite diario establecido.
 * 
 * <p>Esta excepción contiene información sobre el límite actual y el monto
 * que se intentó operar, permitiendo a los manejadores mostrar información
 * precisa al usuario.</p>
 */
public class LimiteDiarioExcedidoException extends OperacionException {
    // Implementación...
}
  • Inmutabilidad: Diseña tus excepciones como objetos inmutables para evitar comportamientos inesperados.
public final class DatoInvalidoException extends Exception {
    private final String campo;
    private final String valor;
    
    public DatoInvalidoException(String campo, String valor, String mensaje) {
        super(mensaje);
        this.campo = campo;
        this.valor = valor;
    }
    
    // Solo getters, sin setters
    public String getCampo() {
        return campo;
    }
    
    public String getValor() {
        return valor;
    }
}

Consideraciones de rendimiento

Las excepciones personalizadas, como cualquier excepción, tienen un coste de rendimiento asociado. Algunas consideraciones importantes:

  • Creación de la pila de llamadas: La construcción de la pila de llamadas (stack trace) es costosa.
  • Uso adecuado: Utiliza excepciones para situaciones realmente excepcionales, no para control de flujo normal.
// Ineficiente: usar excepciones para control de flujo
public boolean existeUsuario(String id) {
    try {
        buscarUsuarioPorId(id);
        return true;
    } catch (UsuarioNoEncontradoException e) {
        return false;
    }
}

// Eficiente: método específico para verificación
public boolean existeUsuario(String id) {
    return repositorioUsuarios.existsById(id);
}
  • Optimización de stack trace: Para excepciones frecuentes en código crítico, se puede considerar omitir la pila de llamadas.
public class ExcepcionOptimizada extends RuntimeException {
    public ExcepcionOptimizada(String mensaje) {
        super(mensaje, null, true, false); // Suprime la pila de llamadas
    }
}

CONSTRUYE TU CARRERA EN IA Y PROGRAMACIÓN SOFTWARE

Accede a +1000 lecciones y cursos con certificado. Mejora tu portfolio con certificados de superación para tu CV.

30 % DE DESCUENTO

Plan mensual

19.00 /mes

13.30 € /mes

Precio normal mensual: 19 €
63 % DE DESCUENTO

Plan anual

10.00 /mes

7.00 € /mes

Ahorras 144 € al año
Precio normal anual: 120 €
Aprende Java online

Ejercicios de esta lección Excepciones

Evalúa tus conocimientos de esta lección Excepciones con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Clases abstractas

Test

Streams: reduce()

Test

Streams: flatMap()

Test

Llamada y sobrecarga de funciones

Puzzle

Métodos referenciados

Test

Métodos de la clase String

Código

Representación de Fecha

Puzzle

Operadores lógicos

Test

Tipos de datos

Código

Estructuras de iteración

Puzzle

Streams: forEach()

Test

Objetos

Puzzle

Funciones lambda

Test

Uso de Scanner

Puzzle

CRUD en Java de modelo Customer sobre un ArrayList

Proyecto

Tipos de variables

Puzzle

Streams: collect()

Puzzle

Operadores aritméticos

Puzzle

Interfaz funcional Consumer

Test

API java.nio 2

Puzzle

API Optional

Test

Interfaz funcional Function

Test

Encapsulación

Test

Interfaces

Código

Uso de API Optional

Puzzle

Representación de Hora

Test

Herencia básica

Test

Clases y objetos

Código

Interfaz funcional Supplier

Puzzle

HashMap

Puzzle

Sobrecarga de métodos

Test

Polimorfismo de tiempo de ejecución

Puzzle

OOP en Java

Proyecto

Creación de Streams

Test

Streams: min max

Puzzle

Métodos avanzados de la clase String

Puzzle

Polimorfismo de tiempo de compilación

Test

Excepciones

Puzzle

Herencia avanzada

Puzzle

Estructuras de selección

Test

Uso de interfaces

Test

HashSet

Test

Objeto Scanner

Test

Streams: filter()

Puzzle

Operaciones de Streams

Puzzle

Interfaz funcional Predicate

Puzzle

Streams: sorted()

Test

Configuración de entorno

Test

CRUD en Java de modelo Customer sobre un HashMap

Proyecto

Uso de variables

Test

Clases

Test

Streams: distinct()

Puzzle

Streams: count()

Test

ArrayList

Test

Datos de referencia

Test

Interfaces funcionales

Puzzle

Métodos básicos de la clase String

Test

Instalación

Test

Funciones

Código

Estructuras de control

Código

Herencia de clases

Código

Streams: map()

Puzzle

Funciones y encapsulamiento

Test

Streams: match

Test

Gestión de errores y excepciones

Código

Datos primitivos

Puzzle

Todas las lecciones de Java

Accede a todas las lecciones de Java y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Instalación De Java

Introducción Y Entorno

Configuración De Entorno Java

Introducción Y Entorno

Ecosistema Jakarta Ee De Java

Introducción Y Entorno

Tipos De Datos

Sintaxis

Variables

Sintaxis

Operadores

Sintaxis

Estructuras De Control

Sintaxis

Funciones

Sintaxis

Excepciones

Programación Orientada A Objetos

Clases Y Objetos

Programación Orientada A Objetos

Encapsulación

Programación Orientada A Objetos

Herencia

Programación Orientada A Objetos

Clases Abstractas

Programación Orientada A Objetos

Interfaces

Programación Orientada A Objetos

Sobrecarga De Métodos

Programación Orientada A Objetos

Polimorfismo

Programación Orientada A Objetos

La Clase Scanner

Programación Orientada A Objetos

Métodos De La Clase String

Programación Orientada A Objetos

Listas

Framework Collections

Conjuntos

Framework Collections

Mapas

Framework Collections

Funciones Lambda

Programación Funcional

Interfaz Funcional Consumer

Programación Funcional

Interfaz Funcional Predicate

Programación Funcional

Interfaz Funcional Supplier

Programación Funcional

Interfaz Funcional Function

Programación Funcional

Métodos Referenciados

Programación Funcional

Creación De Streams

Programación Funcional

Operaciones Intermedias Con Streams: Map()

Programación Funcional

Operaciones Intermedias Con Streams: Filter()

Programación Funcional

Operaciones Intermedias Con Streams: Distinct()

Programación Funcional

Operaciones Finales Con Streams: Collect()

Programación Funcional

Operaciones Finales Con Streams: Min Max

Programación Funcional

Operaciones Intermedias Con Streams: Flatmap()

Programación Funcional

Operaciones Intermedias Con Streams: Sorted()

Programación Funcional

Operaciones Finales Con Streams: Reduce()

Programación Funcional

Operaciones Finales Con Streams: Foreach()

Programación Funcional

Operaciones Finales Con Streams: Count()

Programación Funcional

Operaciones Finales Con Streams: Match

Programación Funcional

Api Optional

Programación Funcional

Api Java.nio 2

Entrada Y Salida (Io)

Api Java.time

Api Java.time

Accede GRATIS a Java y certifícate

Certificados de superación de Java

Supera todos los ejercicios de programación del curso de Java y obtén certificados de superación para mejorar tu currículum y tu empleabilidad.

En esta lección

Objetivos de aprendizaje de esta lección

  • Entender qué son las excepciones y por qué se utilizan en Java
  • Aprender sobre los diferentes tipos de excepciones en Java: checked exceptions, unchecked exceptions y errors
  • Familiarizarse con el manejo de excepciones mediante el uso de bloques try/catch/finally
  • Aprender cómo lanzar excepciones de forma explícita utilizando la palabra clave throw
  • Conocer cómo se pueden crear excepciones personalizadas al extender la clase Exception
  • Entender la importancia del manejo de excepciones en la creación de software robusto y de alta calidad