Tutorial de Java

Constructor

Anterior | Siguiente
Tanto Java como C++ soportan la sobrecarga de métodos, es decir, que dos o más métodos puedan tener el mismo nombre, pero distinta lista de argumentos en su invocación. Si se sobrecarga un método, el compilador determinará ya en tiempo de compilación, en base a lista de argumentos con que se llame al método, cual es la versión del método que debe utilizar.

Tanto Java como C++ soportan la noción de constructor. El constructor es un tipo específico de método que siempre tiene el mismo nombre que la clase y se utiliza para construir objetos de esa clase. No tiene tipo de dato específico de retorno, ni siquiera void. Esto se debe a que el tipo específico que debe devolver un constructor de clase es el propio tipo de la clase.

En este caso, pues, no se puede especificar un tipo de retorno, ni se puede colocar ninguna sentencia que devuelva un valor. Los constructores pueden sobrecargarse, y aunque puedan contener código, su función primordial es inicializar el nuevo objeto que se instancia de la clase. En C++, el constructor se invoca automáticamente a la hora de crear un objeto. En Java, ha de hacerse una llamada explícita al constructor para instanciar un nuevo objeto.

Cuando se declara una clase en Java, se pueden declarar uno o más constructores opcionales que realizan la inicialización cuando se instancia (se crea una ocurrencia) un objeto de dicha clase.

Utilizando el código de la sección anterior, cuando se crea una nueva instancia de MiClase, se crean (instancias) todos los métodos y variables, y se llama al constructor de la clase:

    MiClase mc;
    mc = new MiClase();

La palabra clave new se usa para crear una instancia de la clase. Antes de ser instanciada con new no consume memoria, simplemente es una declaración de tipo. Después de ser instanciado un nuevo objeto mc, el valor de i en el objeto mc será igual a 10. Se puede referenciar la variable (de instancia) i con el nombre del objeto:

    mc.i++; // incrementa la instancia de i de mc

Al tener mc todas las variables y métodos de MiClase, se puede usar la primera sintaxis para llamar al método Suma_a_i() utilizando el nuevo nombre de clase mc:

    mc.Suma_a_i( 10 );

y ahora la variable mc.i vale 21.

Luego, en Java, cuando se instancia un objeto, siempre se hace una llamada directa al constructor como argumento del operador new. Este operador se encarga de que el sistema proporcione memoria para contener al objeto que se va a crear.

En C++, los objetos pueden instanciarse de diferentes formas, pero en Java solamente se pueden instanciar en la pila de memoria, es decir, solamente se pueden instanciar utilizando el operador new para poder solicitar memoria al sistema en tiempo de ejecución y utilizar el constructor para instanciar el objeto en esa zona de memoria. Si al intentar instanciar un objeto, la Máquina Virtual Java (JVM) no puede localizar la memoria que necesita ese objeto, bien inmediatamente o haciendo ejecutarse al reciclador de memoria, el sistema lanzará un OutOfMemoryError.

Tanto en Java como en C++, si no se proporciona explícitamente un constructor, el sistema proporciona uno por defecto que inicializará automáticamente todas las variables miembro a cero o su equivalente, en Java. En C++, el constructor de defecto no realiza ningún tipo de inicialización.

Se puede pensar en el constructor de defecto en Java como un método que tiene el mismo nombre que la clase y una lista de argumentos vacía. Y en C++, el constructor de defecto sería como la llamada cuando se instancia un objeto sin parámetros

    MiClase objeto;

En ambos lenguajes, si se proporciona uno o más constructores, el constructor de defecto no se proporciona automáticamente y si fuese necesaria su utilización, tendría que proporcionarlo explícitamente el programa.

Las dos sentencias siguientes muestran cómo se utiliza el constructor en Java para declarar, instanciar y, opcionalmente, inicializar un objeto:

    MiClase miObjeto = new MiClase();
    MiClase miObjeto = new MiClase( 1,2,3 );

Las dos sentencias devuelven una referencia al nuevo objeto que es almacenada en la variable miObjeto. También se puede invocar al constructor sin asignar la referencia a una variable. Esto es útil cuando un método requiere un objeto de un tipo determinado como argumento, ya que se puede incluir una llamada al constructor de este objeto en la llamada al método:

    miMetodo( new MiConstructor( 1,2,3 ) );

Aquí se instancia e inicializa un objeto y se pasa a la función. Para que el programa compile adecuadamente, debe existir una versión de la función que espere recibir un objeto de ese tipo como parámetro.

Tanto en Java como en C++, cuando un método o una función comienza su ejecución, todos los parámetros se crean como variables locales automáticas. En este caso, el objeto es instanciado en conjunción con la llamada a la función que será utilizada para inicializar esas variables locales cuando comience la ejecución y luego serán guardadas. Como son automáticas, cuando el método concluye su ejecución, se destruirá (en C++) o será marcado para su destrucción (en Java).

En el siguiente ejemplo, java507.java, se ilustran algunos de los conceptos sobre constructores que se han planteado en esta sección.

class MiClase {
    int varInstancia;
      
    // Este es el constructor parametrizado
    MiClase( int dato ) {
        // rellenamos la variable de instancia con los datos
        // que se pasan al constructor
        varInstancia = dato;
        }
      
    void verVarInstancia() {
        System.out.println( "El Objeto contiene " + varInstancia );
        }
    }
      
class java507 { 
    public static void main( String args[] ) {
        System.out.println( "Lanzando la aplicacion" );
        // Instanciamos un objeto de este tipo llamando al 
        // constructor de defecto
        java507 obj = new java507();
        // Llamamos a la funcion pasandole un constructor 
        // parametrizado como parametro
        obj.miFuncion( new MiClase( 100 ) );
        }
      
    // Esta funcion recibe un objeto y llama a uno de sus metodos
    // para presentar en pantalla el dato que contiene el objeto
    void miFuncion( MiClase objeto){
        objeto.verVarInstancia();
        }
    }

Herencia

En casos en que se vea involucrada la herencia, los constructores toman un significado especial porque lo normal es que la subclase necesite que se ejecute el constructor de la superclase antes que su propio constructor, para que se inicialicen correctamente aquellas variables que deriven de la superclase. En C++ y Java, la sintaxis para conseguir esto es sencilla y consiste en incluir en el cuerpo del constructor de la subclase como primera línea de código la siguiente sentencia:

  super( parametros_opcionales );

Esto hará que se ejecute el constructor de la superclase, utilizando los parámetros que se pasen para la inicialización. En el código del ejemplo siguiente, java508.java, se ilustra el uso de esta palabra clase para llamar al constructor de la superclase desde una subclase.

class SuperClase {
    int varInstancia;
      
    // Es necesario proporcionar el constructor por defecto,que
    // es aquel que no tiene parametros de llamada
    SuperClase(){}
      
    // Este es el constructor parametrizado de la superclase
    SuperClase( int pDato ) {
        System.out.println( 
            "Dentro del constructor de la SuperClase" ); 
        varInstancia = pDato; 
        }
      
    void verVarInstancia() {
        System.out.println( "El Objeto contiene " + varInstancia );
        }
    }
      
class SubClase extends SuperClase {
    // Este es el constructor parametrizado de la subclase
    SubClase( int bDato ) {    
        // La siguiente sentencia println no compila, la llamada
        // a super() debe estar al principio de un metodo en caso de
        // que aparezca
        // System.out.println( "En el constructor de la SubClase" ); 
      
        // Llamamos al constructor de la superclase
        super( bDato );
        System.out.println( 
            "Dentro del constructor de la SubClase" );
        }
    }
      
class java508 {
    public static void main( String args[] ) {
        System.out.println( "Lanzando la aplicacion" );
      
        // Instanciamos un objeto de este tipo llamando al
        // constructor de defecto 
        java508 obj = new java508();
        // Llamamos a la funcion pasandole un constructor de la
        // subclase parametrizado como parametro
        obj.miFuncion( new SubClase( 100 ) );
        }
      
        // Esta funcion recibe un objeto y llama a uno de sus metodos
        // para presentar en pantalla el dato que contiene el objeto,
        // en este caso el metodo es heredado de la SuperClase
        void miFuncion( SubClase objeto ) {
            objeto.verVarInstancia();
        }
    }

Si super no aparece como primera sentencia del cuerpo de un constructor, el compilador Java inserta una llamada implícita, super(), al constructor de la superclase inmediata. Es decir, el constructor por defecto de la superclase es invocado automáticamente cuando se ejecuta el constructor para una nueva subclase, si no se especifica un constructor parametrizado para llamar al constructor de la superclase.

Control de Acceso

El control de acceso también tiene un significado especial cuando se trata de constructores. Aunque en otra sección se trata a fondo el tela del control de acceso en Java, con referencia a los constructores se puede decir que el control de acceso que se indique determina la forma en que otros objetos van a pode instanciar objetos de la clase. En la siguiente descripción, se indica cómo se trata el control de acceso cuando se tienen entre manos a los constructores:

private

Ninguna otra clase puede instanciar objetos de la clase. La clase puede contener métodos públicos, y estos métodos pueden construir un objeto y devolverlo, pero nadie más puede hacerlo.

protected

Solamente las subclases de la clase pueden crear instancias de ella.

public

Cualquier otra clase puede crear instancias de la clase.

package

Nadie desde fuera del paquete puede construir una instancia de la clase. Esto es útil si se quiere tener acceso a las clases del paquete para crear instancias de la clase, pero que nadie más pueda hacerlo, con lo cual se restringe quien puede crear instancias de la clase.

En Java y en C++, una instancia de una clase, un objeto, contiene todas las variables y métodos de instancia de la clase y de todas sus superclases. Sin embargo, los dos lenguajes soportan la posibilidad de sobreescribir un método declarado en una superclase, indicando el mismo nombre y misma lista de argumentos; aunque los procedimientos para llevar a cabo esto son totalmente diferentes en Java y en C++.

Como aclaración a terminología que se empleo en este documento, quiero indicar que cuando digo sobrecargar métodos, quiero decir que Java requiere que los dos métodos tengan el mismo nombre, devuelvan el mismo tipo, pero tienen una diferente lista de argumentos. Y cuando digo sobreescribir métodos, quiero decir que Java requiere que los dos métodos tengan el mismo nombre, mismo tipo de retorno y misma lista de argumentos de llamada.

En Java, si una clase define un método con el mismo nombre, mismo tipo de retorno y misma lista de argumentos que un método de una superclase, el nuevo método sobreescribirá al método de la superclase, utilizándose en todos los objetos que se creen en donde se vea involucrado el tipo de la subclase que sobreescribe el método.

Finalizadores

Java no utiliza destructores (al contrario que C++) ya que tiene una forma de recoger automáticamente todos los objetos que se salen del alcance. No obstante proporciona un método que, cuando se especifique en el código de la clase, el reciclador de memoria (garbage collector) llamará:

    // Cierra el canal cuando este objeto es reciclado
    protected void finalize() {
        close();
        }

Cada objeto tiene el método finalize(), que es heredado de la clase Object. Si se necesitase realizar alguna limpieza asociada con la memoria, se puede sobreescribir el método finalize() y colocar en él el código que sea necesario.

Los programadores C++ deben tener en cuenta que el método finalize() no es un destructor. En C++, si existe un destructor, éste será invocado siempre que el objeto se salga de ámbito o vaya a ser destruido. En Java, aunque el método finalize() siempre se invocará antes de que el reciclador de memoria libere la zona de memoria ocupada por el objeto, no hay garantía alguna de que el reciclador de memoria reclame la memoria de un determinado objeto, es decir, no hay garantía de que el método finalize() sea invocado.

La regla de oro a seguir es que no se debe poner ningún código que deba ser ejecutado en el método finalize(). Por ejemplo, si se necesita concluir la comunicación con un servidor cuando ya no se va a usar un objeto, no debe ponerse el código de desconexión en el método finalize(), porque puede que nunca se llamado. Luego, en Java, es responsabilidad del programador escribir métodos para realizar limpieza que no involucre a la memoria ocupada por el objeto y ejecutarlos en el instante preciso. El método finalize() y el reciclador de memoria son útiles para liberar la memoria de la pila y debería restringirse su uso solamente a eso, y no depender de ellos para realizar ningún otro tipo de limpieza.

No obstante, Java dispone de dos métodos para asegurar que los finalizadores se ejecuten. Los dos métodos habilitan la finalización a la salida de la aplicación, haciendo que los finalizadores de todos los objetos que tengan finalizador y que todavía no hayan sido invocados automáticamente, se ejecuten antes de que la Máquina Virtual Java concluya la ejecución de la aplicación. Estos dos métodos son:

runFinalizersOnExit( boolean ), método estático de java.lang.Runtime, y

runFinalizersOnExit( boolean ), método estático de java.lang.System

Una clase también hereda de sus superclase el método finalize(), y en caso necesario, debe llamarse una vez que el método finalize() de la clase haya realizado las tareas que se le hayan encomendado, de la forma:

    super.finalize();

En la construcción de un objeto, se desplaza uno por el árbol de jerarquía, de herencia, desde la raíz del árbol hacia las ramas, y en la finalización, es al revés, los desplazamientos por la herencia debe ser desde las ramas hacia las superclases hasta llegar a la clase raíz.

Navegador

Home | Anterior | Siguiente | Indice | Correo