índice

LAS EXCEPCIONES

INTRODUCCIÓN

EL MECANISMO DE LAS EXCEPCIONES EN JAVA

DIFERENTES TIPOS DE EXCEPCIÓN

EJEMPLO

PERO, �CÓMO LO HACEN?







INTRODUCCIÓN

Las excepciones son un mecanismo para el tratamiento de los errores. Consiste en:

Ejemplo:

        int BuscarCodigoEstacion (String nombreEstacion)
        throws FicheroExcepcion, RecorreExcepcion {
                int resultado = 0;
                // ...
                // abrir fichero de c�digo y si es imposible:
                throw new FicheroExcepcion( );
                // se lanza una excepci�n y el c�digo siguiente
                // no se ejecuta
 
                //  ...
                // busca c�digo de estaci�n y si es imposible:
                throw new RecorreExcepcion( ); // idem
                // si encuentra:
                return resultado;
        }
        void ComponeBillete ( ) {
                try {
                        elCodigoEstacion = BuscaCodigoEstacion (nombreEstacion);
                        laDistanciaEstacion = BuscaDistanciaEstacion (nombreEstacion);
                        elPrecioIda = BuscarPrecioIda (nombreEstacion);
                        elPrecioVuelta = BuscarPrecioVuelta (nombreEstacion);
                        laSobreTasaLocal = BuscarSobreTasaLocal (nombreEstacion);
                        elPrecioTotal = elPrecioIda + elPrecioVuelta + laSobreTasaLocal;
                }
                catch (BuscaExcepcion e ) {
                        Visualiza ("El aparato no puede expedir billetes para
este trayecto");
                        Visualiza ("Por favor, dir�gase a la taquilla");
                }
        }

La secuencia throw new FicheroExcepcion ( ) lanza la excepción FicheroExcepcion: crea un objeto de este tipo, que interrumpe el flujo de ejecución del programa y remonta la pila de llamadas de métodos hasta encontrar un método que sepa atrapar la excepción. La ejecución del programa se transfiere directamente a este método. Se atrapan las excepciones gracias a la palabra clave catch.

Las excepciones son objetos, y como tales se puede definir una jerarquía: así, en el ejemplo anterior, BuscaExcepcion es la superclase de FicheroExcepcion y de RecorreExcepcion. Al declarar que atrapa todas las excepciones de tipo BuscaExcepcion, la secuencia catch atrapa tanto las excepciones FicheroExcepcion como RecorreExcepcion.

Ventajas:

El ejemplo anterior es válido tanto en Java como en C++ (excepto por la sintaxis).

Ahora se estudiará en más detalle cómo se tratan las excepciones en Java.

volver


EL MECANISMO DE LAS EXCEPCIONES EN JAVA


DEFINICIÓN

Las excepciones son objetos. Esto significa que se definen en clases y que tienen todas las propiedades de los objetos.

La clase madre de todas las excepciones es la clase Throwable. Sólo los objetos definidos por una subclase de Throwable pueden ser lanzados, y después eventualmente atrapados.

Esta clase define un cierto número de métodos que puede redefinir o no. Estos métodos permiten construir y recuperar un mensaje asociado al objeto Exception afectado.

Puede añadir atributos y métodos que sean propios de su tipo de excepción. Así, si lanza la excepción EstacionNoEncontrada, puede definir como atributo de la excepción el nombre de la estación. Esto es imposible con el mecanismo de los códigos de retorno: en la utilización de este mecanismo hay que almacenar en algún sitio el nombre de esta estación y recuperarlo seguidamente.

El mecanismo de jerarquía de excepciones permite capturar en el código relaciones abstractas que se encontraban antes en forma de comentario.

En lugar del tradicional

        // errores relacionados con el n�mero de kil�metros
                #define NUM_KILOMETROS_NEGATIVO
                #define NUM_KILOMETROS_DEMASIADO_GRANDE  

se encontrará ahora:

        class NumKilometrosNegativo extends ExceptionNumKilometros {
        }
        class NumKilometrosDemasiadoGrande extends ExceptionNumKilometros {
        }

que el compilador tendrá en cuenta.


volver


LANZAR UNA EXCEPCIÓN

Las excepciones pueden ser lanzadas por:

El mecanismo es muy simple:

        if (numKilometros < 0) {
                throw new NumKilometrosNegativo ( );  
        }
        // las l�neas siguientes no se ejecutan
        // si la excepci�n ha sido lanzada
        if (precio < 0)
                throw new PrecioNegativo ( );
        } 

volver


ATRAPAR UNA EXCEPCIÓN

Las excepciones se tratan en los bloques catch. Pero éstos no aparecen en cualquier sitio. Los bloques catch sólo pueden situarse a continuación de un bloque try. Recíprocamente, un bloque try va siempre inmediatamente seguido de uno o más bloques catch.

La sintaxis completa es la siguiente:

        try {
                // conjunto de instrucciones susceptibles de lanzar
                // NumKilometrosNegativo y PrecioNegativo
                // directa o indirectament, por medio de la llamada a m�todos
                // susceptibles a su vez de lanzar una excepci�n
        }
        catch (NumKilometrosNegativo e)
        {
                // tratamiento de esta excepci�n
        }
        catch (PrecioNegativo e)
        {
                // tratamiento de la segunda excepci�n
        }

El flujo de ejecución pasa al primer bloque catch si y solamente si se ha lanzado una excepción de tipo NumKilometrosNegativo o del tipo de una subclase de NumKilometrosNegativo en el bloque try.

El flujo de ejecución pasa al segundo bloque catch si y solamente si se ha lanzado una excepción del tipo PrecioNegativo o del tipo de una subclase de PrecioNegativo en el bloque try, y si el flujo no ha pasado por otro bloque catch anteriormente.

Así, el orden de los catch es importante, porque cuando una excepción remonta, el primer bloque catch susceptible de tratarla es el que se ejecuta, en detrimento de los demás.

Nunca puede ocurrir que un bloque catch se ejecute cuando varias excepciones remontan del mismo bloque try. Cuando se lanza una excepción, el control de la ejecución pasa a un bloque catch y trata la excepción. Si el gestor de excepciones no encuentra un bloque catch para tratar la excepción, el programa se para. Y si una excepción se lanza en un bloque catch, es signo de que la excepción actual ha sido tratada. No puede haber más que una sóla excepción tratada a la vez.

Ejemplo: Se supone que hay una jerarquía de excepciones con una Abuela, una Madre y una Nieta, y una excepción Lobo que deriva directamente de la clase Exception:

        class Abuela extends Exception { /* ... */};
        class Madre extends Abuela {/* ... */};
        class Nieta extends Madre { /* ... */};

        class Lobo extends Exception { /* ... */};

  

teniendo los bloques siguientes:

        try {
        // secuencia de c�digo que puede lanzar Abuela, Madre o Nieta 
        // seguidamente
        }
        catch (Madre m) {
                // m es la excepci�n que ha sido creada por un throw
            System.out.println (m.toString( ));
                // ...
        }
        catch (Abuela a) {
                // ...
        }
        catch (Nieta n) {
                // ...
        }

Si se lanza una excepción del tipo Abuela, no podrá ser atrapada por el primer bloque catch, porque Abuela es una superclase de Madre, y no una subclase. La excepción será ignorada por el primer bloque y tratada por el segundo.

Si se lanza una excepción del tipo Madre, será tratada por el primer bloque. No será tratada por el segundo bloque, aunque Madre sea una subclase de Abuela, porque ya habrá sido tratada, o consumida, por el primer bloque.

Si se lanza una excepción del tipo Nieta, se tratará en el primer bloque, porque Nieta es una subclase de Madre. No será tratada por el último bloque, porque ya habrá sido tratada por el primero. De hecho resulta imposible entrar en el último bloque catch del ejemplo anterior, sea cual sea la excepción lanzada. Esto lo indica el compilador cuando se compila este programa:

       C:\ProgramasJava\tmp>javac PruebaException.java

        PruebaExcepcion.java:19:catch not reached.

        catch (Nieta n) {

        ^

        1 error

El orden correcto de los bloques debería ser el siguiente:

        try {
                // secuencia de c�digo que puede
                // lanzar los tres tipos de escepciones anteriores      
         }
        catch (Nieta n) {
                // ..
        }
        catch (Madre m) {
                // ..
        }
        catch (Abuela a) {
                // ..
        }


Recordemos que, si el bloque Abuela fuera el único, atraparía de buen grado las excepciones Nieta:

        try {
                // bloque de instrucciones susceptible de lanzar
                // las excepciones Nieta
        }
        catch (Abuela a) {
                // ...
        }
 

Si ahora tenemos el bloque siguiente:

        try {
                // bloque de instrucciones susceptible de lanzar
                // las excepciones Lobo y Nieta
        }
        catch (Lobo 1) {
                // ...
        }


El bloque Lobo no atrapará la excepción Nieta, porque Lobo no es una superclase de Nieta. La excepción Nieta sube a través de la rutina descrita anteriormente, a la búsqueda de un bloque catch susceptible de tratarla.


volver


FINALIZAR EL TRATAMIENTO

Una de las novedades de Java es permitir la ejecución de una porción de código ocurra lo que ocurra. Este código se describe en el bloque finally. Este código se ejecuta tras el bloque try, y después de un eventual bloque catch ocurra lo que ocurra.

Esto cubre los casos siguientes:

El interés de este bloque finally es doble.

En primer lugar, permite reunir en un solo bloque un conjunto de instrucciones que de otro modo serían duplicadas:

        try {
                // abrir un fichero
                // efectuar tratamientos susceptibles de lanzar una excepci�n
                // cerrar el fichero
        }
        catch (CiertaExcepcion e)
        {
                // tratar la excepci�n
                // cerrar el fichero
        }
        catch (OtroTipoExcepcion o)
        {
                        // tratar la excepci�n
                // cerrar el fichero
        } 

Gracias al bloque finally, la sintaxis es:

        try {
                // abrir un fichero
                // efectuar tratamientos susceptibles de lanzar una excepci�n
        }
        catch (CiertaExcepcion e)
        {
                // tratar la excepci�n
        }
        catch (OtroTipoExcepcion o) 
        {
                // tratar la excepci�n 
        }
        finally {
                // cerrar el fichero
        }

El bloque finally permite también efectuar tratamientos tras el bloque try, aunque se haya lanzado una excepción y no haya sido atrapada en los bloques catch que siguen al bloque try:

        try {
                // abrir el fichero
                // efectuar tratamientos que lanzan una excepci�n
                // Madre o Lobo
        }
        catch (Madre m) {
                // ...
        }
        finally {
                // cerrar el fichero
        };

Las instrucciones del bloque finally se ejecutarán, aunque se lance una excepción Lobo.


volver


DECLARAR UNA EXCEPCIÓN

Uno de los puntos fuertes del tratamiento de excepciones en Java es la obligación de declaración. Es decir, un método debe incluir en su signatura el conjunto de excepciones cuyo lanzamiento puede producirse en su interior y que no se tratan en sus bloques catch. El conjunto de excepciones cuyo lanzamiento puede producirse en su interior se trata en realidad de:

Esta declaración se realiza con el empleo de la palabra clave throws tras el nombre del método:

        void MetodoCualquiera ( ) throws Lobo {
                try {
                        // abrir el fichero
                        // efectuar tratamientos que lanzan una excepci�n Madre o Lobo
                }
                catch (Madre m) {
                        // ...
                }
                finally {
                        // cerrar el fichero
                }
        } 

En el ejemplo anterior, no se declara la excepción Madre en la signatura del método MetodoCualquiera, porque ésta es tratada en el interior de este método y no puede remontar más allá. No es por tanto necesario declararla.

Esta declaración obligatoria aumenta la calidad de la escritura del código de dos formas complementarias.

En primer lugar, quien escribe el método se ve obligado a declarar en la signatura de éste todas las excepciones susceptibles de ser lanzadas por la llamada a este método. Esto le obliga a ser consciente de todas las excepciones lanzadas por los métodos que llama. Debe elegir, para cada una de excepciones, entre declararlas y tratarlas. No puede ignorarlas.

En segundo lugar, quien utiliza el método aprende, gracias a las cláusulas throws, cuáles son las excepciones susceptibles de ser lanzadas por este método y por los métodos llamados. Puede diseñar su estrategia en función de este conjunto de excepciones, y no sólo en función de las excepciones lanzadas por el propio método.

Este esfuerzo de documentación de las excepciones tiene dos ventajas:

Esta declaración sistemática sería sin embargo poco realista en la práctica si todas las excepciones debieran ser declaradas. En efecto, más de una decena de excepciones son definidas por el gestor de Java y son susceptibles de ser lanzadas un poco en cualquier punto del código. La declaración obligatoria aplicada al pie de la letra tendría como consecuencia la presencia, al principio de cada método, de un bloque parecido a éste:

        void unMetodo ( ) throws ArithmeticException, 
        NullPointerException, IncompatibleClassChangeException,
        ClassCastException, NegativeArraySizeException,
        OutOfMemoryException, NoClassDefFoundException,
        IncompatibleTypeException, ArrayIndexOutOfBoundsException,
        UnsatisfiedLinkException, InternalException /* ... no exhaustivo ... */ {

Esto implicaría:

Si saliera una nueva versión de Java con una excepción de sistema suplementaria, del tipo: ModemNotRespondingException, habría que reescribir simplemente todas las cabeceras de los métodos.

Hay que hacer frente pues a dos restricciones contradictorias:

La solución es simple y elegante, y se basa en la utilización de la herencia.



volver


DIFERENTES TIPOS DE EXCEPCIÓN

En efecto, al decidir que todas las excepciones subclases directas o indirectas de la clase RuntimeException no sean declaradas, se explica a continuación cómo se ha resuelto la contradicción del apartado anterior.

Todas las excepciones lanzadas por el núcleo ejecutable, como ArithmeticException, NullPointerException, etc., heredan de la clase RuntimeException. Por convención, el compilador sabe que las subclases de RuntimeException no tienen que declararse en la signatura de los métodos.

Así se evita la pesadez del código y se conserva la obligación de declarar para las otras excepciones.

Las excepciones RuntimeException son definidas por el lenguaje. Pueden ser lanzadas por el propio programador y deben ser atrapadas por él. Es más puede definirlas. Puede definir excepciones que hereden de la clase RuntimeException. Entonces no tiene por qué declararlas si no las trata.

Las excepciones subclases de Error, reservadas a los errores de hardware. Hay tres tipos:

Si se desea definir una jerarquía de excepciones propia, el lenguaje Java no aporta una respuesta a esta cuestión conceptual. Algunos consejos:



volver


EJEMPLO

El siguiente ejemplo es un procedimiento de tratamiento de los eventos

        public boolean handleEvent (Event evt) {
                if (evt.id == Event.Window_Destroy) {
                        System.exit (0);
                }
                else if (evt.target == si) {
                        elContador.Si ( );
                        visualizaResultado.setText (elContador.VisualizaContador ( ));
                }
                else if (evt.target == no) {
                        elContador.No ( );
                        visualizaResultado.setText (elContador.VisualizaContador ( ));
                };
                return super.handleEvent (evt);
        }

Se observa que:

Es tentador querer disminuir algo el tamaño del código poniendo en común la línea:

        visualizaResultado.setTest (elContador.VisualizaContador ( ));
        

Desplazándola hacia el final del método queda:

        public boolean handleEvent (Event evt) {
                if (evt.id == Event.Window_Destroy) {
                        System.exit (0);
                }
                else if (evt.target == si) {
                elContador.Si ( );
                // visualizaResultado.setText (elContador.VisualizaContador( ));
                }
                else if (evt.target == no) {
                elContador.No ( );
                // visualizaResultado.setText (elContador.VisualizaContador( ));
                };
                visualizaResultado.setText (elContador.VisualizaContador ( ));
                return super.handleEvent (evt);
        }

Después de compilar y ejecutar:

        java.langArithmeticException: / by zero
        at Contador.Visualizacontador (Sondeo.java:60)
        at Sondeo.handleEvent (Sondeo.java:37)
        at java.awt.Component.postEvent (Component.java:838)
        at sun.awt.win32.MComponentPeer.handleMouseMoved (MComponentPeer.java:254)
        at sun.awt.win32.MToolkit.run (Mtoolkit.java:57)
        at java.lang.Thread.run (Thread.java:289)

Analizando estos mensajes: se diría que se ha lanzado una excepción en el método VisualizaContador( ), porque se realiza una división por cero en la línea 60. Revisando el código de este método:

        String VisualizaContador ( ) {
                StringBuffer cadena = new StringBuffer ( );
                cadena.append ((numvotosSi * 100 / (numVotosSi+numVotosNo)));
                // la l�nea anterior es la l�nea 60
                cadena.append ("% estima la censura �til");
                return cadena.toString( ); 
        } 

El sistema no soporta la llamada al método, cuando numVotosSi+numVotosNo vale cero. Se visualiza el resultado del contador, antes incluso de su inicialización. Y ésto al final del método handleEvent( ). Efectivamente, el desplazamiento de la línea que se efectuaba al principio de este apartado ejecuta el método, sea cual sea el evento que se produzca en la ventana. Por ejemplo, cuando el ratón se posiciona sobre la ventana, se genera un nuevo evento, y el método handleEvent( ) intenta mostrar el resultado del contador. No hay que desplazar la línea:

        visualizaResultado.setText (elContador.Visualizacontador ( )); 
  

Java aporta dos cosas en esta búsqueda de error:

una librería gráfica que atrapa estas excepciones y las gestiona inteligentemente; visualiza informaciones precisas que permiten encontrar el error muy deprisa.

volver


PERO, �CÓMO LO HACEN?

¿Cómo hace la librería gráfica para dar tanta información sobre las excepciones?

Emplea los diferentes métodos de Throwable, que permiten describir la excepción de forma comprensible.

Si utiliza las excepciones en C++, habría definido una clase madre, en la que habría puesto métodos de almacenamiento de información y métodos de seguimiento para recuperar esas informaciones.

Pero a pesar de su trabajo, entre el momento en que se lanza una excepción y ell momento en que se trata, es imposible saber dónde ha ocurrido. En Java, no sólo existe la jerarquía de excepciones, no sólo incluye métodos de información y de seguimiento, sino que además el núcleo sigue la historia de la excepción y la restituye si se le pide.

Ejemplo de excepción lanzada en el fondo de una pila de llamadas:

        import java.io.*;
        public class Mu�ecaRusa extends Objects {
                public static void main (String args[ ]) {
                        Mu�ecaRusa laMu�ecaRusa = new Mu�ecaRusa( );
                        laMu�ecaRusa.metodo1( );
                }
                void metodo1( ) {
                        try{
                                metodo2( );
                        }
                        catch (Exception e) {
                                System.out.println ("Llamada a e.toString: " +e.toString( ));
                                System.out.println ("Llamada a e.printStackTrace: ")
                                e.printStackTrace( );
                        }
                };
                void metodo2 ( ) {metodo3 ( ); };
                void metodo3 ( ) {metodo4 ( ); };
                void metodo4 ( ) {int i = 0; i 0 2 / i; };      
        } 

La llamada a este programa lanza una excepción en el metodo4, que remonta hasta que sea atrapada por la cláusula catch del metodo1, la cual sigue la excepción. Se obtiene el resultado siguiente:

        Llamada a e.toString: java.lang.ArithmeticException: / by zero
        Llamada a e.printStackTrace: 
        java.lang.ArithmeticException: / by zero
                at Mu�ecaRusa.metodo4 (Mu�ecaRusa.java:20) 
                at Mu�ecaRusa.metodo3 (Mu�ecaRusa.java:19) 
                at Mu�ecaRusa.metodo2 (Mu�ecaRusa.java:18)
                at Mu�ecaRusa.metodo1 (Mu�ecaRusa.java:10) 
                at Mu�ecaRusa.main (Mu�ecaRusa.java:6) 

Si se desea, se puede seguir solamente el tipo de la excepción (método toString( )), o bien seguir el conjunto de llamadas que conducen a su manifestación (método printStackTrace( )).



volver


Hosted by www.Geocities.ws

1