Tutorial de Java

Propagación de Excepciones

Anterior | Siguiente
La cláusula catch comprueba los argumentos en el mismo orden en que aparezcan en el programa. Si hay alguno que coincida, se ejecuta el bloque y sigue el flujo de control por el bloque finally (si lo hay) y concluye el control de la excepción.

Si ninguna de las cláusulas catch coincide con la excepción que se ha producido, entonces se ejecutará el código de la cláusula finally (en caso de que la haya). Lo que ocurre en este caso, es exactamente lo mismo que si la sentencia que lanza la excepción no se encontrase encerrada en el bloque try.

El flujo de control abandona este método y retorna prematuramente al método que lo llamó. Si la llamada estaba dentro del ámbito de una sentencia try, entonces se vuelve a intentar el control de la excepción, y así continuamente.

Cuando una excepción no es tratada en la rutina en donde se produce, lo que sucede es lo siguiente. El sistema Java busca un bloque try..catch más allá de la llamada, pero dentro del método que lo trajo aquí. Si la excepción se propaga de todas formas hasta lo alto de la pila de llamadas sin encontrar un controlador específico para la excepción, entonces la ejecución se detendrá dando un mensaje. Es decir, podemos suponer que Java nos está proporcionando un bloque catch por defecto, que imprime un mensaje de error, indica las últimas entradas en la pila de llamadas y sale.

No hay ninguna sobrecarga en el sistema por incorporar sentencias try al código. La sobrecarga se produce cuando se genera la excepción.

Se ha indicado ya que un método debe capturar las excepciones que genera, o en todo caso, declararlas como parte de su llamada, indicando a todo el mundo que es capaz de generar excepciones. Esto debe ser así para que cualquiera que escriba una llamada a ese método esté avisado de que le puede llegar una excepción, en lugar del valor de retorno normal. Esto permite al programador que llama a ese método, elegir entre controlar la excepción o propagarla hacia arriba en la pila de llamadas. La siguiente línea de código muestra la forma general en que un método declara excepciones que se pueden propagar fuera de él, tal como se ha visto a la hora de tratar la sentencia throws:

tipo_de_retorno( parametros ) throws e1,e2,e3 { }

Los nombres e1,e2,... deben ser nombres de excepciones, es decir, cualquier tipo que sea asignable al tipo predefinido Throwable. Observar que, como en la llamada al método se especifica el tipo de retorno, se está especificando el tipo de excepción que puede generar (en lugar de un objeto Exception).

He aquí un ejemplo, tomado del sistema Java de entrada/salida:

byte readByte() throws IOException;
short readShort() throws IOException;
char readChar() throws IOException;

void writeByte( int v ) throws IOException;
void writeShort( int v ) throws IOException;
void writeChar( int v ) throws IOException;

Lo más interesante aquí es que la rutina que lee un char, puede devolver un char; no el entero que se requiere en C. C necesita que se devuelva un int, para poder pasar cualquier valor a un char, y además un valor extra (-1) para indicar que se ha alcanzado el final del fichero. Algunas de las rutinas Java lanzan una excepción cuando se alcanza el fin del fichero.

En el siguiente diagrama se muestra gráficamente cómo se propaga la excepción que se genera en el código, a través de la pila de llamadas durante la ejecución del código:

Cuando se crea una nueva excepción, derivando de una clase Exception ya existente, se puede cambiar el mensaje que lleva asociado. La cadena de texto puede ser recuperada a través de un método. Normalmente, el texto del mensaje proporcionará información para resolver el problema o sugerirá una acción alternativa. Por ejemplo:

class SinGasolina extends Exception {
    SinGasolina( String s ) {    // constructor
        super( s );
        }
    ....

// Cuando se use, aparecerá algo como esto
    try {
        if( j < 1 )
            throw new SinGasolina( "Usando deposito de reserva" );
    } catch( SinGasolina e ) {
        System.out.println( o.getMessage() );
        }

Esto, en tiempo de ejecución originaría la siguiente salida por pantalla:

> Usando deposito de reserva

Otro método que es heredado de la superclase Throwable es printStackTrace(). Invocando a este método sobre una excepción se volcará a pantalla todas las llamadas hasta el momento en donde se generó la excepción (no donde se maneje la excepción). Por ejemplo:

// Capturando una excepción en un método
class testcap {
    static int slice0[] = { 0,1,2,3,4 };

    public static void main( String a[] ) {
        try {
            uno();
        } catch( Exception e ) {
            System.out.println( "Captura de la excepcion en main()" );
            e.printStackTrace();
            }
        }

    static void uno() {
        try {
            slice0[-1] = 4;
        } catch( NullPointerException e ) {
            System.out.println( "Captura una excepcion diferente" );
            }
        }
    }

Cuando se ejecute ese código, en pantalla observaremos la siguiente salida:

> Captura de la excepcion en main()
> java.lang.ArrayIndexOutOfBoundsException: -1
        at testcap.uno(test5p.java:19)
        at testcap.main(test5p.java:9)

Con todo el manejo de excepciones podemos concluir que se proporciona un método más seguro para el control de errores, además de representar una excelente herramienta para organizar en sitios concretos todo el manejo de los errores y, además, que se pueden proporcionar mensajes de error más decentes al usuario indicando qué es lo que ha fallado y por qué, e incluso podemos, a veces, recuperar al programa automáticamente de los errores.

La degradación que se produce en la ejecución de programas con manejo de excepciones está ampliamente compensada por las ventajas que representa en cuanto a seguridad de funcionamiento de esos mismos programas.

Navegador

Home | Anterior | Siguiente | Indice | Correo