Capturar una excepción en lugar de caerse
Ya sabes que cuando algo sale mal, Java lanza una excepción, y una excepción no manejada detiene tu programa con un stack trace. Un bloque try-catch es la forma de tomar el control: envuelves el código riesgoso en try, y si lanza una excepción, Java salta al bloque catch que coincide en lugar de caerse.
En el momento en que 10 / 0 lanza la excepción, el resto del bloque try se omite y el control pasa al catch. Después de que el catch termina, la ejecución continúa con normalidad: el programa no muere.
La variable del catch contiene la excepción
La e en catch (ArithmeticException e) es un objeto real. Lleva información sobre lo que salió mal, siendo lo más útil un mensaje:
e.getMessage() devuelve una descripción breve. Durante la depuración, e.printStackTrace() vuelca el rastreo completo mostrando exactamente de dónde vino la excepción; úsalo cuando un mensaje por sí solo no baste para encontrar la causa.
Captura el tipo específico, no todo
Un bloque catch solo captura excepciones que coinciden con su tipo declarado (o una subclase). El mayor error de los principiantes es capturar Exception para hacer que un problema "desaparezca":
// No hagas esto: oculta bugs reales
try {
doWork();
} catch (Exception e) {
// se traga NullPointerException, erratas, errores de lógica... todo
}
Captura el tipo más estrecho que realmente puedas manejar. Si esperas una entrada numérica incorrecta, captura NumberFormatException. Cualquier cosa que no hayas anticipado debería poder propagarse para que te enteres realmente de ella en lugar de continuar en silencio en un estado roto.
Manejar varios tipos de excepción
Puedes apilar varios bloques catch. Java los revisa de arriba abajo y ejecuta el primero que coincide, así que ordénalos del más específico al más general:
Cuando dos tipos comparten el mismo manejo, usa un solo multi-catch con | en lugar de duplicar el bloque:
Un detalle a tener en cuenta: un tipo más general debe ir después de los específicos. Poner catch (Exception e) primero hace que los bloques posteriores, más específicos, sean inalcanzables, y el compilador lo rechaza.
finally siempre se ejecuta
Un bloque finally se ejecuta después del try y de cualquier catch, sin importar lo que haya ocurrido: éxito, una excepción capturada o incluso un return anticipado. Es donde corresponde la limpieza que siempre debe ocurrir.
"Closing resource" se imprime tanto si se dispara la excepción como si no. Evita, eso sí, hacer return desde dentro de finally: puede descartar silenciosamente una excepción o sobrescribir un valor devuelto desde el bloque try.
try-with-resources cierra las cosas por ti
Cuando trabajas con algo que debe cerrarse (un archivo, una conexión de red, una sentencia de base de datos), declararlo dentro de try (...) lo cierra automáticamente cuando el bloque termina, incluso si se lanza una excepción. Funciona con cualquier tipo que implemente AutoCloseable.
Esto reemplaza el viejo patrón de abrir en try y cerrar en finally, y es menos propenso a errores porque no puedes olvidarte de cerrar. Prefiérelo siempre que estés tratando con un recurso que se pueda cerrar.
No uses excepciones para el flujo de control normal
try-catch es para situaciones excepcionales, no para condiciones ordinarias. Capturar una excepción es más costoso que una simple comprobación, y hace que el código sea más difícil de leer. Si puedes comprobar la condición primero, hazlo así:
// Evita: usar un catch para comprobar si falta una clave
try {
process(map.get(key).trim());
} catch (NullPointerException e) {
// manejar la clave faltante
}
// Prefiere: comprobar explícitamente
String value = map.get(key);
if (value != null) {
process(value.trim());
}
Reserva catch para cosas que están genuinamente fuera de tu control: entrada de usuario incorrecta, archivos faltantes, fallos de red.
Siguiente: NullPointerException
La excepción más común que capturarás (y provocarás) en Java es la NullPointerException: aparece en el instante en que llamas a un método sobre algo que resultó ser null. A continuación profundizaremos en qué exactamente la dispara, cómo leer su stack trace y los hábitos que evitan que ocurra desde el principio.
Preguntas frecuentes
¿Cómo se usa try-catch en Java?
Pon el código que podría lanzar una excepción dentro de un bloque try y luego añade un bloque catch que nombre el tipo de excepción que quieres manejar: try { risky(); } catch (IOException e) { ... }. Si el código del try lanza una excepción que coincide, Java salta directamente al bloque catch en lugar de caerse. La variable (e) contiene el objeto de la excepción para que puedas leer su mensaje con e.getMessage().
¿Para qué sirve el bloque finally en Java?
Un bloque finally se ejecuta después de try/catch pase lo que pase, ya sea que el código funcione, lance una excepción o incluso haga return de forma anticipada. Es el lugar para la limpieza que siempre debe ocurrir, como cerrar un archivo o liberar un bloqueo. Para cerrar recursos, try-with-resources suele ser más limpio porque los cierra automáticamente.
¿Deberías capturar Exception o un tipo de excepción específico?
Captura el tipo más específico que realmente puedas manejar. Capturar Exception (o peor aún, Throwable) se traga todos los problemas, incluidos bugs como NullPointerException, y oculta la verdadera causa. Captura NumberFormatException si eso es lo que esperas, y deja que las excepciones inesperadas se propaguen para que te enteres de ellas.