W3docs

Excepciones de Java

Una visión general de las excepciones en Java — qué son, la jerarquía de excepciones y por qué importa el manejo de excepciones.

Excepciones de Java

Una excepción es lo que ocurre cuando su programa se topa, en su camino actual, con una situación que no puede manejar — un archivo que no está, un número dividido entre cero, un índice de arreglo fuera de rango. En lugar de devolver una respuesta equivocada o corromper el estado en silencio, Java detiene el método actual, construye un objeto de excepción que describe qué salió mal y empieza a buscar código que sepa cómo lidiar con ello. Aprender la maquinaria de excepciones es aprender cómo Java le dice qué salió mal, dónde y qué podría hacer al respecto.

Qué es realmente una excepción

Una excepción es un objeto Java corriente. En concreto, una instancia de una clase que hereda de java.lang.Throwable. Cuando algo sale mal, la JVM (o su propio código) crea uno de estos objetos y lo lanza. Desde ese momento, el programa sigue un flujo de control distinto hasta que, o bien un bloque catch acepta la excepción, o bien el hilo termina.

El objeto transporta información: un tipo (la clase — NullPointerException, IOException, etc.), un mensaje (una descripción legible por humanos) y una traza de pila (la cadena de llamadas congelada en el momento en que se creó la excepción). Cuando lee una excepción en una consola, está leyendo esas tres cosas.

Exception in thread "main" java.lang.ArithmeticException: / by zero
  at calc.Money.divide(Money.java:42)
  at calc.App.main(App.java:11)

La primera línea es tipo + mensaje. Las líneas indentadas son la traza de pila, la llamada más reciente primero.

Por qué excepciones en lugar de códigos de error

Los lenguajes más antiguos — C es el ejemplo clásico — señalan el fallo con códigos de retorno: cada función devuelve un int, usted lo comprueba, y si es negativo algo salió mal. Ese enfoque tiene dos problemas:

  1. Puede ignorar el valor de retorno. Un llamador que olvida comprobarlo no ve un fallo inmediato, y el error aflora más tarde en un lugar confuso.
  2. El camino de error ensucia el camino feliz. Cada línea de trabajo real va envuelta en if (err) return err;.

Las excepciones invierten esto. Por defecto, una excepción no manejada detiene la ejecución, ruidosamente. Usted opta por manejarla allí donde realmente tiene una estrategia. El buen camino se mantiene limpio; el camino de recuperación está en su propio bloque.

Las tres cosas que pueden salir mal

Java ordena todo lo que es Throwable en tres cubos, y la diferencia importa porque el lenguaje los trata de forma distinta:

  • Error — la propia JVM está en apuros. OutOfMemoryError, StackOverflowError. Estos no se atrapan en código normal. Por lo general no hay nada útil que pueda hacer.
  • RuntimeException (una subclase de Exception) — errores de programación que aparecen en tiempo de ejecución. NullPointerException, IndexOutOfBoundsException, ClassCastException. El compilador no le obliga a manejarlas, porque en código correcto no deberían ocurrir.
  • Exception comprobada (checked) (todo lo demás bajo Exception) — fallos recuperables que el programa debería anticipar. IOException, SQLException. El compilador exige que las atrape o las declare en la cláusula throws de su método.

La línea entre «error de programación» (excepción de runtime) y «fallo anticipado» (excepción comprobada) es una de las decisiones de diseño más discutidas de Java. Volveremos sobre ella en Excepciones comprobadas vs. no comprobadas en Java.

Cómo funcionan lanzar y atrapar

Cuando se ejecuta un throw, la JVM:

  1. Desenrolla la pila — el método actual termina abruptamente, luego su llamador hace lo mismo, y así sucesivamente.
  2. En cada marco, comprueba si hay un bloque try { ... } catch (SomeType e) { ... } cuyo catch coincida con el tipo de la excepción (o con un supertipo).
  3. El primer catch que coincide gana. El control salta allí, la pila deja de desenrollarse y la ejecución se reanuda en el bloque catch.
  4. Si nada coincide, el hilo muere. En un programa de un solo hilo, eso significa que la JVM imprime la traza de pila y termina.

Por eso una excepción lanzada puede viajar a través de muchos métodos antes de ser atrapada — cada lanzamiento no atrapado burbujea un marco más arriba.

Una primera prueba

No tiene que escribir throw para encontrarse con una excepción — Java mismo las lanza cuando algo sale mal. Aquí el ejemplo más simple: dividir un entero entre cero. La JVM crea una ArithmeticException por usted.

java— editable, runs on the server

Sin el try/catch, la tercera iteración haría caer todo el programa y la cuarta nunca se ejecutaría. Con él, el fallo queda contenido: obtenemos un mensaje de error y el bucle continúa. Esa capacidad — acotar el daño de un fallo — es todo el sentido de la maquinaria de excepciones.

Qué cubre esta parte del libro

Los capítulos restantes de esta parte son el vocabulario de trabajo del manejo de excepciones de Java, una pieza a la vez:

  • La forma de try/catch/finally y para qué sirve cada cláusula.
  • Múltiples catch y el atajo multi-catch.
  • try-with-resources para todo lo que necesita cerrarse.
  • Lanzar excepciones usted mismo con throw, y declararlas con throws.
  • Comprobadas vs. no comprobadas: cuál usar y cuándo.
  • La jerarquía de clases completa.
  • Escribir sus propios tipos de excepción para su dominio.
  • Los principios que distinguen un buen manejo de excepciones del ruido defensivo.

Léala en orden — cada capítulo da por supuesto el anterior.

Qué sigue

El manejo de excepciones empieza con la construcción más fundamental: el bloque try/catch. Continúe con Java try...catch.

Practice

Práctica

A method `loadConfig()` reads a file and throws `IOException` on failure. The caller wraps the call in `try { loadConfig(); } catch (RuntimeException e) { ... }`. The IO fails. What happens?