14 - Excepciones

Mira el siguiente trozo de código:

public class Main {
    
    public static void main(String[] args) {
        Scanner reader = new Scanner(System.in);
        int dividendo, divisor;

        System.out.print("Dividendo: ");
        dividendo = reader.nextInt();
        System.out.print("Divisor: ");
        divisor = reader.nextInt();

        System.out.println("Resultado: " + (dividendo / divisor));
    }
}

La aplicación es muy sencilla. Lo único que hace es pedir dos números y mostrar por pantalla el resultado de la división entera. Si probamos nuestra aplicación con 6 y 4, por ejemplo, todo funciona como toca:

Dividendo: 6
Divisor: 4
Resultado: 1

¿Qué pasa si probamos con 6 y 0?:

Dividendo: 6
Divisor: 0
Exception in thread "main" java.lang.ArithmeticException: / by zero
        at ejemplos.T12.Main.main(Main.java:16)

La aplicación muestra un error, ya que estamos intentando dividir entre 0. Lo importante es el mensaje del error: Exception in thread…. Una excepción es una situación que ocurre durante la ejecución del código y finaliza la ejecución de forma anormal, por ejemplo, al tratar de dividir un número entero entre cero.

La diferencia entre una excepción y un error es que las primeras las podemos (la mayoría las debemos) detectar y tratar en nuestro código, mientras un error no se pueden manejar en tiempo de ejecución (por ejemplo, el programa requiere más memoria de la disponible).

Todos los tipos de excepciones y errores son subclases de la clase Throwable , que es la clase base de la jerarquía. Una rama está encabezada por Exception. Esta clase se utiliza para condiciones excepcionales que los programas de usuario deberían detectar. Otra rama, Error, es utilizada por el sistema de tiempo de ejecución de Java (JVM) para indicar errores que tienen que ver con el entorno de tiempo de ejecución en sí (JRE).

En Java, dentro de las excepciones tenemos dos clases:

  • Excepciones marcadas (checked): Son las excepciones que ocurren en tiempo de compilación. Son todas aquellas clases que heredan de la clase Exception, excepto RuntimeException y sus derivadas.
  • Excepciones no marcadas (unchecked): Son las excepciones que ocurren en tiempo de ejecución. Son aquellas clases que heredan de la clase RuntimeException.

Casi todos los lenguajes de programación ofrecen herramientas para capturar y tratar excepciones. Una de las más comunes es el bloque try…catch. Lo que se hace es encerrar el código susceptible de lanzar una excepción dentro del bloque try y, si ocurre una excepción, capturarla en el bloque catch.

Por ejemplo, vamos a modificar nuestro código anterior usando un bloque try…catch:

        try {
            System.out.println("Resultado: " + (dividendo / divisor));            
        } catch (Exception e) {
            System.out.println("Error en la división");
        }

Dividendo: 6
Divisor: 0
Error en la división

Como ves, el programa intenta ejecutar el bloque de código encerrado en el try y, si salta alguna excepción, se ejecuta el código encerrado en el bloque catch.

Existe otro bloque opcional que podemos añadir al try…catch: el bloque finally. Este bloque se ejecutará siempre pase lo que pase. Es decir, si el código encerrado en el bloque try lanza una excepción o no. Por ejemplo, fíjate como siempre se muestra por pantalla la frase “Este código se ejecutará siempre” del siguiente código:

        try {
            System.out.println("Resultado: " + (dividendo / divisor));            
        } catch (Exception e) {
            System.out.println("Error en la división");
        } finally {
            System.out.println("Este código se ejecutará siempre");
        }

Dividendo: 6
Divisor: 0
Error en la división
Este código se ejecutará siempre

Dividendo: 6
Divisor: 4
Resultado: 1
Este código se ejecutará siempre

El bloque finally es, en ciertas ocasiones, muy útil. Por ejemplo, si abrimos un fichero para operar con él, podemos poner la orden de cerrar el fichero dentro del bloque finally para asegurarnos que, pase lo que pase, el fichero se cerrará (o desconectar con un bbdd, por ejemplo).

Una de las diferencias fundamentales entre las excepciones checked y las unchecked es que, si ejecutamos algún código que pueda lanzar una excepción de tipo checked, Java nos obliga a encerrarlo entre un bloque try…catch, mientras que con las segundas es opcional.

Esto es importante a la hora de lanzar excepciones en nuestros métodos, como veremos más adelante.

Vamos a volver a nuestro primer ejemplo y ejecutar la aplicación con los siguientes datos:

Dividendo: 6
Divisor: 1.5
Exception in thread "main" java.util.InputMismatchException
        at java.base/java.util.Scanner.throwFor(Scanner.java:939)
        at java.base/java.util.Scanner.next(Scanner.java:1594)
        at java.base/java.util.Scanner.nextInt(Scanner.java:2258)
        at java.base/java.util.Scanner.nextInt(Scanner.java:2212)
        at ejemplos.T12.Main.main(Main.java:14)

En este caso, hemos intentado almacenar en una variable de tipo entero el número 1.5, con lo que Java nos muestra la excepción correspondiente. Si encerramos todo nuestro código en el bloque try..catch, veremos como nos muestra la frase “Error en la división” siempre que se capture una excepción (sea del tipo que sea):

        try {
            System.out.print("Dividendo: ");
            dividendo = reader.nextInt();
            System.out.print("Divisor: ");
            divisor = reader.nextInt();
            System.out.println("Resultado: " + (dividendo / divisor));            
        } catch (Exception e) {
            System.out.println("Error en la división");
        } finally {
            System.out.println("Este código se ejecutará siempre");
        }

Dividendo: 6
Divisor: 0
Error en la división
Este código se ejecutará siempre

Dividendo: 6
Divisor: 1.5
Error en la división
Este código se ejecutará siempre

A menudo, nos interesa hacer diferentes cosas según el tipo de excepción que salte. Si te fijas en el bloque catch, hay una objeto (e) de tipo Exception que se le pasa al bloque:

        try {
        
            ...
            
        } catch (Exception e) {
            
            ...
            
        } 

La clase Exception tiene varios métodos muy útiles (derivados de sus clases antecesoras Trhowable y Object) que nos pueden servir para tratar las excepciones, entre ellos:

  • getMessage(): Obtenemos el mensaje asociado a la excepción.
  • getClass(): Obtenemos la clase de la excepción. Si lo usamos tal cual, la clase será del tipo “class java.lang…”. Para obtener el nombre simple de la clase podemos usar el método getCanonicalName().
  • getStackTrace(): Devuelve la traza de la excepción.

Por ejemplo, vamos a mostrar el mensaje y la clase de nuestro excepción:

        try {
            System.out.print("Dividendo: ");
            dividendo = reader.nextInt();
            System.out.print("Divisor: ");
            divisor = reader.nextInt();
            System.out.println("Resultado: " + (dividendo / divisor));            
        } catch (Exception e) {
            System.out.println(e.getClass().getCanonicalName());
            System.out.println(e.getMessage());
            System.out.println("Error en la división");
        } finally {
            System.out.println("Este código se ejecutará siempre");
        }

Dividendo: 6
Divisor: 0
java.lang.ArithmeticException
Error en la división
Este código se ejecutará siempre

Dividendo: 6
Divisor: 1.5
java.util.InputMismatchException
null
Error en la división
Este código se ejecutará siempre

De esta forma, podríamos personalizar las acciones a tomar según el tipo de excepción con un simple if:

        try {
            System.out.print("Dividendo: ");
            dividendo = reader.nextInt();
            System.out.print("Divisor: ");
            divisor = reader.nextInt();
            System.out.println("Resultado: " + (dividendo / divisor));            
        } catch (Exception e) {
            if (e.getMessage()!= null && e.getMessage().equals("/ by zero")) {
                System.out.println("El divisor no puede ser 0");
            } else if(e.getClass().getCanonicalName().equals("java.util.InputMismatchException")) {
                System.out.println("Ha introducido algún dato erróneo");
            } else {
                System.out.println("Error desconocido");
            }
        }

Dividendo: 6
Divisor: 0
El divisor no puede ser 0

Dividendo: 6
Divisor: 1.5
Ha introducido algún dato erróneo

En cualquier caso, existe un método mejor para personalizar nuestras acciones según el tipo de excepción que capturemos: anidar bloques catch:

        try {
            System.out.print("Dividendo: ");
            dividendo = reader.nextInt();
            System.out.print("Divisor: ");
            divisor = reader.nextInt();
            System.out.println("Resultado: " + (dividendo / divisor));            
        } catch (InputMismatchException e) {
            System.out.println("Entrada incorrecta");
        } catch (ArithmeticException e) {
            System.out.println("División por 0");
        } catch (Exception e) {
            System.out.println("Error en la división");
        }

Nuestra aplicación nunca debería hacer un volcado de pila con los datos de la excepción, ya que estamos dando información que puede ser utilizada por un usuario malintencionado. Si te fijas, la última excepción capturada en el bloque anterior es de tipo Exception (de cualquier tipo), con lo que nos aseguramos de capturar todas las excepciones posibles y actuar en consecuencia.
El tratamiento de excepciones puede ser muy tedioso. Por suerte, frameworks como Spring nos dan herramientas para simplificar la tarea, como veremos en segundo.

Además de poder capturar excepciones, también podemos lanzarlas para que sean capturadas en otra parte del programa. Para lanzar una nueva excepción, tenemos que crear un nuevo objeto del tipo de excepción que queramos y lanzarla mediante la palabra reservada trhow. Además, podemos añadir un mensaje como descripción de la excepción:

throw new Exception("Mensaje de la excepción");

Por ejemplo, vamos a simular un error y lanzar una excepción dentro de un bloque try…catch para poder capturarla:

public class Main {
    
    public static void main(String[] args) {
        
        try {
            System.out.println("Ocurre un error");
            throw new Exception("Ha ocurrido un error en la aplicación");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }

    }
}

Ocurre un error
Ha ocurrido un error en la aplicación

En este caso, hemos lanzado una excepción de la clase general Exception, pero podríamos haber lanzado cualquier tipo de excepción. Aquí puedes ver los tipos de excepciones que tiene Java por defecto.

A menudo, nos es muy útil que algunos métodos de nuestras clases lancen excepciones si ocurre algún error. El procedimiento es el mismo que el anterior, con una pequeña modificación en la cabecera del método si la excepción no es de tipo unchecked. Por ejemplo, vamos a crear un método que lo único que hará será mostrar por pantalla un mensaje y lanzar una excepción:

public class Main {
    
    public static void metodoExcepcion(){
        System.out.println("Método que lanza una excepción");
        throw new Exception("Mensaje de la excepción");
    }

    public static void main(String[] args) {
        
        metodoExcepcion();
    }
}

Si dejamos el código como está, verás que el compilador te muestra un error. Eso es debido a que debemos indicar en la cabecera del método que éste puede lanzar una excepción si ocurre algún tipo de error. Ten en cuenta que estamos lanzando una excepción de tipo genérica, con lo que Java nos obliga a tratarla como si fuera de tipo checked, ya que de ella dependen tanto las excepciones checked como las unchecked. Para eso, solo debemos trhows tipoExcepcion en la cabecera del método:

public class Main {
    
    public static void metodoExcepcion() throws Exception{
        System.out.println("Método que lanza una excepción");
        throw new Exception("Mensaje de la excepción");
    }

    public static void main(String[] args) {
        
        metodoExcepcion();
    }
}

Todavía nos falta algo para que la aplicación funcione. Como hemos dicho, al lanzar una excepción de tipo checked (o genérica), debemos tratarla, con lo que tenemos que encerrar el código donde llamamos al método en un bloque try…catch:

public class Main {
    
    public static void metodoExcepcion() throws Exception{
        System.out.println("Método que lanza una excepción");
        throw new Exception("Mensaje de la excepción");
    }

    public static void main(String[] args) {
        
        try {
            metodoExcepcion();
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }

    }
}

Si utilizamos excepciones de tipo unchecked, no hace falta añadir nada a la cabera del método (aunque podemos seguir haciéndolo) ni encerrar la llamada al método en un bloque try…catch. Por ejemplo, vamos a modificar el tipo de excepción a RuntimeException, que es de tipo unchecked:

public class Main {

    public static void metodoExcepcion() {
        System.out.println("Método que lanza una excepción");
        throw new RuntimeException("Excepción en tiempo de ejecución");
    }

    public static void main(String[] args) {

        metodoExcepcion();

    }
}

Como ves, ahora podemos llamar al método de forma normal (sin encerrarlo en un bloque try…catch, aunque de esta forma, la salida de la aplicación será un error del compilador de Java, ya que no estamos capturando la posible excepción:

Método que lanza una excepción
Exception in thread "main" java.lang.RuntimeException: Excepción en tiempo de ejecución
        at ejemplos.T12_2.Main.metodoExcepcion(Main.java:9)
        at ejemplos.T12_2.Main.main(Main.java:27)

Un método puede lanzar diferentes tipos de excepciones. Para indicarlo, debemos añadir la lista de excepciones en la cabecera separada por comas:
    public void miMetodo() throws ExceptionInInitializerError, ArithmeticException, RuntimeException ...{
       
       ...
       
    }

Las excepciones son uno de los métodos más útiles para tratar los errores conocidos de nuestra aplicación. Por ejemplo, vamos a hacer una aplicación con una clase principal y otra llamada Trabajador. La clase Trabajador solo tendrá dos propiedades (nombre y edad), sus setters y el método toString(). En la clase principal, crearemos un nuevo trabajador, le asignaremos un nombre y edad y lo mostraremos por pantalla:

public class Trabajador {
 
    private String nombre;
    private int edad;

    public void setNombre(String nombre) {
        this.nombre = nombre;
    }

    public void setEdad(int edad) {
        this.edad = edad;
    }

    @Override
    public String toString(){
        return "Nombre: " + this.nombre +
            "\nEdad: " + this.edad;
    }
}

public class Main {
  
    public static void main(String[] args) {
        Trabajador trabajador1 = new Trabajador();

        trabajador1.setNombre("Pepe");
        trabajador1.setEdad(54);

        System.out.println(trabajador1.toString());
    }
}

Nombre: Pepe
Edad: 54

Como ves, el código es muy sencillo y todo funciona como toca. Vamos a modificar la edad del trabajador:

    public static void main(String[] args) {
        Trabajador trabajador1 = new Trabajador();

        trabajador1.setNombre("Pepe");
        trabajador1.setEdad(999);

        System.out.println(trabajador1.toString());
    }

Nombre: Pepe
Edad: 999

La aplicación sigue funcionando como toca, aunque no parece que tenga mucho sentido tener un trabajador de 999 años (por lo menos, por ahora). Podríamos comprobar la edad (entre 18 y 65 años, por ejemplo) en el método setEdad() y devolver un valor que represente el error (por ejemplo, devolvemos -1). Este enfoque, nos obliga a cambiar la cabecera del método (para indicar que ahora devolveremos un entero) y devolver cualquier otro valor (0, por ejemplo) si todo ha ido bien:

    public int setEdad(int edad) {
        if (edad<18 || edad>65) {
            return -1;
        }
        this.edad = edad;
        return 0;
    }

En nuestra clase principal, podríamos comprobar el valor devuelto por el método y actuar en consecuencia:

        if(trabajador1.setEdad(99) == -1) {
            System.out.println("La edad debe estar comprendida entre 18 y 65 años");
        }

Aparte del hecho de no tener mucho sentido el que un método setter devuelva algo, también tenemos otro problema. ¿Y si tenemos otro método que puede devolver diferentes tipos de errores? En ese caso, nos obligaría a ir añadiendo códigos diferentes de error, con la complejidad que eso añade a nuestro código.

Si usamos excepciones, el código se simplifica, ya que podemos lanzar diferentes tipos en nuestros métodos según el error que queramos capturar:

    public void setEdad(int edad) throws Exception {
        if(edad < 18) {
            throw new Exception("La edad debe ser superior a 18 años");
        } else if (edad > 65) {
            throw new Exception("La edad no puede ser mayor que 65 años");
        }
        this.edad = edad;
    }

Podemos crear nuestras propias excepciones con el código que queramos. Para eso, lo normal es crear una clase que herede de la clase Exception:

public class ExceptionEdad extends Exception{

    private int code;

    public ExceptionEdad(String message, int code) {
        super(message);
        this.code = code;
    }

    public int getCode(int code) {
        return this.code;
    }  
}

En este caso, hemos creado una clase ExceptionEdad para comprobar la edad de los trabajadores. Además del mensaje de la excepción, añadimos otra propiedad code para asignarle un código diferente a cada excepción (fíjate que en el constructor de la clase, usamos super(message) para llamar al constructor de la clase Exception). Ahora, ya podemos usar la nueva clase para lanzar excepciones de ese tipo:


    public void setEdad(int edad) throws ExceptionEdad {
        if(edad < 18) {
            throw new ExceptionEdad("La edad debe ser superior a 18 años", 1);
        } else if (edad > 65) {
            throw new ExceptionEdad("La edad no puede ser mayor que 65 años", 2);            
        }
        this.edad = edad;
    }

Aunque la excepción que hemos creado es sencilla (sólo añadimos el mensaje de error), podríamos añadir toda la complejidad que queramos. Por ejemplo, podríamos tener sólo el código de la excepción (sin el mensaje) y almacenar los mensajes en un fichero aparte en diferentes idiomas. A la hora de mostrar el mensaje de la excepción, podríamos mirar el código y mostrarlo en el idioma en el que esté configurada nuestra aplicación.

Aunque podemos tratar las excepciones en cualquier lugar, lo mejor es tratarlas en las clases más generales (en nuestro caso, en la clase Main). El resto de métodos de las demás clases, solo se deberían dedicar a lanzar excepciones si ocurre algún error controlable.

¿Qué pasa con las excepciones checked? Como hemos dicho, Java nos obliga a tratar esas excepciones cuando utilizamos algún método que pueda lanzar una excepción de ese tipo. Por ejemplo, a la hora de leer un fichero con algunas clases de Java (FileReader, BufferReader…) debemos encerrar ese código en un bloque try…catch. Si hacemos, por ejemplo, una clase para leer ficheros y usamos esas clases, en sus métodos estaremos capturando las excepciones de forma obligatoria.

Una forma común de solucionar ese problema para poder tratar las excepciones en las clases superiores, es propagar esa excepción hacia arriba:

public class Fichero {
    
    public void read(String path) throws Exception {

        try {
            File file = new File(path);
            FileReader fileReader = new FileReader(file);                
        } catch (Exception e) {
            throw new Exception("No se puede leer el fichero", e);
        }
    }
}

Fíjate que a la hora de crear la nueva excepción, usamos un constructor con un mensaje y la excepción que hemos capturado (recuerda que una clase puede tener varios constructores con diferentes parámetros). Ésto lo hacemos para no perder la traza de la excepción original.

Uno de los problemas de las excepciones en Java es que muchas API lanzan excepciones checked cuando deberían ser unchecked. Una posible solución es transformar esa excepción checked en un checked cuando la propagamos hacia los métodos superiores:

public class Fichero {

    public void read(String path) throws RuntimeException {

        try {
            File file = new File(path);
            FileReader fileReader = new FileReader(file);                
        } catch (Exception e) {
            throw new RuntimeException("No se puede leer el fichero", e);
        }
    }
}

El código anterior es a modo de ejemplo. Ten en cuenta que las excepciones checked deberían usarse cuando el programa no tiene ningún tipo de error, pero ocurre una situación excepcional. En el ejemplo anterior, si, por ejemplo, no existe el fichero, no quiere decir que el programa tenga errores (se puede haber borrado fuera de la aplicación, con lo que no es responsabilidad del programa), con lo que sí que deberíamos lanzar una excepción checked.

Si quieres saber más sobre el uso correcto de las excepciones en Java, puedes mirar este enlace a una web de un curso de Hibernate de Lorenzo González donde está mucho más detallado el tema (el curso es muy completo, pero el nivel es bastante avanzado, con lo que deberías echarle un vistazo cuando tengas más conocimiento sobre Java).

Antes hemos visto como podemos usar el bloque finally para, por ejemplo, cerrar un recurso como BufferedReader o FileReader. A partir de Java 7, se añadió la sentencia try-with-resources para cerrar automáticamente los recursos.

Por ejemplo, si queremos leer las líneas de un archivo (el tema de ficheros lo veremos en el punto siguiente), una de las forma convencional de hacerlo antes de Java 7 era:

    public static void main(String[] args) {
        String path = "path_archivo";
        String line;
        BufferedReader br = null;

        try {
            br = new BufferedReader(new FileReader(path));
            while((line = br.readLine()) != null){
                System.out.println(line);
            }
        } catch (Exception e) {
            e.getStackTrace();
        } finally {
            if (br != null) 
                try {
                    br.close();                    
                } catch (Exception e) {
                    e.getStackTrace();
                }
        }
    }

Tenemos que añadir el bloque finally para asegurarnos de cerrar el recurso (lo que puede dar otra excepción, de ahí el nuevo bloe try).

A partir de Java 7, podemos aprovechar la nueva construcción para simplificar el código:

        try (BufferedReader br = new BufferedReader(new FileReader(path))) {
            while((line = br.readLine()) != null){
                System.out.println(line);
            }            
        } catch (Exception e) {
            e.getStackTrace();
        }

Como ves, el código queda mucho más limpio, ya que el bloque try-with-resources ya se encarga de cerrar automáticamente los recursos.

Ejercicio 1a

Haz una aplicación que pida un número por pantalla y muestre el resultado de multiplicarlo por 2. Si ocurre alguna excepción (el número introducido no tiene el formato correcto, por ejemplo) la aplicación deberá mostrar el mensaje de error “Algo ha salido mal”.

Ejercicio 1b

Modifica la aplicación anterior para que se cierre siempre el objeto de tipo Scanner pase lo que pase.

Ejercicio 2a

Escribe una aplicación que pida al usuario su edad por pantalla. Crea un método que devuelva true o false según la edad sea mayor de 18 o menor. Muestra por pantalla la frase “Puedes pasar” o “Tienes que ser mayor de edad para pasar” según el resultado del método (sin utilizar excepciones). Si ocurre alguna excepción, la aplicación mostrará el mensaje “Algo ha salido mal”.

Ejercicio 2b

Modifica la aplicación anterior para que el método que comprueba la edad lance una excepción de tipo Exception si la edad es menor de 18 años. En el método main(), Si ocurre alguna excepción (del tipo que sea), la aplicación mostrará el mensaje “Tienes que ser mayor de edad para pasar”.

Ejercicio 2c

Elimina el bloque try..catch del método main(). ¿Por qué el compilador muestra un error? ¿Cómo lo puedes solucionar?

Ejercicio 2d

Cambia el tipo de excepción que devuelve el método que comprueba la edad a RuntimeException y vuelve a eliminar el bloque try..catch del método main(). ¿Por qué ahora el compilador sí que te deja ejecutar el programa?

Ejercicio 2e

Vuelve a capturar las excepciones en el main() (deja la excepción del método que comprueba la edad como RuntimeException) y muestra la frase “Puedes pasar” si la edad es mayor que 18 años, “Tienes que ser mayor de edad para pasar” si la edad es inferior a 18 años o “La edad tiene que ser un entero” si se introduce algo diferente a un entero.

Ejercicio 3a

Modifica la aplicación anterior para que se muestre la frase “No puedes pasar” si la edad introducida es menor de 18 años o mayor que 65. Para eso, lanza una excepción de tipo RuntimeException en el método que comprueba la edad si se cumple alguna de esas condiciones.

Ejercicio 3b

Modifica la aplicación anterior para que el mensaje de la excepción sea “Tienes que ser mayor de edad para pasar” o “Tienes que tener menos de 65 años para pasar” según el tipo de error. Utiliza el método correspondiente de la clase Exception para mostrar ese mensaje de error.

Ejercicio 3c

Crea dos excepciones de tipo Exception dentro de un nuevo package llamado exception. La primera será AgeLowerException y su mensaje será “Tienes que ser mayor de edad para pasar”. La segunda será AgeTopException y su mensaje “Tienes que tener menos de 65 años para pasar”.

Haz que el método que comprueba la edad en la aplicación principal lance esos tipos de excepciones según el caso.

Captura en la aplicación principal ambos tipos de excepciones y si el usuario introduce un dato incorrecto.

Ejercicio 3d

Modifica la aplicación anterior para que si ocurre alguna excepción no prevista se muestre el mensaje “Ha ocurrido un error inesperado”.

Ejercicio 3e

Cambia el tipo de excepciones creadas anteriormente a RuntimeException. ¿Puedes cambiar algo en la clase principal?

Ejercicio 4

Crea una aplicación mediante la arquitectura por capas que estamos viendo en este curso. La aplicación operará con dos recursos: libros y autores.

Listado de libros:

id Título id del autor
1 El nombre de la rosa 1
2 El señor de los anillos 2
3 El hobbit 2
4 La fundación 3

Listado de autores:

id Nombre
1 Umberto Eco
2 J. R. R. Tolkien
3 Isaac Asimov
4 Alejandro Dumas

Desde nuestra clase principal (App) se llamarán a los controladores correspondientes para ejecutar las siguientes acciones:

  • Mostrar el listado de libros
  • Mostrar un libro por id
  • Añadir un libro
  • Mostrar listado de libros de un autor según su id
  • Mostrar el listado de autores
  • Borrar un autor

A la hora de ejecutar las acciones tienes que tener en cuenta las siguientes restricciones:

  • Si no se encuentra el recurso (libro o autor) deberá mostrar el correspondiente mensaje de error
  • Al añadir un libro, si el id del autor pasado no es válido (no existe), deberá mostrar el mismo mensaje de error anterior (“Autor no encontrado” o similar)
  • Al mostrar el listado de libros, deberá comprobar que el id del autor existe (y, si no es así, mostrará el mensaje de error correspondiente)
  • Cuando se borre un autor deberá comprobar dos cosas: que el id del autor existe y que no tenga libros asociados. Si tiene algún libro asociado, deberá mostrar un error indicando primero que elimine sus libros

Haz toda la gestión de errores mediante excepciones en los métodos que creas convenientes.

  • clase/daw/prog/3eval/excepciones.txt
  • Última modificación: 2024/02/09 09:16
  • por cesguiro