Introducción
En un programa real, las cosas salen mal: el usuario ingresa letras donde se esperan números, el archivo no existe, la conexión a la base de datos falla. Si no manejas estos casos, tu programa termina abruptamente con un mensaje de error confuso.
En Java, los errores en tiempo de ejecución se representan como excepciones — objetos que describen qué salió mal y dónde. El lenguaje te da herramientas para detectarlos, manejarlos y continuar la ejecución de forma controlada.
¿Qué vas a aprender en este lab?
- Entender qué es una excepción y cómo fluye
- Capturar excepciones con
try-catch-finally - Lanzar excepciones con
throw - Crear tus propias excepciones personalizadas
- Distinguir entre excepciones verificadas y no verificadas
- Usar
try-with-resourcespara cerrar recursos automáticamente
Requisitos previos: fundamentos de Java y nociones básicas de POO (clases, herencia).
1. ¿Qué es una excepción?
Cuando Java encuentra una operación imposible (dividir por cero, acceder a un índice inválido, convertir texto en número...), lanza un objeto de tipo Exception. Si nadie lo captura, el programa termina y muestra un stack trace — la ruta de llamadas que llevó al error.
int[] numeros = {1, 2, 3};
System.out.println(numeros[5]); // ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 3
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 3
at Main.main(Main.java:3)
Jerarquía de excepciones
Throwable
├── Error (problemas graves de la JVM — no se capturan normalmente)
│ ├── OutOfMemoryError
│ └── StackOverflowError
└── Exception
├── RuntimeException (no verificadas — no obligan a try-catch)
│ ├── NullPointerException
│ ├── ArrayIndexOutOfBoundsException
│ ├── ClassCastException
│ └── IllegalArgumentException
└── IOException (verificadas — el compilador obliga a manejarlas)
├── FileNotFoundException
└── SQLException
2. try-catch
Envuelve el código que puede fallar en un bloque try. Si lanza una excepción, el flujo salta al bloque catch correspondiente.
try {
int resultado = 10 / 0; // lanza ArithmeticException
System.out.println(resultado); // esto NO se ejecuta
} catch (ArithmeticException e) {
System.out.println("Error: " + e.getMessage()); // Error: / by zero
}
System.out.println("El programa continúa."); // esto SÍ se ejecuta
Capturar múltiples excepciones
String[] datos = {"42", "hola", null};
for (String dato : datos) {
try {
int numero = Integer.parseInt(dato);
System.out.println("Número: " + numero);
} catch (NumberFormatException e) {
System.out.println("'" + dato + "' no es un número válido.");
} catch (NullPointerException e) {
System.out.println("El dato es null.");
}
}
// Número: 42
// 'hola' no es un número válido.
// El dato es null.
Multi-catch (Java 7+)
Cuando varios tipos de excepción requieren el mismo manejo:
try {
// código que puede lanzar varias excepciones
} catch (NumberFormatException | IllegalArgumentException e) {
System.out.println("Dato inválido: " + e.getMessage());
}
Capturar la clase base
try {
// algún código
} catch (Exception e) {
// captura CUALQUIER excepción que herede de Exception
System.out.println("Error inesperado: " + e.getMessage());
}
Capturar
Exceptiondirectamente es un último recurso. Siempre que sea posible, captura el tipo específico para poder dar una respuesta adecuada.
3. finally
El bloque finally se ejecuta siempre, ocurra o no una excepción. Es el lugar indicado para limpiar recursos (cerrar conexiones, archivos, etc.).
public static int dividir(int a, int b) {
try {
return a / b;
} catch (ArithmeticException e) {
System.out.println("División por cero.");
return -1;
} finally {
System.out.println("Bloque finally ejecutado."); // siempre se ejecuta
}
}
System.out.println(dividir(10, 2)); // Bloque finally ejecutado. → 5
System.out.println(dividir(10, 0)); // División por cero. Bloque finally ejecutado. → -1
4. throw — lanzar excepciones
Puedes lanzar una excepción manualmente con throw cuando detectas un estado inválido en tu lógica de negocio.
public static double calcularRaiz(double numero) {
if (numero < 0) {
throw new IllegalArgumentException("No se puede calcular la raíz de un número negativo: " + numero);
}
return Math.sqrt(numero);
}
// En main:
System.out.println(calcularRaiz(16.0)); // 4.0
System.out.println(calcularRaiz(-4.0));
// Exception in thread "main" java.lang.IllegalArgumentException: No se puede calcular la raíz de un número negativo: -4.0
Capturando la excepción lanzada
try {
double resultado = calcularRaiz(-9.0);
} catch (IllegalArgumentException e) {
System.out.println("Error de entrada: " + e.getMessage());
}
// Error de entrada: No se puede calcular la raíz de un número negativo: -9.0
5. Excepciones personalizadas
Crear tus propias excepciones hace que el código sea más descriptivo y permite distinguir errores de tu dominio de errores genéricos de Java.
// Excepción no verificada (extiende RuntimeException)
public class SaldoInsuficienteException extends RuntimeException {
private final double montoFaltante;
public SaldoInsuficienteException(double montoFaltante) {
super("Saldo insuficiente. Faltan: $" + montoFaltante);
this.montoFaltante = montoFaltante;
}
public double getMontoFaltante() {
return montoFaltante;
}
}
public class CuentaBancaria {
private double saldo;
public CuentaBancaria(double saldoInicial) {
this.saldo = saldoInicial;
}
public void retirar(double monto) {
if (monto > saldo) {
throw new SaldoInsuficienteException(monto - saldo);
}
saldo -= monto;
}
public double getSaldo() { return saldo; }
}
CuentaBancaria cuenta = new CuentaBancaria(100.0);
try {
cuenta.retirar(150.0);
} catch (SaldoInsuficienteException e) {
System.out.println(e.getMessage()); // Saldo insuficiente. Faltan: $50.0
System.out.println("Falta: $" + e.getMontoFaltante()); // Falta: $50.0
}
Excepciones verificadas vs no verificadas
Verificadas (Exception) |
No verificadas (RuntimeException) |
|
|---|---|---|
| El compilador obliga a manejarlas | Sí | No |
| Cuándo usarlas | Errores recuperables esperados (archivo no encontrado, red caída) | Errores de programación (índice inválido, argumento nulo) |
| Ejemplo | IOException, SQLException |
NullPointerException, IllegalArgumentException |
6. throws — declarar que un método puede fallar
Cuando un método puede lanzar una excepción verificada sin capturarla internamente, debe declararlo con throws:
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
// El compilador sabe que este método puede lanzar IOException
public static String leerArchivo(String ruta) throws IOException {
return Files.readString(Path.of(ruta));
}
// Quien llame al método debe manejarlo
public static void main(String[] args) {
try {
String contenido = leerArchivo("datos.txt");
System.out.println(contenido);
} catch (IOException e) {
System.out.println("No se pudo leer el archivo: " + e.getMessage());
}
}
7. try-with-resources
Cuando trabajas con recursos que deben cerrarse (archivos, conexiones a base de datos, streams), usa try-with-resources. El recurso se cierra automáticamente al terminar el bloque, incluso si ocurre una excepción.
import java.io.*;
// Sin try-with-resources: tedioso y propenso a errores
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("datos.txt"));
String linea = reader.readLine();
System.out.println(linea);
} catch (IOException e) {
System.out.println("Error: " + e.getMessage());
} finally {
if (reader != null) {
try { reader.close(); } catch (IOException e) { }
}
}
// Con try-with-resources: limpio y seguro
try (BufferedReader reader = new BufferedReader(new FileReader("datos.txt"))) {
String linea = reader.readLine();
System.out.println(linea);
} catch (IOException e) {
System.out.println("Error: " + e.getMessage());
}
// reader.close() se llama automáticamente
Cualquier clase que implemente AutoCloseable o Closeable puede usarse en try-with-resources.
8. Ejercicios
Ejercicio 1
Escribe un método parsearEntero(String texto) que:
- Intente convertir el texto a
intconInteger.parseInt - Si falla, capture
NumberFormatExceptiony devuelva unOptional<Integer>vacío - Si tiene éxito, devuelva el valor envuelto en
Optional.of
Úsalo para procesar una lista de Strings mixtos (números y texto) sin que el programa falle.
Ejercicio 2
Crea una excepción personalizada EdadInvalidaException que extienda RuntimeException. Úsala en una clase Persona cuyo constructor rechace edades negativas o mayores de 150.
Ejercicio 3
Escribe un método que lea un archivo de texto línea por línea usando try-with-resources y devuelva el número de líneas. Si el archivo no existe, muestra un mensaje amigable y devuelve -1.
Errores Comunes
Error 1 — Capturar y silenciar la excepción
// Nunca hagas esto: tragas el error y no sabes qué pasó
try {
// código
} catch (Exception e) {
// vacío — el error desaparece sin dejar rastro
}
// Al menos registra el error
try {
// código
} catch (Exception e) {
System.err.println("Error inesperado: " + e.getMessage());
e.printStackTrace(); // en producción usarías un logger
}
Error 2 — Usar excepciones para control de flujo normal
// Las excepciones son costosas; no las uses como if-else
try {
int index = lista.indexOf("elemento");
String elemento = lista.get(index);
} catch (IndexOutOfBoundsException e) {
System.out.println("No existe");
}
// Verifica primero
if (lista.contains("elemento")) {
String elemento = lista.get(lista.indexOf("elemento"));
}
Error 3 — Capturar la excepción en el lugar equivocado
// Capturar demasiado pronto: pierdes contexto
public static int dividir(int a, int b) {
try {
return a / b;
} catch (ArithmeticException e) {
return 0; // el llamador no sabe que hubo un error
}
}
// Dejar que la excepción suba si el llamador puede manejarla mejor
public static int dividir(int a, int b) {
if (b == 0) throw new IllegalArgumentException("El divisor no puede ser cero");
return a / b;
}
Resumen
| Concepto | Para qué sirve |
|---|---|
try-catch |
Capturar y manejar una excepción |
finally |
Código que se ejecuta siempre (limpiar recursos) |
throw |
Lanzar una excepción desde tu código |
throws |
Declarar que un método puede lanzar una excepción verificada |
| Excepción personalizada | Describir errores específicos de tu dominio |
try-with-resources |
Cerrar recursos automáticamente |
Próximos pasos
- Genéricos: crea métodos y clases que trabajen con cualquier tipo sin perder seguridad de tipos
- Colecciones: aplica el manejo de excepciones a operaciones con listas y mapas
- Patrones de diseño: el patrón Result/Either es una alternativa funcional a las excepciones para errores esperados