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