Excepciones - parte 2

Intermedio
Java
Java
Actualizado: 27/04/2026

try catch finally con recursos

flowchart LR
    Try[try] --> Code[Código que puede fallar]
    Code --> Ok[Éxito]
    Code --> Err[Excepción lanzada]
    Err --> Cat1[catch IOException]
    Err --> Cat2[catch SQLException]
    Err --> Multi[catch IO o SQL multi]
    Cat1 --> Fin[finally siempre]
    Cat2 --> Fin
    Ok --> Fin
    Try --> Twr[try with resources cierra AutoCloseable]
    Cat1 --> Re[Re lanzar wrap]

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
    }
}
Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Java es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Java

Explora más contenido relacionado con Java y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

  • Aprender cómo lanzar excepciones de forma explícita utilizando la palabra clave throw.
  • Declarar excepciones comprobadas en la firma del método con throws.
  • Conocer cómo se pueden crear excepciones personalizadas al extender la clase Exception.
  • Diseñar jerarquías de excepciones que reflejen el dominio de la aplicación.
  • Aplicar patrones avanzados con excepciones: múltiples errores, información de recuperación e inmutabilidad.
  • Entender la importancia del manejo de excepciones en la creación de software robusto y de alta calidad.

Cursos que incluyen esta lección

Esta lección forma parte de los siguientes cursos estructurados con rutas de aprendizaje