Lanzar excepciones
throw, throws y diseño de contratos
throw: lanzar una excepción
throw lanza una instancia de Throwable. Detiene la ejecución normal y desencadena
la búsqueda de un catch compatible hacia arriba en la pila de llamadas. Si no se encuentra ninguno,
la JVM termina el hilo y muestra el stack trace.
// throw: lanzar una excepción
public int dividir(int a, int b) {
if (b == 0) throw new ArithmeticException("Divisor no puede ser cero");
return a / b;
}
// Siempre se lanza una instancia — nunca la clase
// throw new IllegalArgumentException("mensaje"); // correcto
// throw IllegalArgumentException; // error de compilación
// Relanzar la misma excepción
try {
operacionRiesgosa();
} catch (IOException e) {
log.error("Fallo E/S", e);
throw e; // relanza la misma instancia (preserva el stack trace original)
} throws: declarar en la firma
throws declara que un método puede lanzar una o más checked exceptions. Es parte del contrato del
método y el compilador obliga al llamador a manejarlas. Para unchecked exceptions (RuntimeException) no es obligatorio declararlas, aunque algunos equipos lo hacen por documentación.
// throws: declarar en la firma que el método puede lanzar checked exceptions
// El llamador debe manejarlas o propagarlas a su vez
public void exportarCSV(List<Pedido> pedidos, String ruta) throws IOException {
try (BufferedWriter bw = new BufferedWriter(new FileWriter(ruta))) {
for (Pedido p : pedidos) {
bw.write(p.getId() + "," + p.getTotal());
bw.newLine();
}
}
// IOException de FileWriter y bw.write() se propaga al llamador
}
// El llamador decide qué hacer:
try {
exportarCSV(pedidos, "/tmp/export.csv");
} catch (IOException e) {
mostrarError("No se pudo exportar: " + e.getMessage());
} Envolver excepciones preservando la causa
Cuando se convierte una checked en unchecked, siempre pasar la excepción original como causa. Si no se hace, se pierde el stack trace original y el diagnóstico de problemas en producción se vuelve mucho más difícil.
// Patrón habitual: envolver checked en unchecked para no contaminar las firmas
// Preservar la excepción original como "causa" para no perder información de debug
public List<String> leerLineas(String ruta) {
try {
return java.nio.file.Files.readAllLines(java.nio.file.Paths.get(ruta));
} catch (IOException e) {
throw new RuntimeException("Error al leer fichero: " + ruta, e); // e = causa
}
}
// La causa se puede obtener con getCause()
try {
leerLineas("/no/existe.txt");
} catch (RuntimeException e) {
System.out.println("Causa raíz: " + e.getCause()); // IOException original
e.printStackTrace(); // muestra toda la cadena de causas
} Diseño de contratos con excepciones
Elige la excepción que mejor describe la violación. IllegalArgumentException
para parámetros inválidos, IllegalStateException para estado inconsistente,
NullPointerException para parámetros null explícitamente rechazados, y
UnsupportedOperationException para operaciones no implementadas.
// Diseñar contratos claros con excepciones
// Precondiciones: IllegalArgumentException, NullPointerException
public Pedido crearPedido(Cliente cliente, List<Producto> productos) {
Objects.requireNonNull(cliente, "cliente no puede ser null");
Objects.requireNonNull(productos, "productos no puede ser null");
if (productos.isEmpty()) throw new IllegalArgumentException("Debe haber al menos un producto");
// ...
}
// Estado inválido: IllegalStateException
public void confirmar() {
if (estado != Estado.BORRADOR)
throw new IllegalStateException("Solo se pueden confirmar pedidos en borrador");
estado = Estado.CONFIRMADO;
}
// Operación no soportada: UnsupportedOperationException
public void eliminar() {
throw new UnsupportedOperationException("Los pedidos confirmados no se pueden eliminar");
}