Skip to content

1 Excepciones

Introducción

Uno de los problemas más importantes al escribir aplicaciones es el tratamiento de los errores. Los errores detienen la ejecución del programa e impiden su desarrollo normal y, lo peor, además provocan que el usuario esté desinformado. Los programadores tiene que reconocer las situaciones que pueden provocar el fin de la ejecución normal del programa por un error no controlado. Dicho de otra forma, todos los posibles errores en un programa deben de estar controlados. A veces es imposible evitarlos (por ejemplo no hay papel en la impresora, o falla el disco duro), pero sí reaccionar de forma lógica para que el usuario reconozca lo que está ocurriendo

Java nos echa una mano para ello a través de las excepciones. Se denomina excepción a un hecho que podría provocar la detención del programa; es decir una condición de error en tiempo de ejecución pero que puede ser controlable (a través de los mecanismos adecuados). En java sin embargo se denomina error a una condición de error incontrolable (ejemplos son el error que ocurre cuando no se dispone de más memoria o errores de sintaxis). Los errores de sintaxis son detectados durante la compilación, pero los errores de ejecución pueden provocar situaciones irreversibles, su control debe hacerse también en tiempo de ejecución y eso siempre ha sido problemático para la programación de aplicaciones.

En Java se puede preparar el código susceptible a provocar excepciones de modo que si ocurre una excepción, el código es lanzado (throw) a una determinada rutina previamente preparada por el programador, que permite manipular esa excepción. Si la excepción no fuera capturada, la ejecución del programa se detendría irremediablemente (en muchas ocasiones la propia sintaxis de Java impide que la excepción no sea controlada; es decir, obliga a controlarla)

Try-catch

El control de las excepciones se realiza mediante las sentencias try y catch.

try{
    // instrucciones que puedan provocar la excepción
} catch(ClassException1 objetoQueLoCaptura){
    // instrucciones que se ejecutan si hay un error de tipo ClassException1
} catch(ClassException2 objetoQueLoCaptura){
    // instrucciones que se ejecutan si hay un error de tipo ClassException2
}
// ...

Como se puede observar, puede haber más de una sentencia catch para un mismo bloque try debido a que un bloque de código puede ser susceptible a provocar varios tipos diferentes de excepciones.

Dentro del bloque try se colocan las instrucciones susceptibles de provocar una excepción, el bloque catch sirve para capturar esa excepción y evitar el fin de la ejecución del programa. Desde el bloque catch se maneja, en definitiva, la excepción.

Cada catch maneja un tipo de excepción. Cuando se produce una excepción, se busca el catch que posea el manejador de excepción adecuado, será el que utilice el mismo tipo de excepción que se ha producido. La búsqueda del catch se realiza en el orden en que se han escrito. Si se produce una excepción, primero se mira si cuadra en el primer catch. Si no cuadra, se pasa al siguiente, y así sucesivamente. Por este motivo, es importante el orden en que se coloquen los bloques catch.

Cuando acaba la ejecución del bloque catch, el programa continúa con la ejecución del código que le sigue al bloque del try-catch.

public class TryCatch {
    public void show(){
        final String FIN = "fin";
        int base, exponent;
        String baseString;
        Scanner keyboard = new Scanner(System.in);

        try{
            System.out.println("Bienvenido al programa para calcular la potencia");
            System.out.print("Introduce la base o fin para terminar");
            baseString = keyboard.nextLine();
            if(!baseString.equalsIgnoreCase(FIN)){
                base = Integer.parseInt(baseString);
                System.out.print("Introduce el exponente: ");
                exponent = keyboard.nextInt();
                System.out.printf("%d elevado a %d es igual a %d\n", base, exponent, (int) Math.pow(base, exponent));
            }
        } catch (NumberFormatException e){
            System.out.println("Error en la base");
        } catch (InputMismatchException e){
            System.out.println("Error en el exponente");
        }
    }

    public static void main(String[] args) {
        new TryCatch().show();
    }
}

Para la base, si el usuario no introduce fin y tampoco introduce un número entero, el Integer.parseInt al intentar convertir la cadena a número lanzará una excepción de tipo NumberFormatException y será manejada por el catch correspondiente. Para el exponente, si se introduce algo que no sea un número (una letra, un símbolo...), se producirá una excepción de tipo InputMismatchException y se manejará por su correspondiente catch. Si se produce un error de otro tipo, el programa se detendrá.

Puede ser que el programador quiera mostrar el mismo mensaje de error para ambas excepciones. En este caso se utiliza el multi-catch, incorporado en Java desde la versión 7:

public class MultiCatch {
    public void show(){
        final String FIN = "fin";
        int base, exponent;
        String baseString;
        Scanner keyboard = new Scanner(System.in);

        try{
            System.out.println("Bienvenido al programa para calcular la potencia");
            System.out.print("Introduce la base o fin para terminar");
            baseString = keyboard.nextLine();
            if(!baseString.equalsIgnoreCase(FIN)){
                base = Integer.parseInt(baseString);
                System.out.print("Introduce el exponente: ");
                exponent = keyboard.nextInt();
                System.out.printf("%d elevado a %d es igual a %d\n", base, exponent, (int) Math.pow(base, exponent));
            }
        } catch (NumberFormatException | InputMismatchException e){
            System.out.println("Error en la base o en el exponente");
        }
    }

    public static void main(String[] args) {
        new MultiCatch().show();
    }
}

En la programación de aplicaciones en general siempre ha habido dos formas de manejar una excepción:

  • Interrupción: en este caso se asume que el programa ha encontrado un error irrecuperable. La operación que dio lugar a la excepción se anula y se entiendo que no hay manera de regresar al código que provocó la excepción.
  • Reanudación: se puede manejar el error y regresar al código que provocó el error.

La filosofía de Java es del tipo interrupción, pero se puede intentar emular la reanudación encerrando el bloque try en un bucle que se repetirá hasta que el error deje existir:

public class Resumption {
    public void show(){
        byte number = 0;
        Scanner keyboard = new Scanner(System.in);
        boolean error = false;

        do{
            try{
                System.out.print("Introduce un número de tipo byte, es decir, entre -128 y 127: ");
                number = keyboard.nextByte();
                System.out.printf("Valor del número: %d\n", number);
                error = false; // Si se ha entrado antes en el catch, error está a true
            } catch (InputMismatchException e){
                System.out.println("Error");
                error = true;
                keyboard.nextLine(); // Limpieza de buffer
            }
        } while (error);
    }

    public static void main(String[] args) {
        new Resumption().show();
    }
}

Si el usuario introduce cualquier cosa que no sea un número, el método nextByte() lanza la excepción InputMismatchException y lo que ha metido el usuario se queda en el buffer. Para solucionarlo, se realiza una limpieza del buffer con keyboard.nextLine().

Métodos de las excepciones

  • getMessage: obtiene el mensaje descriptivo de la excepción.
  • toString: devuelve una cadena sobre la situación de la excepción. Suele indicar la clase de excepción y el texto de getMessage.
  • printStackTrace: escribe el método y mensaje de la excepción (la llamada información de pila). El resultado es el mismo mensaje que muestra el ejecutor (la máquina virtual de Java) cuando no se controla la excepción.
public class MethodsOfExceptions {
    public void show(){
        final String FIN = "fin";
        int base, exponent;
        String baseString;
        Scanner keyboard = new Scanner(System.in);

        try{
            System.out.println("Bienvenido al programa para calcular la potencia");
            System.out.print("Introduce la base o fin para terminar");
            baseString = keyboard.nextLine();
            if(!baseString.equalsIgnoreCase(FIN)){
                base = Integer.parseInt(baseString);
                System.out.print("Introduce el exponente: ");
                exponent = keyboard.nextInt();
                System.out.printf("%d elevado a %d es igual a %d\n", base, exponent, (int) Math.pow(base, exponent));
            }
        } catch (NumberFormatException e){
            System.out.println(GREEN + e.getMessage() + RESET);
            System.out.println(CYAN + e + RESET); // e.toString()
            e.printStackTrace();
        } catch (InputMismatchException e){
            System.out.println(GREEN + e.getMessage() + RESET);
            System.out.println(CYAN + e + RESET); // e.toString()
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        new MethodsOfExceptions().show();
    }
}

Cuando se debe capturar una excepción

Una excepción se debe capturar cuando no sea un error de programación, es decir, el programador debe distinguir si esa excepción es un error suyo de programación o no, poeque si es un error de programación, no debe capturar el error sino solucionarlo.

Por ejemplo, cuando se intenta acceder a una parte de la cadena que no existe, por ejemplo, si la cadena hola le hacemos charAt(4), Java nos lanza la excepción StringIndexOutOfBoundsException. El programador no debe capturar con un try-catch, sino que debe corregir el error de programación que ha producido dicha excepción.