Sesión 6

1. Vamos a construir un ejercicio completo de uso de excepciones. Para ello seguiremos los pasos que se indican a continuación:

  1. Antes de comenzar, lee la introducción del punto 2.1 (Excepciones), y los apartados 2.1.1 (Tipos de excepciones) y 2.1.2 (Captura de excepciones) del tema 2 de teoría.
  2. Echa un vistazo a la clase Ej1.java que se proporciona en la plantilla. Verás que calcula la división de dos números (en el método divide), y se ejecuta dicha división en el método main, pasándole los dos números como argumentos en el parámetro args de dicho método.
  3. Compila y prueba el funcionamiento de la clase, ejecutando una división sencilla, como por ejemplo 20 / 4, de la siguiente forma:
javac Ej1.java
java Ej1 20 4
  1. Observa que la clase no realiza ciertos controles: no se comprueba si se le han pasado parámetros al main al ejecutar, ni si dichos parámetros son números correctos, ni si se puede realizar la división (no se podría dividir por un número que fuese cero, por ejemplo). Vamos a ir controlando dichos errores mediante lanzamiento y captura de excepciones, en los siguientes pasos.
  2. Dentro del método main vamos a comprobar que se hayan pasado 2 parámetros al programa, y que dichos dos parámetros sean dos números.
    1. Para lo primero (comprobar que se han pasado 2 parámetros), capturaremos una excepción de tipo ArrayIndexOutOfBoundsException, al acceder al parámetro args:
      public static void main(String[] args)
      {
      	String param1=null, param2=null;
      	int dividendo=0, divisor=0;
      
      	try
      	{
      		param1 = args[0];
      		param2 = args[1];
      	} catch (ArrayIndexOutOfBoundsException e) {
      		System.out.println ("Faltan parametros");
      		System.exit(-1);
      	}
      
      	...
      
      	System.out.println ("Resultado: " + divide(dividendo, divisor));
      }

      Prueba después la excepción capturada, ejecutando el programa con algo como:

      java Ej1 20 

      Debería capturar la excepción y mostrar por pantalla el mensaje "Faltan parametros".

    2. Para lo segundo (comprobar que los dos parámetros son números), capturaremos una excepción de tipo NumberFormatException si falla el método de conversión a entero de los parámetros de tipo String (es decir, capturaremos la excepción al llamar a Integer.parseInt(...)):
      public static void main(String[] args)
      {
      	String param1=null, param2=null;
      	int dividendo=0, divisor=0;
      
      	...
      
      	try
      	{
      		dividendo = Integer.parseInt(param1);
      		divisor = Integer.parseInt(param2);
      	} catch (NumberFormatException e2) {
      		System.out.println ("Formato incorrecto del parametro");
      		System.exit(-1);
      	}
      
      	System.out.println ("Resultado: " + divide(dividendo, divisor));
      }

      Prueba después la excepción capturada, ejecutando el programa con algo como:

      java Ej1 20 hola

      Debería capturar la excepción y mostrar por pantalla el mensaje "Formato incorrecto del parametro".

    3. Observa que se puede poner todo junto, ahorrándonos las variables param1 y param2, y capturando las dos excepciones en un solo bloque:
      public static void main(String[] args)
      {
      	int dividendo=0, divisor=0;
      
      	try
      	{
      		dividendo = Integer.parseInt(args[0]);
      		divisor = Integer.parseInt(args[1]);
      	} catch (ArrayIndexOutOfBoundsException e) {
      		...
      	} catch (NumberFormatException e2) {
      		...
      	}
      
      	System.out.println ("Resultado: " + divide(dividendo, divisor));
      }

      Modifica el método main para dejarlo todo en un solo bloque try, y vuelve a probar los distintos casos de fallo indicados antes, para ver que el programa sigue comportándose igual.

  3. Lee ahora el punto 2.1.3 (Lanzamiento de excepciones) del tema 2 de teoría.
  4. Una vez tenemos comprobado que se pasan 2 parámetros, y que éstos son numéricos, sólo nos falta comprobar que se puede realizar una división correcta, es decir, que no se va a dividir por cero. Eso lo vamos a comprobar dentro del método divide.
    1. Al principio del método, comprobamos si el segundo parámetro del mismo (el divisor) es cero, si lo es, lanzaremos una excepción indicando que el parámetro no es correcto. Dentro de los subtipos de excepciones de RuntimeException, tenemos una llamada IllegalArgumentException que nos puede ayudar. Probamos a poner estas líneas al principio del método divide:
      public static int divide(int dividendo, int divisor)
      {
      	if (divisor == 0)
      		throw new IllegalArgumentException ("Divisor incorrecto");
      
      	...
      }
    2. Compila y ejecuta la clase. Prueba con algo como:
      java Ej1 20 0

      ¿Qué mensaje aparece? ¿Qué significa?

    3. Notar que lanzamos la excepción en el método divide pero no la capturamos en main. Por eso nos muestra un mensaje de error más largo, con la traza de la excepción. Si quisiéramos controlar qué saca el error, deberíamos capturar la excepción en el main y mostrar el texto que quisiéramos.

      Capturemos la excepción en el main y mostremos la traza del error:
      public static void main(String[] args)
      {
      	...
      
      	try
      	{
       	    System.out.println ("Resultado: " + divide(dividendo, divisor));
      	} catch (IllegalArgumentException e3) {
      		e3.printStackTrace()
      	}
      }

      ¿Qué mensaje aparece? ¿Qué significa?

  5. Lee ahora el punto 2.1.4 (Creación de nuevas excepciones) del tema 2 de teoría.
  6. Vamos a añadir una última excepción al método divide para comprobar que se realiza una división de números naturales (es decir, enteros mayores que 0). Para ello vamos a crear una excepción propia, llamada NumeroNaturalException, como la siguiente:
public class NumeroNaturalException extends Exception
{
	public NumeroNaturalException(String mensaje)
	{
		super(mensaje);
	}
}
  1. ¿Para qué sirve la llamada a super en el constructor en este caso?
  2. Ahora añadiremos el lanzamiento de esta excepción en el método divide, comprobando, tras ver si el divisor es cero, que tanto dividendo como divisor son positivos:
public static int divide(int dividendo, int divisor) 
{
	if (divisor == 0)
		throw new IllegalArgumentException ("Divisor incorrecto");
	if (dividendo < 0 || divisor < 0)
		throw new NumeroNaturalException("La division no es natural");

	...
}
  1. Prueba a compilar la clase. ¿Qué error te da? ¿A qué puede deberse?
  2. Para subsanarlo, hay que indicar en el método divide que dicho método puede lanzar excepciones de tipo NumeroNaturalException. Eso se hace mediante una cláusula throws en la declaración del método:
    public static nt divide(int dividendo, int divisor) 
    throws NumeroNaturalException
    {
        if(divisor == 0)
    	throw new IllegalArgumentException ("Divisor incorrecto");
        if (dividendo < 0 || divisor < 0)
    	throw new NumeroNaturalException("La division no es natural");
    
    	...
    }

    NOTA: esta cláusula sólo hay que introducirla para excepciones que sean de tipo checked, es decir, que se pueda predecir que pueden pasar al ejecutar un programa.  Dichas excepciones son los subtipos fuera de RuntimeException, y cualquier excepción que podamos crear nosotros. 

  3. Prueba a compilar la clase. ¿Qué error te da ahora? ¿Por qué?
  4. Lo que nos queda por hacer para terminar de corregir la clase es capturar la excepción que se lanza en el método divide, cuando utilizamos dicho método en el main:
    public static void main(String[] args)
    {
       ...
    
       try
       {
    	System.out.println ("Resultado: " + divide(dividendo, divisor));
       } catch (IllegalArgumentException e3) {
    	e3.printStackTrace()
       } catch (NumeroNaturalException e4) {
    	System.out.println ("Error: " + e4.getMessage());
    	System.exit(-1);
       }
    }

¿Para qué sirve la llamada a "getMessage()" (qué texto obtenemos con esa llamada)?
Nota que cuando hemos añadido antes la excepción IllegalArgumentException no ha habido que poner una cláusula "throws" en la declaración de "divide", ni capturar la excepción en el main para poder compilar, y sin embargo sí lo hemos hecho ahora para la NumeroNaturalException. ¿A qué se debe esta diferencia?

  1. Finalmente, compila y ejecuta el programa de las siguientes formas, observando que da la salida adecuada:
java Ej1 20 4				// 5
java Ej1 20 				// "Faltan parametros"
java Ej1 20 hola			// "Formato incorrecto del parametro"
java Ej1 20 0				// Excepción de tipo IllegalArgument
java Ej1 20 -1				// "Error: La division no es natural"

PARA ENTREGAR