Saltar al contenido principal

Excepciones

Hasta ahora hemos creado programas que normalmente siguen el flujo que esperamos:

Inicio



Ejecutar instrucciones



Mostrar resultado



Fin

Pero en un programa pueden ocurrir situaciones inesperadas.

Por ejemplo:

  • Intentar dividir entre 0.
  • Intentar acceder a una posición que no existe en un ArrayList.
  • Intentar abrir un archivo que no existe.
  • Recibir un dato incorrecto del usuario.

Cuando ocurre un problema durante la ejecución del programa, Java puede producir una excepción.

Por ejemplo, al intentar dividir un número por 0:

int resultado = 10 / 0;

Esto provocará una excepción:

ArithmeticException

Las excepciones nos permiten detectar problemas y decidir qué hacer cuando ocurren.


¿Qué es una excepción?

Una excepción es un problema que ocurre durante la ejecución de un programa.

Por ejemplo:

int numero = 10;

int resultado = numero / 0;

No podemos dividir un número entre 0.

Cuando Java llega a esta instrucción:

numero / 0

se produce una excepción.

Java muestra información similar a:

Exception in thread "main" java.lang.ArithmeticException: / by zero

Además, el programa dejará de ejecutar las instrucciones posteriores si la excepción no se controla.

Por ejemplo:

System.out.println("Inicio");

int resultado = 10 / 0;

System.out.println("Fin");

La salida será:

Inicio

Después ocurre la excepción. Por lo que la línea:

System.out.println("Fin");

no llegará a ejecutarse.

Cada excepción tiene un nombre predefinido por Java, como es ArithmeticException. Nos iremos encontrando con las excepciones más comunes pero hay muchas más.

A continuación veremos qué hacer cuando nos encontramos con un código que creemos puede producir alguna excepción.


try

Para intentar controlar una excepción podemos utilizar:

try

La palabra try significa que vamos a indicar un bloque de código que podría producir una excepción.

Por ejemplo, dividir entre 0 puede producir una excepción, por lo que meteremos esa instrucción entre las llaves del try:

try {

int resultado = 10 / 0;

}

Pero try normalmente se utiliza junto con catch.

La estructura básica es:

try {

// Código que puede producir una excepción

} catch (Exception e) {

// Código que se ejecuta si ocurre la excepción

}

Podemos imaginarlo así:

try



Intentar ejecutar código



¿Ocurre una excepción?

├── NO → continuar

└── SÍ → pasa a evaluar el catch

catch

La palabra:

catch

permite capturar una excepción.

Por ejemplo:

try {

int resultado = 10 / 0;

} catch (ArithmeticException e) {

System.out.println("No se puede dividir entre cero");

}

Ahora, cuando ocurre la excepción:

int resultado = 10 / 0;

Java entra en el bloque:

catch

y ejecuta:

System.out.println("No se puede dividir entre cero");

La salida será:

No se puede dividir entre cero

Esto evita que el programa termine directamente, ya que cuando ocurre una ArithmeticException, en lugar de mostrarse y parar completamente el código, se ejecuta el catch y sigue con normalidad.

Podemos visualizarlo:

try

10 / 0



ArithmeticException



catch

"No se puede dividir entre cero"

De manera que el código anterior:

System.out.println("Inicio");

int resultado = 10 / 0;

System.out.println("Fin");

que mostrava solamente:

Exception in thread "main" java.lang.ArithmeticException: / by zero

y no continuava el código. Ahora si le añadimos el try catch:

System.out.println("Inicio");

try {

int resultado = 10 / 0;

} catch (ArithmeticException e) {

System.out.println("No se puede dividir entre cero");

}

System.out.println("Fin");

El programa podrá continuar con normalidad si salta la excepción:

Inicio
No se puede dividir entre cero
Fin

El objeto de la excepción

En:

catch (ArithmeticException e)

tenemos por un lado el tipo de excepción, en este caso ArithmeticException y la variable e, que nos permite acceder a información sobre lo que ha ocurrido.

Por ejemplo en:

try {
int numerador = 10;
int denominador = 0;
int resultado = numerador / denominador;
System.out.println("Resultado: " + resultado);
} catch (ArithmeticException e) {
String mensaje = e.getMessage();
System.out.println("Error al dividir: " + mensaje);
}

usamos el método e.getMessage(), que nos muestra el mensaje predefinido del error en cuestión, en este caso:

Error al dividir: / by zero

Capturar diferentes tipos de excepciones

No todos los problemas producen el mismo tipo de excepción.

Por ejemplo:

int resultado = 10 / 0;

produce:

ArithmeticException

Mientras que:

ArrayList<String> nombres = new ArrayList<>();

nombres.get(10);

puede producir:

IndexOutOfBoundsException

Y:

Integer.parseInt("hola");

produce:

NumberFormatException

Varios catch

También podemos tener varios bloques catch.

Por ejemplo:

try {

// Código que puede producir diferentes excepciones

} catch (ArithmeticException e) {

System.out.println("Error matemático");

} catch (NumberFormatException e) {

System.out.println("El formato del número es incorrecto");

} catch (IndexOutOfBoundsException e) {

System.out.println("La posición no existe");

}

Java comprobará qué tipo de excepción se ha producido y ejecutará el catch correspondiente.

Podemos imaginarlo así:

                    try


Ocurre una excepción

┌──────────┼──────────┐
↓ ↓ ↓
ArithmeticException NumberFormatException IndexOutOfBoundsException
│ │ │
↓ ↓ ↓
catch 1 catch 2 catch 3

finally

Además de try y catch, podemos utilizar:

finally

El bloque finally contiene código que queremos ejecutar al finalizar el bloque try, haya ocurrido o no una excepción.

Por ejemplo:

try {

System.out.println("Intentando ejecutar");

} catch (Exception e) {

System.out.println("Ha ocurrido un error");

} finally {

System.out.println("Fin del proceso");

}

Si no ocurre ninguna excepción:

Intentando ejecutar

Fin del proceso

Si ocurre una excepción:

Intentando ejecutar

Ha ocurrido un error

Fin del proceso

El bloque finally se ejecuta en ambos casos.

Podemos visualizarlo:

             try

┌──────┴──────┐
↓ ↓
Sin error Con error
│ │
↓ ↓
continuar catch
│ │
└──────┬──────┘

finally

throw

Hasta ahora hemos visto cómo controlar excepciones que se producen automáticamente.

Pero también podemos provocar nosotros mismos una excepción.

Para ello utilizamos:

throw

Por ejemplo:

throw new Exception();

Esto significa:

Crear una excepción y lanzarla.

Podemos utilizarlo para comprobar si un dato cumple una determinada condición.

Es decir, podemos crear nosotros nuestras propias excepciones. Por ejemplo, por defecto introducir una edad negativa no es ningún error de Java. Pero nosotros podemos decidir que sí lo sea, lanzando una exepción si se introdujese una edad negativa:

int edad = -5;

if (edad < 0) {

throw new IllegalArgumentException("La edad no puede ser negativa");

}

throw con try y catch

También podemos lanzar una excepción y capturarla.

Por ejemplo:

try {

int edad = -5;

if (edad < 0) {

throw new IllegalArgumentException("Edad no válida");

}

} catch (IllegalArgumentException e) {

System.out.println(e.getMessage());

}

La salida será:

Edad no válida

Aquí tenemos dos conceptos diferentes:

throw

Lanza una excepción

catch

Captura una excepción

throws

Aunque pueda parecer lo mismo, antes hemos hablado de throw.

throw y throws tienen nombres parecidos, pero no significan lo mismo.

Utilizamos:

throw

para lanzar una excepción concreta.

Por ejemplo:

throw new Exception("Ha ocurrido un error");

Mientras que:

throws

se utiliza en la declaración de un método para indicar que ese método puede lanzar una determinada excepción.

Por ejemplo:

public static void comprobarEdad(int edad) throws Exception {

if (edad < 0) {

throw new Exception("Edad no válida");

}

}

Aquí:

throws Exception

indica:

Este método puede lanzar una excepción de tipo Exception.

Mientras que:

throw new Exception(...)

es el momento en el que realmente lanzamos la excepción.

Podemos recordarlo así:

throws

Declara que puede ocurrir

throw

Lanza la excepción

Ejemplo con throws

Podemos crear un método:

public static void comprobarEdad(int edad) throws Exception {

if (edad < 0) {

throw new Exception("La edad no puede ser negativa");

}

System.out.println("Edad correcta");

}

Ahora podemos llamarlo:

comprobarEdad(20);

Esto mostrará:

Edad correcta

Pero si hacemos:

comprobarEdad(-5);

se lanzará la Exception.

Por norma general cuando queramos que nuestro código lanze una excepción con throw necesitaremos añadirle a dicha clase el throws Exception para que esté preparado para lanzarla.


Excepciones personalizadas

En algunos programas necesitaremos crear nuestros propios tipos de excepciones.

Por ejemplo, imaginemos que estamos creando un programa para gestionar cuentas bancarias.

Podríamos querer una excepción específica para indicar que no hay suficiente dinero:

SaldoInsuficienteException

Para ello podemos crear nuestra propia clase de excepción. Lo bueno es que la sintaxis para crear tus propias excepciones es siempre muy similar:

public class NombreDeLaExcepcion extends Exception {

public NombreDeLaExcepcion(String mensaje) {

super(mensaje);

}

}

Por ejemplo:

public class SaldoInsuficienteException extends Exception {

public SaldoInsuficienteException(String mensaje) {

super(mensaje);

}

}

Nuestra excepción:

SaldoInsuficienteException

│ extends

Exception

Ahora podemos utilizarla en nuestro programa. Se recomienda crear un archivo .java por cada excepción creada.


Utilizar una excepción personalizada

Podemos crear una clase Cuenta:

public class Cuenta {

double saldo;

public Cuenta(double saldo) {

this.saldo = saldo;

}

public void retirar(double cantidad) throws SaldoInsuficienteException {

if (cantidad > saldo) {

throw new SaldoInsuficienteException(
"No hay suficiente saldo"
);

}

saldo -= cantidad;

}

}

Aquí comprobamos:

if (cantidad > saldo)

Si no hay suficiente dinero:

throw new SaldoInsuficienteException(...)

lanzamos nuestra excepción personalizada con el mensaje que indiquemos entra los paréntesis.

El método también indica:

throws SaldoInsuficienteException

porque puede producir esta excepción.


Capturar una excepción personalizada

Ahora podemos utilizar nuestra clase:

Cuenta cuenta = new Cuenta(100);

try {

cuenta.retirar(150);

} catch (SaldoInsuficienteException e) {

System.out.println(e.getMessage());

}

La salida será:

No hay suficiente saldo

Podemos visualizar todo el proceso:

Cuenta

saldo = 100



retirar(150)



150 > 100



throw



SaldoInsuficienteException



catch



"No hay suficiente saldo"

Ejemplo completo

Vamos a crear un pequeño programa para validar la edad de una persona.

Primero creamos nuestra excepción:

public class EdadInvalidaException extends Exception {

public EdadInvalidaException(String mensaje) {

super(mensaje);

}

}

Después creamos un método para comprobar la edad:

public static void comprobarEdad(int edad) throws EdadInvalidaException {

if (edad < 0) {

throw new EdadInvalidaException(
"La edad no puede ser negativa"
);

}

if (edad > 120) {

throw new EdadInvalidaException(
"La edad no puede ser mayor de 120"
);

}

System.out.println("Edad válida");

}

Ahora podemos utilizarlo:

public static void main(String[] args) {

try {

comprobarEdad(150);

} catch (EdadInvalidaException e) {

System.out.println(e.getMessage());

}

}

Errores frecuentes

Utilizar throw y throws indistintamente

No son lo mismo.

Recordemos:

throw

Lanza una excepción
throws

Declara que un método puede lanzarla

Utilizar try sin catch ni finally

No podemos escribir simplemente:

try {

// Código

}

Un try debe estar acompañado de al menos un:

catch

o:

finally

Por ejemplo:

try {

// Código

} catch (Exception e) {

// Controlar error

}

O:

try {

// Código

} finally {

// Código final

}

Capturar una excepción demasiado general

Podemos escribir:

catch (Exception e)

para capturar muchas excepciones.

Pero si conocemos el tipo concreto, normalmente es mejor especificarlo.

Por ejemplo:

catch (NumberFormatException e)

es más específico que:

catch (Exception e)

Esto permite controlar cada tipo de error de una manera diferente.

Por ejemplo:

try {

int numero = Integer.parseInt("hola");

} catch (NumberFormatException e) {

System.out.println("Debes introducir un número");

}

Crear excepciones personalizadas sin necesidad

No siempre necesitamos crear nuestra propia excepción.

Si Java ya dispone de una excepción adecuada, podemos utilizarla.

Por ejemplo:

throw new IllegalArgumentException(
"La edad no puede ser negativa"
);

Puede ser suficiente para un programa pequeño.

Las excepciones personalizadas son especialmente útiles cuando queremos representar errores específicos de la lógica de nuestra aplicación.


Resumen

Las excepciones permiten controlar situaciones inesperadas que pueden ocurrir durante la ejecución de un programa.

La estructura más habitual es:

try {

// Código que puede producir una excepción

} catch (Exception e) {

// Controlar la excepción

} finally {

// Código que queremos ejecutar al finalizar

}

Podemos lanzar excepciones manualmente utilizando:

throw

Por ejemplo:

throw new Exception("Ha ocurrido un error");

Y podemos indicar que un método puede lanzar una excepción utilizando:

throws

Por ejemplo:

public void comprobar() throws Exception {

}

También podemos crear nuestras propias excepciones:

public class MiExcepcion extends Exception {

public MiExcepcion(String mensaje) {

super(mensaje);

}

}

Las excepciones nos permiten evitar que cualquier problema inesperado termine directamente nuestro programa y nos permiten decidir cómo queremos responder ante cada situación.