Programación Avanzada

Programación Orientada a Objetos

Excepciones

Introducción

Las excepciones son situaciones anómalas que pueden ocurrir durante la ejecución de las aplicaciones, como por ejemplo, acceder a un posición de un vector fuera rango.

En algunos casos las excepciones no se podrán gestionar, por ejemplo los de la propia JVM, y en otros casos sí.

Java proporciona un mecanismo de gestión de excepciones en los casos en los que estemos obligados a gestionarlas. En caso de que no queramos gestionar algún tipo de excepción estaremos obligados a indicarlo explícitamente. No podremos compilar el código a menos que gestionemos la excepción, o indiquemos explícitamente que no lo queremos hacer.

La definición de excepciones propias se hace a través del mecanismo de extensión de clases, la Herencia.

Bibliografía

Contenidos

  1. Que es una excepción.
  2. Tipos de excepciones.
  3. Cómo se gestiona una excepción.
  4. Creación de excepciones propias.
  5. Pruebas unitarias sobre excepciones.
  6. Resumen.

Que es una excepción

El siguiente ejemplo de código muestra un caso muy común de excepción:

int[] array = new int[10];
System.out.println(array[10]);

Estamos intentando acceder a una posición fuera del rango del array.

Otra excepción común es intentar abrir un fichero que no existe.

FileReader fichero = new FileReader("este fichero no existe");

Que es una excepción

Las excepciones, como todo en Java durante la ejecución de nuestras aplicaciones, son instancias de clases concretas.

Java nos informa de que algo ha ido mal porque recibimos un objeto de una clase que representa un determinado error.

int[] array = new int[10];
System.out.println(array[10]);

Para informarnos del error anterior, Java nos hace llegar una instancia de la clase ArrayIndexOutOfBoundsException.

FileReader fichero = new FileReader("este fichero no existe");

En este otro caso, Java nos hace llegar una instancia de la clase FileNotFoundException.

Tipos de excepciones

Java representa las excepciones como clases. Existe una jerarquía de clases que representan errores en Java.

Tipos de excepciones

  • Excepciones irrecuperables: Hijas de Error. Son errores de la propia máquina virtual de Java.
  • Excepciones que NO es necesario gestionar: Hijas de RunTimeException. Son excepciones muy comunes, por ejemplo NullPointerException, ArrayIndexOutOfBoundsException.
  • Excepciones que es necesario gestionar: Hijas de Exception. Todas las demás, por ejemplo IOException.

Cómo se gestiona una excepción

Java proporciona un mecanismo para la gestión de excepciones: los bloques try...catch[...finally]

try{
    FileReader fichero = new FileReader("nombre del fichero");
} catch (FileNotFoundException e) {
    e.printStackTrace();
}

Como ves, el bloque finally es opcional.

Cómo se gestiona una excepción

Un buen estilo de programación implica cerrar los ficheros una vez que hemos acabado de trabajar con ellos.

FileReader fichero = null;
try{
    fichero = new FileReader("nombre del fichero");
} catch (FileNotFoundException e) {
    e.printStackTrace();
} finally {
    if(fichero != null) fichero.close();
}

Pero, el método close() también puede producir un error de tipo IOException, luego me exige un nuevo bloque try...catch.

Cómo se gestiona una excepción

Lo podemos escribir del siguiente modo:

FileReader fichero = null;
try{
    fichero = new FileReader("nombre del fichero");
} catch (FileNotFoundException e) {
    e.printStackTrace();
} finally {
    try {
        if(fichero != null)fichero.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

El bloque anterior de código es bastante obtuso.

Cómo se gestiona una excepción

El truco es el siguiente:

try {
    try {
        fichero = new FileReader("Hola");
    } finally {
        fichero.close();
    }
} catch(FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

Cómo se gestiona una excepción

Además, fíjate en el orden de los catch:

try {
    try {
        fichero = new FileReader("Hola");
    } finally {
        fichero.close();
    }
} catch(FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

FileNotFoundException es hija de IOException.

¿Funcionaría igual si cambiamos el orden?

Cómo se gestiona una excepción

¿Y si no queremos gestionar una excepción en el lugar donde se produce?

void metodo() throws IOException {
	try {
	    try {
	        fichero = new FileReader("Hola");
	    } finally {
	        fichero.close();
	    }
	} catch(FileNotFoundException e) {
	    e.printStackTrace();
	}
}

Usamos la palabra reservada throws indicando cual es la excepción que no gestionamos.

Cómo se gestiona una excepción

Desde Java 7, podemos listar varias opciones dentro del mismo bloque catch(...), y si existe relación padre-hija entre ellas, basta con indicar el padre.

try {
    try {
        fichero = new FileReader("Hola");
    } finally {
        fichero.close();
    }
} catch (IOException e) {
    e.printStackTrace();
}

La gestión de errores es más sucinta.

Cómo se gestiona una excepción

También, desde Java 7 se puede utilizar recursos en los bloques try...catch

try(FileWriter fw = new FileWriter("src/main/resources/adios.txt")) {
    fw.write("Adios");
} catch (IOException e) {
    e.printStackTrace();
}
  

No me preocupo de cerrar el fichero, ya que FileWriter, por herencia, implementa el interface Closeable cuyo único método es close() y lo invoca automáticamente la máquina virtual.

Cómo se gestiona una excepción

Podemos decir que un método tiene dos puntos de salida, el usual si no se genera ninguna excepción durante su ejecución, o devolver una excepción en el punto en que esta se produzca, si es que no la gestionamos dentro del método.

int metodo() throws IOException {
    ....
}

Este método puede devolver un entero o una excepción.

Joshua Bloch. Effective Java. Capítulo 8.

Creación de excepciones propias

Si estamos creando nuestro propio API, puede que existan casos excepcionales en los que nos interese lanzar una excepción que describe un error al utilizar nuestro API.

Java nos permite definir nuestras propias excepciones, una vez definidas, su gestión es la misma que para el resto de excepciones, los bloques try...catch.

Los pasos son:

  1. Definir una clase que represente a nuestra excepción.
  2. Lanzarla en las situaciones de error.
  3. Gestionarla en la clase cliente como cualquier otra excepción.

Creación de excepciones propias

Para definir una excepción basta con crear una clase hija de Exception:

public class TemperaturaNoValidaException extends Exception {
    public TemperaturaNoValidaException() {
        super("La temperatura no puede ser inferior a -273º Celsius");
    }
}

El texto de la excepción se puede recuperar con el método getMessage() definido en la clase Throwable.

Creación de excepciones propias

El siguiente paso es «lanzar» una instancia de la clase anterior cuando se produzca una situación anómala en nuestro API.

public class Conversor {
    public double celsiusAFharenheit(float celsius)
        throws TemperaturaNoValidaException {
        if(celsius < -273) throw new TemperaturaNoValidaException();
        return 9.0/5.0*celsius + 32;
    }
}

En este caso, ninguna temperatura puede ser inferior al cero absoluto.

Creación de excepciones propias

Finalmente, la clase cliente estará obligada a gestionar la posible excepción:

Conversor conversor = new Conversor();
try {
    conversor.celsiusAFharenheit(-300);
} catch (TemperaturaNoValidaException e) {
    e.printStackTrace();
}

Lo que veremos por consola será:

conversor.TemperaturaNoValidaException: La temperatura no puede
ser inferior a -273º Celsius
    at conversor.Conversor.celsiusAFharenheit(Conversor.java:6)
    at temperatura.Principal.main(Principal.java:10)

Creación de excepciones propias

Si queremos dar más información en el error, podemos definir un nuevo constructor:

public class TemperaturaNoValidaException extends Exception {
    public TemperaturaNoValidaException() {
        super("La temperatura no puede ser inferior a -273º Celsius");
    }

    public TemperaturaNoValidaException(double temperaturaActual) {
        super("La temperatura no puede ser inferior a -273º Celsius " +
                "la actual es " + temperaturaActual);
    }
}

Creación de excepciones propias

Y cuando lanzamos la excepción proporcionamos el valor de la temperatura errónea:

public class Conversor {
    public double celsiusAFharenheit(float celsius)
        throws TemperaturaNoValidaException {
        if(celsius < -273) throw new TemperaturaNoValidaException(celsius);
        return 9.0/5.0*celsius + 32;
    }
}

Creación de excepciones propias

La gestión de la excepción sigue siendo la misma:

Conversor conversor = new Conversor();
try {
    conversor.celsiusAFharenheit(-300);
} catch (TemperaturaNoValidaException e) {
    e.printStackTrace();
}

Y la información de la excepción más detallada:

conversor.TemperaturaNoValidaException: La temperatura no puede
ser inferior a -273º Celsius la actual es -300.0
    at conversor.Conversor.celsiusAFharenheit(Conversor.java:11)
    at temperatura.Principal.main(Principal.java:10)

Creación de excepciones propias

Aunque hemos visto con detalle cómo crear nuestras propias excepciones, no abuses de ello, siempre es mejor buscar alguna excepción ya predefinida y pasarle un mensaje descriptivo de lo que ha ido mal.

En el caso de la temperatura no valida, podemos utilizar la clase ya definida en el API de Java IllegalArgumentException y lanzar una instancia de esta clase.

public class Conversor {
    public double celsiusAFharenheit(float celsius)
    throws IllegalArgumentException {
        if(celsius < -273) throw new
            IllegalArgumentException("La temperatura no puede " +
            "ser inferior a -273º Celsius la actual es " + celsius);
        return 9.0/5.0*celsius + 32;
    }
}

Pruebas unitarias sobre excepciones

En JUnit 5 disponemos de un nuevo modo de probar que se producen excepciones, distinto de JUnit 4.

@Test
public void test() {
    Conversor conversor = new Conversor();
    assertThrows(TemperaturaNoValidaException.class, 
        () -> conversor.celsiusAFharenheit(-300));
}

Pero ten en cuenta que cualquier otra excepción hija de TemperaturaNoValidaException también pasará el test.

Resumen

La excepciones son situaciones anómalas que se pueden dar durante la ejecución de nuestras aplicaciones.

En el API de Java existen tres tipos de excepciones: las de la máquina virtual, las que no necesitamos gestionar, y las que estamos obligados a gestionar.

Para el último caso Java proporciona un mecanismo bien definido, los bloques try...catch[...finally].

Nosotros también podemos definir nuestras propias excepciones extendiendo a la clase adecuada, y el mecanismo para gestionarlas es el mismo que con las del API de Java.

Finalmente, no debes olvidar hacer las pruebas unitarias para tus excepciones.