Bloques catch múltiples y multi-catch en Java
Captura distintos tipos de excepción en Java con varios bloques catch o una cláusula multi-catch (Tipo1 | Tipo2 e) en un solo bloque.
Un único try puede ir seguido de cualquier número de bloques catch. Cada uno declara un tipo de excepción diferente y se ejecuta únicamente si el lanzamiento coincide. Así es como Java permite que un bloque de código reaccione de manera distinta dependiendo de qué salió mal — un fallo de red no es un error de análisis, y puede que quieras manejarlos de formas completamente diferentes.
Múltiples catches secuenciales
try {
String body = httpClient.get(url);
Config cfg = parser.parse(body);
apply(cfg);
} catch (IOException e) {
retryLater(url);
} catch (ParseException e) {
report("config file is malformed: " + e.getMessage());
} catch (SecurityException e) {
report("not allowed to apply config");
}Solo uno de los catch se ejecuta por lanzamiento — el primero cuyo tipo sea una coincidencia de instancia con la excepción lanzada. Tras ejecutarse, el control salta a la instrucción posterior a toda la sentencia try, no al siguiente catch.
El orden importa: de lo específico a lo general
Un catch (T e) coincide con cualquier cosa que sea T o cualquier subclase de T. Si listas una superclase antes que su subclase, el catch de la subclase es inalcanzable y el compilador lo rechaza:
try { ... }
catch (Exception e) { ... } // matches everything below Exception
catch (IOException e) { ... } // ERROR: unreachableEl orden a seguir es el tipo más específico arriba, el más amplio abajo:
try { ... }
catch (FileNotFoundException e) { ... } // most specific
catch (IOException e) { ... } // wider
catch (Exception e) { ... } // catch-all (used sparingly)Este también es un ejercicio de diseño útil: escribir los catches te obliga a pensar en qué fallos puede producir realmente el bloque.
Multi-catch (un bloque, varios tipos)
Java 7 añadió la forma multi-catch: un solo bloque que maneja varios tipos de excepción no relacionados, separados por |:
try {
return parser.read(file);
} catch (IOException | ParseException e) {
log.warn("could not load config: " + e);
return Config.defaults();
}Úsalo cuando el manejo sea idéntico para varios fallos distintos. Es más corto que dos bloques catch casi duplicados y hace evidente de un vistazo la relación "estos comparten una respuesta".
Reglas a tener en cuenta:
- Los tipos en la unión no deben estar relacionados por herencia.
IOException | FileNotFoundExceptionno compilará — uno es subtipo del otro, por lo que el más amplio ya lo cubre. - Dentro del bloque,
ese tipifica como el supertipo común de los tipos listados. Puedes llamar a métodos declarados en ese supertipo, pero no a los específicos del subtipo. Para la mayoría de usos (registro, envoltura),getMessage()ytoString()son suficientes. - El parámetro catch en un multi-catch es implícitamente final — no puedes reasignarlo. (Los catches de tipo único son solo efectivamente finales; la diferencia no importa en la práctica.)
Cuándo dividir un try
Un error común de legibilidad es envolver un método entero en un único try gigante y luego capturar todo lo que cualquier línea pueda lanzar. La lógica de manejo al final acaba confundida sobre qué línea falló.
Dos formas más limpias cuando eso ocurre:
- Dos sentencias try separadas, cada una limitada a un conjunto relacionado de operaciones.
- Un try dentro de un método que llama a métodos más pequeños, cada uno responsable de un tipo de fallo.
Cuanto más pequeño sea el try, más fácil resulta razonar sobre los catches. "¿Qué línea podría lanzar esto?" debería tener siempre una respuesta breve.
Un ejemplo elaborado
Un pequeño programa que hace tres cosas — analizar un número, buscarlo en un array, dividir por él — cada una de las cuales puede fallar a su manera. Capturamos cada fallo con un manejador dedicado para que los mensajes sean específicos. El último catch usa la forma multi-catch para agrupar dos fallos que comparten una respuesta.
Cada entrada toma un camino diferente a través de los catches, pero cada iteración imprime una línea limpia — el programa nunca deja al usuario sin respuesta.
Qué sigue
try/catch maneja el camino feliz y el camino de fallo. La tercera cláusula, finally, maneja las cosas que deben ocurrir de todas formas. Continúa en bloque finally de Java.