Tutorial de Java

Excepciones y Control General del Flujo

Anterior | Siguiente
Excepciones

try-catch-throw

    try {
        sentencias;
    } catch( Exception ) {
        sentencias;
    }

Java implementa excepciones para facilitar la construcción de código robusto. Cuando ocurre un error en un programa, el código que encuentra el error lanza una excepción, que se puede capturar y recuperarse de ella. Java proporciona muchas excepciones predefinidas.

El manejo y control de excepciones es tan importante en Java que debe ser tratado en un capítulo aparte, aunque sirva este punto como mención previa de su existencia.

Control General del Flujo

break

    break [etiqueta];

La sentencia break puede utilizarse en una sentencia switch o en un bucle. Cuando se encuentra en una sentencia switch, break hace que el control del flujo del programa pase a la siguiente sentencia que se encuentre fuera del entorno del switch. Si se encuentra en un bucle, hace que el flujo de ejecución del programa deje el ámbito del bucle y pase a la siguiente sentencia que venga a continuación del bucle.

Java incorpora la posibilidad de etiquetar la sentencia break, de forma que el control pasa a sentencias que no se encuentran inmediatamente después de la sentencia switch o del bucle, es decir, saltará a la sentencia en donde se encuentre situada la etiqueta. La sintaxis de una sentencia etiquetada es la siguiente:

    etiqueta: sentencia;

continue

    continue [etiqueta];

La sentencia continue no se puede utilizar en una sentencia switch, sino solamente en bucles. Cuando se encuentra esta sentencia en el discurrir normal de un programa Java, la iteración en que se encuentre el bucle finaliza y se inicia la siguiente.

Java permite el uso de etiquetas en la sentencia continue, de forma que el funcionamiento normal se ve alterado y el salto en la ejecución del flujo del programa se realizará a la sentencia en la que se encuentra colocada la etiqueta.

Por ejemplo, al encontrarse con bucles anidados, se pueden utilizar etiquetas para poder salir de ellos:

    uno: for( ) {
             dos: for( ) {
                 continue;       // seguiría en el bucle interno
                 continue uno;   // seguiría en el bucle principal
                 break uno;      // se saldría del bucle principal
                 }
             }

Hay autores que sugieren que el uso de sentencias break y continue etiquetadas proporciona una alternativa al infame goto (que C++ todavía soporta, pero Java no, aunque reserva la palabra). Quizá se así, pero el entorno de uso de las sentencias etiquetadas break y continue es mucho más restrictivo que un goto. Concretamente, parece que un break o continue con etiqueta, compila con éxito solamente si la sentencia en que se encuentra colocada la etiqueta es una sentencia a la que se pueda llegar con un break o continue normal.

return

    return expresión;

La sentencia return se utiliza para terminar un método o función y opcionalmente devolver un valor al método de llamada.

En el código de una función siempre hay que ser consecuentes con la declaración que se haya hecho de ella. Por ejemplo, si se declara una función para que devuelva un entero, es imprescindible colocar un return final para salir de esa función, independientemente de que haya otros en medio del código que también provoquen la salida de la función. En caso de no hacerlo se generará un Warning, y el código Java no se puede compilar con Warnings.

    int func() {
        if( a == 0 )
            return 1;
        return 0; // es imprescindible porque se retorna un entero
        }

Si el valor a retornar es void, se puede omitir ese valor de retorno, con lo que la sintaxis se queda en un sencillo:

    return;

y se usaría simplemente para finalizar el método o función en que se encuentra, y devolver el control al método o función de llamada.

Esta sentencia se puede utilizar para mostrar uno de los problemas para los que Java está destinado a corregir. En C++ se permite que los valores de retorno de una función o método puedan ser por valor o por referencia. Cuando se devuelve un valor por referencia, o un puntero, en C++, hay que asegurarse de que el objeto referenciado continúe existiendo a la terminación del método. Por ejemplo, si se devuelve un puntero o una referencia a una variable local de la función, pueden presentarse problemas, porque esa variable deja de existir tan pronto como finaliza la función.

El compilador Borland C++ utilizado por el autor para realizar pruebas, no permite que se devuelva una referencia a una variable local, pero sí que permite devolver un puntero a esa variable. El siguiente programa, java409.cpp, es muy simple e ilustra esto. Crea un array en la función principal para modificar la memoria y luego utiliza el puntero devuelto para presentar el contenido de la memoria a la que apunta ese puntero. El programa presenta en pantalla porquería, porque después de que termine la función, el puntero apunta a una dirección de memoria que ya no es válida para el programa. Sin embargo, compilando la aplicación con GNU C++ y corriendo sobre Linux, el valor que se presenta sí es el correcto.

#include <iostream.h>
    int *test() { // Declara la variable local a la función 
        int variableLocal = 6; // Guarda su dirección en un puntero
        int *puntero = &variableLocal;
    
        // Devuelve el puntero a la variable local
        return puntero;
        }
    
    void main() {
        // Recoge el puntero devuelto por la función de test
        int *punteroMain = test();
        // Rellena con cosas la memoria
        int memArray[5] = { 10,11,12,13,14 };
    
        // Presenta en pantalla el contenido de la zona a que apunta el 
        // puntero
        cout << "El contenido de la memoria es " << *punteroMain;
        }     

Aparentemente en Java, solamente se puede retornar por valor. En el caso de tipos básicos, se devuelve una copia del item retornado. Si no se quiere devolver una copia, siempre está la posibilidad de utilizar las versiones objeto de los tipos básicos.

Y con los objetos, el retorno por valor devuelve una copia de la dirección en que se encuentra almacenado el objeto. Tener una copia de esta dirección es lo mismo que disponer de la dirección original, porque todos los objetos en Java se almacenan en memoria dinámica y esta memoria no es sobreescrita hasta que todas las referencias al objeto que contiene, dejan de existir. Y en caso de no se libere esa memoria cuando ya no haya referencias a ella, el garbage collector se encargará en su siguiente ejecución de devolverla al Sistema.

El java409.java intenta reproducir las circunstancias del ejemplo C++. Para ello, instancia un objeto en un método, devuelve una referencia a ese objeto, intenta sobreescribir la memoria indicada por la referencia creando e inicializando un array bastante grande y, finalmente, presenta en pantalla el contenido del objeto utilizando la referencia que había sido devuelta en un principio.

La aplicación Java no adolece del mismo problema que su versión C++, porque los objetos instanciados en un método no se destruyen cuando termina su ejecución, sino cuando dejan de existir referencias al objeto.

Y el programa C++ que se presenta a continuación, java410.cpp, proporciona la misma flexibilidad que la aplicación Java. En él, en vez de declarar una variable local en la función, se crea un puntero en la función a una variable creada en memoria dinámica. La dirección de esa variable en memoria dinámica se le asigna al puntero de la función.

Aunque el puntero se destruirá cuando la función concluya, la variable que se ha creado en memoria dinámica no se destruirá hasta que termine el programa (porque no se hace ninguna llamada al operador delete). Esta es una de las formas en las que C++ puede instanciar un objeto y pasarlo a la función de llamada mientras mantiene su validez aún cuando esa función haya concluido su ejecución.

Navegador

Home | Anterior | Siguiente | Indice | Correo