3.dh.1 - Excepciones

Objetivos

1. Contenidos

1.1 ¿Qué es una excepción?

Una excepción es un evento inesperado o un error que se produce durante la ejecución de un programa y que interrumpe el flujo normal de las instrucciones.

En una aplicación profesional, las excepciones aparecen cuando ocurre una situación que el programa no puede resolver siguiendo el camino normal: un fichero que no existe, un dato mal escrito por el usuario, una división entre cero, una conexión que falla o un valor nulo utilizado de forma incorrecta.

Idea clave: una excepción no siempre significa que el programa esté mal diseñado. Lo importante es que el programa esté preparado para detectar la situación, controlarla y responder de forma adecuada.

Las excepciones tienen tres características principales:

  • Evento anómalo: representan una situación no prevista en el flujo normal del programa.
  • Interrupción de la ejecución: si no se controlan, detienen el procesamiento secuencial.
  • Posibilidad de manejo: Java proporciona mecanismos para capturarlas y decidir qué hacer.

1.2 ¿Qué ocurre si no controlamos una excepción?

Cuando una excepción no se controla, Java detiene el programa y muestra un mensaje de error técnico. Esto puede ser útil para el programador durante el desarrollo, pero no es aceptable en una aplicación real.

Ejemplo: reparto de unidades de trabajo entre operarios.


public class DivisionSinControl {
	public static void main(String[] args) {
		Integer unidades = 120;
		Integer operarios = 0;

		Integer unidadesPorOperario = unidades / operarios;

		System.out.println("Unidades por operario: " + unidadesPorOperario);
	}
}
				

Este programa compila correctamente, pero al ejecutarse se produce una ArithmeticException, porque no se puede dividir entre cero.

Diferencia importante: un error de compilación impide generar el programa. Una excepción ocurre durante la ejecución, cuando el programa ya está funcionando.

1.3 Control de excepciones con try-catch

La estructura básica para controlar excepciones en Java es try-catch.

  • En el bloque try se escribe el código que puede producir una excepción.
  • En el bloque catch se indica qué debe hacer el programa si se produce esa excepción.

Sintaxis básica:


try {
	// Código que puede producir una excepción
} catch (TipoDeExcepcion excepcion) {
	// Código que se ejecuta si ocurre esa excepción
}
				

Ejemplo controlado: reparto de unidades con validación de error.


public class DivisionControlada {
	public static void main(String[] args) {
		Integer unidades = 120;
		Integer operarios = 0;

		try {
			Integer unidadesPorOperario = unidades / operarios;
			System.out.println("Unidades por operario: " + unidadesPorOperario);
		} catch (ArithmeticException excepcion) {
			System.out.println("No se puede calcular el reparto porque no hay operarios disponibles.");
		}

		System.out.println("El programa continúa funcionando.");
	}
}
				

En este caso, el programa no finaliza bruscamente. Captura la excepción, muestra un mensaje comprensible y continúa su ejecución.

1.4 Capturar información de la excepción

El objeto recibido en el catch contiene información sobre lo que ha ocurrido. Algunos métodos útiles son:

Método Utilidad
getMessage() Devuelve un mensaje breve con la causa del problema.
printStackTrace() Muestra la traza completa del error. Es útil durante el desarrollo, pero no debe mostrarse al usuario final.

public class InformacionExcepcion {
	public static void main(String[] args) {
		String textoCantidad = "veinte";

		try {
			Integer cantidad = Integer.parseInt(textoCantidad);
			System.out.println("Cantidad registrada: " + cantidad);
		} catch (NumberFormatException excepcion) {
			System.out.println("El dato introducido no es un número válido.");
			System.out.println("Detalle técnico: " + excepcion.getMessage());
		}
	}
}
				

Buena práctica: el usuario debe ver mensajes claros y orientados a la solución. Los detalles técnicos deben reservarse para el programador o para un sistema de registro de errores.

1.5 Varios bloques catch

Un mismo bloque try puede producir distintos tipos de excepciones. Para cada una podemos escribir un bloque catch diferente.


public class RegistroPedido {
	public static void main(String[] args) {
		String codigoProducto = "A-100";
		String textoUnidades = "cinco";
		Integer unidadesEnAlmacen = 20;

		try {
			Integer unidadesSolicitadas = Integer.parseInt(textoUnidades);
			Integer paquetes = unidadesEnAlmacen / unidadesSolicitadas;

			System.out.println("Producto: " + codigoProducto);
			System.out.println("Paquetes que se pueden preparar: " + paquetes);
		} catch (NumberFormatException excepcion) {
			System.out.println("Las unidades solicitadas deben escribirse con cifras.");
		} catch (ArithmeticException excepcion) {
			System.out.println("No se puede preparar el pedido con cero unidades solicitadas.");
		}
	}
}
				

Orden de los catch: si se capturan excepciones muy generales y muy concretas, las más concretas deben escribirse primero. Por ejemplo, NumberFormatException debe ir antes que Exception.

1.6 El bloque finally

El bloque finally se ejecuta siempre, tanto si se produce una excepción como si no. Se utiliza normalmente para liberar recursos, cerrar conexiones o dejar el programa en un estado seguro.


public class EjemploFinally {
	public static void main(String[] args) {
		Boolean conexionAbierta = false;

		try {
			conexionAbierta = true;
			System.out.println("Conectando con el sistema de almacén...");

			Integer productos = 10;
			Integer cajas = 0;
			Integer productosPorCaja = productos / cajas;

			System.out.println("Productos por caja: " + productosPorCaja);
		} catch (ArithmeticException excepcion) {
			System.out.println("No se puede calcular el reparto entre cero cajas.");
		} finally {
			if (conexionAbierta) {
				System.out.println("Cerrando la conexión con el sistema de almacén.");
			}
		}
	}
}
				

Aunque el cálculo falle, el bloque finally se ejecutará y permitirá cerrar la conexión simulada.

1.7 Excepciones verificadas y no verificadas

Java diferencia entre excepciones verificadas y no verificadas.

Tipo Nombre habitual ¿Obliga el compilador a controlarlas? Ejemplo
Verificadas Checked exceptions Sí IOException
No verificadas Unchecked exceptions No NullPointerException, ArithmeticException, NumberFormatException

Las excepciones verificadas suelen estar relacionadas con operaciones externas al programa, como ficheros, red o bases de datos. Como pueden fallar por motivos ajenos al código, Java obliga a controlarlas.


import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class LecturaFichero {
	public static void main(String[] args) {
		try {
			BufferedReader lector = new BufferedReader(new FileReader("pedidos.txt"));
			String linea = lector.readLine();

			while (linea != null) {
				System.out.println("Pedido: " + linea);
				linea = lector.readLine();
			}

			lector.close();
		} catch (IOException excepcion) {
			System.out.println("No se ha podido leer el fichero de pedidos.");
			System.out.println("Detalle técnico: " + excepcion.getMessage());
		}
	}
}
				

1.8 try-with-resources

Cuando se trabaja con recursos que deben cerrarse, como ficheros o conexiones, Java permite utilizar try-with-resources. Esta estructura cierra automáticamente el recurso al terminar el bloque.


import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class LecturaFicheroSegura {
	public static void main(String[] args) {
		try (BufferedReader lector = new BufferedReader(new FileReader("pedidos.txt"))) {
			String linea = lector.readLine();

			while (linea != null) {
				System.out.println("Pedido: " + linea);
				linea = lector.readLine();
			}
		} catch (IOException excepcion) {
			System.out.println("No se ha podido leer el fichero de pedidos.");
		}
	}
}
				

Recomendación: para ficheros, conexiones y otros recursos cerrables, es preferible usar try-with-resources antes que cerrar manualmente el recurso.

1.9 Lanzar excepciones con throw

Además de capturar excepciones, un programa puede lanzar sus propias excepciones cuando detecta que los datos no cumplen las reglas de negocio.

Ejemplo: validación de edad mínima para registrar a una persona trabajadora en una aplicación interna.


public class ValidacionEdad {
	public static void main(String[] args) {
		Integer edad = 16;

		try {
			validarEdadLaboral(edad);
			System.out.println("Edad válida para el registro.");
		} catch (IllegalArgumentException excepcion) {
			System.out.println("No se puede completar el registro: " + excepcion.getMessage());
		}
	}

	public static void validarEdadLaboral(Integer edad) {
		if (edad == null) {
			throw new IllegalArgumentException("la edad no puede estar vacía");
		}

		if (edad < 18) {
			throw new IllegalArgumentException("la edad debe ser igual o superior a 18 años");
		}
	}
}
				

La palabra clave throw permite crear una excepción y lanzarla en el punto exacto en el que se detecta el problema.

1.10 Declarar excepciones con throws

Cuando un método puede producir una excepción verificada y no la controla internamente, debe declararla con throws. De esta forma, el método avisa a quien lo llame de que tendrá que controlar esa situación.


import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class ServicioPedidos {
	public static void main(String[] args) {
		try {
			String primerPedido = leerPrimerPedido("pedidos.txt");
			System.out.println("Primer pedido: " + primerPedido);
		} catch (IOException excepcion) {
			System.out.println("No se puede acceder al fichero de pedidos.");
		}
	}

	public static String leerPrimerPedido(String ruta) throws IOException {
		try (BufferedReader lector = new BufferedReader(new FileReader(ruta))) {
			return lector.readLine();
		}
	}
}
				

En este ejemplo, leerPrimerPedido() no decide cómo actuar si falla la lectura. Simplemente declara el problema con throws IOException y deja que el método main() lo gestione.

1.11 Buenas prácticas al trabajar con excepciones

  • No ocultes los errores: evitar bloques catch vacíos.
  • Captura excepciones concretas: mejor NumberFormatException que Exception si sabes cuál puede ocurrir.
  • No uses excepciones para controlar casos normales: si puedes validar antes, valida antes.
  • Da mensajes claros: el usuario necesita saber qué puede hacer para corregir el problema.
  • Registra los detalles técnicos: en aplicaciones reales, los errores deben quedar registrados para su diagnóstico.
  • Mantén el programa en un estado seguro: cierra recursos y evita dejar operaciones a medias.

Ejemplo de validación antes de operar:


public class ValidacionAntesDeDividir {
	public static void main(String[] args) {
		Integer unidades = 120;
		Integer operarios = 0;

		if (operarios == 0) {
			System.out.println("No se puede repartir el trabajo porque no hay operarios.");
		} else {
			Integer unidadesPorOperario = unidades / operarios;
			System.out.println("Unidades por operario: " + unidadesPorOperario);
		}
	}
}
				

2. Ejercicios

2.1 Conversión de datos de entrada

Crea un programa en Java llamado ConversionCantidad que convierta el texto "125" a un Integer usando Integer.parseInt() y muestre el resultado.

Después, cambia el texto por "12A" y controla la excepción NumberFormatException mostrando el mensaje: La cantidad introducida no es válida.

2.2 Reparto de tareas entre trabajadores

Crea un programa en Java llamado RepartoTareas con dos variables: Integer tareas = 45; e Integer trabajadores = 0;.

Calcula cuántas tareas corresponden a cada trabajador y controla la excepción ArithmeticException para que el programa no se detenga si el número de trabajadores es cero.

2.3 Lectura de fichero de clientes

Crea un programa en Java llamado LecturaClientes que intente leer la primera línea de un fichero llamado clientes.txt.

Utiliza try-with-resources y controla la excepción IOException mostrando un mensaje claro si el fichero no existe o no se puede leer.

2.4 Validación de precio

Crea un programa en Java llamado ValidacionPrecio con un método validarPrecio(Double precio).

El método debe lanzar una IllegalArgumentException si el precio es null o si es menor o igual que cero. En el método main(), llama al método dentro de un bloque try-catch y muestra un mensaje de error comprensible.

Reto: Registro seguro de pedidos

Desarrolla un programa en Java llamado RegistroSeguroPedidos que simule la validación de un pedido antes de registrarlo.

El programa debe trabajar con estos datos fijos:


String codigoProducto = "PR-458";
String textoUnidades = "8";
Double precioUnitario = 14.95;
			

Requisitos:

  1. Convierte textoUnidades a Integer controlando NumberFormatException.
  2. Comprueba que las unidades sean superiores a cero. Si no lo son, lanza una IllegalArgumentException.
  3. Comprueba que el precio no sea null y que sea mayor que cero. Si no lo es, lanza una IllegalArgumentException.
  4. Calcula el importe total del pedido.
  5. Muestra un mensaje final indicando si el pedido se ha registrado correctamente o si se ha rechazado.
  6. Añade un bloque finally que muestre siempre el mensaje Proceso de registro finalizado.

Ampliación: prueba el programa con valores incorrectos como "ocho", "0", null o un precio negativo para comprobar que cada excepción se controla correctamente.

3. Bibliografía