try, catch y finally
En resumen: Con varios bloques catch atrapas tipos distintos de excepciones, de lo concreto a lo general, y finally se ejecuta siempre, con error o sin él.
Problema
La cuenta para cero personas
El programa divide la cuenta del restaurante a partes iguales. Ya conoces la entrada incorrecta, y la atrapas:
import java.util.Scanner;
public class Main { public static void main(String[] args) { Scanner in = new Scanner(System.in); try { int bill = Integer.parseInt(in.nextLine()); int people = Integer.parseInt(in.nextLine()); System.out.println("Por persona: " + bill / people + " €"); } catch (NumberFormatException e) { System.out.println("Escribe números enteros."); } }}La cuenta es de 90 €, y alguien escribe 0 como número de personas. El programa vuelve a detenerse:
Exception in thread "main" java.lang.ArithmeticException: / by zero at Main.main(Main.java:9)catch está ahí, pero no ayuda. Solo atrapa NumberFormatException, y esta es otra clase de excepción.
La idea
Varios bloques catch
Después de un try pueden ir varios bloques catch, uno para cada tipo de error:
Scanner in = new Scanner(System.in);try { int bill = Integer.parseInt(in.nextLine()); int people = Integer.parseInt(in.nextLine()); System.out.println("Por persona: " + bill / people + " €");} catch (NumberFormatException e) { System.out.println("Escribe números enteros.");} catch (ArithmeticException e) { System.out.println("Las personas no pueden ser 0.");}| Entrada | Salida |
|---|---|
| 90 y 3 | Por persona: 30 € |
| 90 y 0 | Las personas no pueden ser 0. |
| 90 y tres | Escribe números enteros. |
Los bloques se comprueban de arriba abajo. Se ejecuta el primero cuyo tipo coincide, y solo ese. Los demás se saltan.
Scanner in = new Scanner(System.in);try { int bill = Integer.parseInt(in.nextLine()); int people = Integer.parseInt(in.nextLine()); System.out.println("Por persona: " + bill / people + " €");} catch (NumberFormatException e) { System.out.println("Escribe números enteros.");} catch (ArithmeticException e) { System.out.println("Las personas no pueden ser 0.");}Paso a paso, en palabras
- Primera ejecución: cuenta de 90 €, las personas son 0.
- 90 / 0 es una división entre cero: se lanza ArithmeticException.
- El primer catch es para NumberFormatException. No coincide, se salta.
- El segundo coincide. Se ejecuta él y solo él.
- Segunda ejecución: en lugar del número de personas se ha escrito «tres».
- parseInt no puede sacar un número: se lanza NumberFormatException.
- Ya coincide el primer catch. Al segundo no se llega.
- Los bloques se comprueban de arriba abajo. Se ejecuta el primero que coincide.
Más precisamente, para curiosos
- Cuando dos tipos se tratan igual, puedes atraparlos en un solo bloque con
|:catch (NumberFormatException | ArithmeticException e).
La idea
La familia de las excepciones
Las excepciones son clases, relacionadas por herencia. Aquí están las más comunes:
Throwable├── Error un problema grave de la JVM, no se atrapa│ ├── OutOfMemoryError│ └── StackOverflowError└── Exception ├── IOException archivos, red ├── InterruptedException └── RuntimeException ├── ArithmeticException ├── IllegalArgumentException │ └── NumberFormatException ├── IndexOutOfBoundsException │ └── ArrayIndexOutOfBoundsException └── NullPointerExceptioncatch atrapa el tipo que nombras y todas sus clases hijas. Es el polimorfismo del módulo sobre la herencia: una variable de un tipo padre puede apuntar a un objeto de una clase hija. Por eso catch (Exception e) lo atrapa todo:
static void split(String billText, String peopleText) { try { int bill = Integer.parseInt(billText); int people = Integer.parseInt(peopleText); System.out.println("Por persona: " + bill / people + " €"); } catch (Exception e) { System.out.println(e.getClass().getSimpleName() + ": " + e.getMessage()); }}
split("90", "3");split("90", "0");split("90", "tres");Por persona: 30 €ArithmeticException: / by zeroNumberFormatException: For input string: "tres"e.getClass().getSimpleName() da el nombre de la clase real del objeto. La variable es de tipo Exception, pero el objeto es de una clase hija.
Más precisamente, para curiosos
Errory sus clases hijas son problemas de la propia JVM, como quedarse sin memoria o un desbordamiento de la pila. Los lanza la propia JVM. Normalmente después de ellos el programa no puede hacer nada sensato, así que no se atrapan.catch (Throwable t)también los atraparía. No lo hagas.
- Atrapa de la forma más concreta que puedas. catch (Exception e) también esconde errores que no esperabas, como un fallo en tu propio código.
Pruébalo
¿Qué catch?
¿Qué imprimirá el programa?
int[] seats = { 4, 2, 6 };try { System.out.println("Mesa 4: " + seats[3] + " sitios");} catch (ArithmeticException e) { System.out.println("División entre cero");} catch (RuntimeException e) { System.out.println("Error de ejecución");} catch (Exception e) { System.out.println("Error general");}seats[3] está fuera del array de 3 elementos. ArrayIndexOutOfBoundsException no es una ArithmeticException, así que el primer bloque se salta. Es una clase hija de RuntimeException, así que el segundo coincide y se ejecuta. Al tercero nunca se llega, aunque también coincidiría.Pruébalo
Encuentra el error
Elige la línea con el error.
Exception es la clase padre de ArithmeticException. El primer catch lo atrapa todo, y al segundo nunca se puede llegar. El compilador lo dice: "exception ArithmeticException has already been caught". La regla es de lo concreto a lo general: intercambia los dos bloques.La idea
Excepciones comprobadas y throws
El compilador te obliga a tratar algunas excepciones. Aquí hay un método que imprime un recibo y espera a la impresora con Thread.sleep (una pausa en milisegundos):
static void printReceipt() { System.out.println("Imprimo el recibo..."); Thread.sleep(300); System.out.println("Listo.");}El programa no compila: "unreported exception InterruptedException; must be caught or declared to be thrown". La pausa se puede interrumpir desde fuera, y el compilador quiere que digas qué debe pasar entonces. Tienes dos opciones: atrapar la excepción en el método, o declararla con throws y dejar que decida quien llama:
static void printReceipt() throws InterruptedException { System.out.println("Imprimo el recibo..."); Thread.sleep(300); System.out.println("Listo.");}
try { printReceipt();} catch (InterruptedException e) { System.out.println("Interrumpido.");}Imprimo el recibo...Listo.- Las excepciones comprobadas son las clases hijas de
Exceptionque no sonRuntimeException. Normalmente vienen de fuera, de cosas que no dependen del programa: un archivo que falta, una conexión de red que se cae, una pausa interrumpida. - Las excepciones no comprobadas son
RuntimeExceptiony sus clases hijas. Normalmente son un error en el código o en la entrada: un índice, una división entre cero, un formato incorrecto. El compilador no te obliga a atraparlas.
Donde más a menudo te encontrarás con una excepción comprobada es con los archivos: IOException.
La idea
finally: siempre
La impresora de la caja tiene que liberarse después de cada recibo, o el siguiente no se imprimirá. Pase lo que pase, salga bien o haya un error:
static void printReceipt(String qtyText) { System.out.println("La impresora está ocupada."); try { int qty = Integer.parseInt(qtyText); System.out.println("Café: " + qty + " x 2 € = " + qty * 2 + " €"); } catch (NumberFormatException e) { System.out.println("Cantidad incorrecta: " + qtyText); } finally { System.out.println("La impresora está libre."); }}
printReceipt("3");printReceipt("tres");La impresora está ocupada.Café: 3 x 2 € = 6 €La impresora está libre.La impresora está ocupada.Cantidad incorrecta: tresLa impresora está libre.finally se ejecuta siempre: cuando todo sale bien, cuando la excepción se atrapa, e incluso cuando ninguno de estos bloques catch la atrapa. En este último caso finally se ejecuta, y la excepción sigue subiendo hasta el siguiente try que pueda atraparla.
¿Por qué no una simple línea después del try/catch? Porque esa línea se salta si la excepción es de un tipo que catch no atrapa, o si try tiene un return. finally no se salta.
Más precisamente, para curiosos
- También puedes tener
tryconfinallyy sincatch: la excepción no se atrapa, pero la limpieza se ejecuta.
- Para los recursos que hay que cerrar, como archivos y conexiones a bases de datos, hay una forma más corta:
try (...)con el recurso entre los paréntesis (try-with-resources). La veremos cuando lleguemos a los archivos. Cierra el recurso por sí sola, igual que lo haríafinally.
finallyno se ejecuta solo si se detiene todo el programa, por ejemplo conSystem.exit(0).
Pruébalo
finally y return
¿Qué imprimirá el programa?
static String reserve(int guests) { try { if (guests > 6) { return "No hay mesa para " + guests; } return "Mesa para " + guests; } finally { System.out.println("Registré el intento."); }}
System.out.println(reserve(8));Un return en try no se salta finally. El método ejecuta primero finally, y después devuelve el texto. Por eso «Registré el intento.» se imprime antes del resultado, que println recibe solo cuando el método ha terminado.
Ejercicio
Programas con varios bloques catch
Resuélvelas en tu editor. No imprimas mensajes para pedir datos.
1. Precio por persona. Los precios están en el array { 120, 75, 300 }. Lee un número de producto (desde 1) y el número de personas, e imprime el precio por persona con división entera. Atrapa tres cosas: un producto que no existe, cero personas, y todo lo demás (entonces imprime el mensaje de la excepción).
| Entrada | Salida |
|---|---|
| 2 y 3 | Por persona: 25 € |
| 5 y 2 | No existe ese producto. |
| 1 y 0 | El número de personas no puede ser 0. |
| 1 y dos | Error: For input string: "dos" |
2. Calculadora. Lee un número entero, un operador (+, -, * o /) y un segundo número entero, cada uno en su propia línea. Imprime la cuenta. Con texto en lugar de un número di «Escribe números enteros.», con división entre cero «División entre cero.», y con cualquier otro operador «Operador desconocido: » seguido del operador.
84/484 / 4 = 2184/0División entre cero.Más tareas
- El datáfono. El método
pay(String amountText)imprime «El datáfono está conectado.», después «Pagado: X €» o un mensaje sobre la entrada incorrecta, y enfinally«El datáfono está apagado.». Llámalo con «25» y con «veinticinco». - Nota media sin caídas. Lee 5 líneas con notas. Sáltate las que no son números enteros (una excepción) o no están entre 2 y 6 (un
if). Imprime la media con división entera. Si ninguna nota es válida, la división entre cero lanza: atrápala e imprime «No hay notas válidas.» - Predice y comprueba. Escribe un
tryconcatchyfinally, donde cada bloque imprime una línea. Predice la salida cuando sale bien, con una excepción atrapada y con una no atrapada, y después ejecútalo.
- Pausa. Escribe un método
pause(int ms)que llame aThread.sleep(ms). Hazlo una vez conthrows InterruptedExceptiony otra contry/catchdentro. ¿Qué tiene que hacermainen cada uno de los dos casos?
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Métodos de instancia · Herencia · Polimorfismo · Excepciones
- Continúa en: Lanzar excepciones y crear las tuyas · Archivos · Datos en archivos · Códigos hash y funciones hash
- Ejercicios sobre el tema: Errores y excepciones