Ir al contenido
wannacode

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:

Main.java
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:

Error
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
  1. Primera ejecución: cuenta de 90 €, las personas son 0.
  2. 90 / 0 es una división entre cero: se lanza ArithmeticException.
  3. El primer catch es para NumberFormatException. No coincide, se salta.
  4. El segundo coincide. Se ejecuta él y solo él.
  5. Segunda ejecución: en lugar del número de personas se ha escrito «tres».
  6. parseInt no puede sacar un número: se lanza NumberFormatException.
  7. Ya coincide el primer catch. Al segundo no se llega.
  8. 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:

La jerarquía
Throwable
├── Error un problema grave de la JVM, no se atrapa
│ ├── OutOfMemoryError
│ └── StackOverflowError
└── Exception
├── IOException archivos, red
├── InterruptedException
└── RuntimeException
├── ArithmeticException
├── IllegalArgumentException
│ └── NumberFormatException
├── IndexOutOfBoundsException
│ └── ArrayIndexOutOfBoundsException
└── NullPointerException

catch 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");
Salida
Por persona: 30 €
ArithmeticException: / by zero
NumberFormatException: 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
  • Error y 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");
}
Respuesta

Pruébalo

Encuentra el error

Este programa no compila. ¿En qué línea está el error?

Elige la línea con el error.

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.");
}
Salida
Imprimo el recibo...
Listo.
  • Las excepciones comprobadas son las clases hijas de Exception que no son RuntimeException. 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 RuntimeException y 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");
Salida
La impresora está ocupada.
Café: 3 x 2 € = 6 €
La impresora está libre.
La impresora está ocupada.
Cantidad incorrecta: tres
La 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 try con finally y sin catch: 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ía finally.
  • finally no se ejecuta solo si se detiene todo el programa, por ejemplo con System.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));
Respuesta

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.

Entrada
84
/
4
Salida
84 / 4 = 21
Entrada
84
/
0
Salida
Divisió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 en finally «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 try con catch y finally, 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 a Thread.sleep(ms). Hazlo una vez con throws InterruptedException y otra con try/catch dentro. ¿Qué tiene que hacer main en cada uno de los dos casos?

Test

Ponte a prueba

1. La excepción coincide con dos de los tres bloques catch. ¿Cuál se ejecuta?
2. ¿Atrapa catch (RuntimeException e) una excepción de tipo NumberFormatException?
3. ¿Por qué catch (Exception e) tiene que ir el último?
4. ¿Cuándo se ejecuta finally?
5. ¿Qué excepción es comprobada (el compilador te obliga a atraparla o a declararla con throws)?

Creado por Ivo Gergov como parte de la campaña de Evo Tech por una educación accesible en programación ·