Está en la página 1de 35

CONSTRUCTORES EN JAVA

1. Qué es un constructor en Java

Un constructor inicializa un objeto cuando se crea. Tiene el


mismo nombre que su clase y es sintácticamente similar a un método.
Sin embargo, los constructores no tienen un tipo de devolución
explícito. Normalmente, usará un constructor para dar valores iniciales
a las variables de instancia definidas por la clase, o para realizar
cualquier otro procedimiento de inicio requerido para crear un objeto
completamente formado.

Todas las clases tienen constructores, ya sea que usted defina uno o no,
porque Java proporciona automáticamente un constructor
predeterminado. En este caso, las variables de miembro no
inicializadas tienen sus valores predeterminados, que son cero, null y
false, para tipos numéricos, tipos de referencia y booleanos,
respectivamente. Una vez que defines tu propio constructor, el
constructor predeterminado ya no se usa.

Aquí hay un ejemplo simple que usa un constructor:


//Un ejemplo simple de constructor

class MiClase {

int x;

MiClase(){

x=10;

class ConstructorDemo {

public static void main(String[] args) {

MiClase t1= new MiClase();

MiClase t2= new MiClase();

System.out.println(t1.x + " - "+t2.x);

En este ejemplo, el constructor de MiClase es:

MiClase(){

x=10;

}
Este constructor asigna a la variable de instancia x de MiClase el valor
de 10. Este constructor es llamado por new cuando se crea un objeto.
Por ejemplo, en la línea:

MiClase t1= new MiClase();

El constructor MiClase() es llamado en el objeto t1, dando a t1.x el valor


de 10. Lo mismo es cierto para t2. Después de la construcción, t2.x tiene
el valor de 10. Por lo tanto, el resultado del programa es:

10 - 10

2. Constructores parametrizados

En el ejemplo anterior, se utilizó un constructor sin parámetros. Aunque


esto está bien para algunas situaciones, la mayoría de las veces
necesitará un constructor que acepte uno o más parámetros.
Los parámetros se agregan a un constructor de la misma manera que
se agregan a un método: simplemente declararlos dentro del paréntesis
después del nombre del constructor. Por ejemplo, aquí, MiClase tiene un
constructor parametrizado:

//Un ejemplo de constructor parametrizado

class MiClase {

int x;

MiClase(int i){

x=i;

class ConstructorDemo {

public static void main(String[] args) {

MiClase t1= new MiClase(15);

MiClase t2= new MiClase(28);

System.out.println(t1.x + " - "+t2.x);

La salida de este programa es:


15 - 28

En esta versión del programa, el constructor MiClase() define un


parámetro llamado i, que se usa para inicializar la variable de instancia,
x. Por lo tanto, cuando la línea:

MiClase t1= new MiClase(15);

se ejecuta, el valor 10 pasa a i, que luego se asigna a x.

3. Ejemplo de Constructor en Java

Siguiendo nuestro ejemplo anterior, vamos a agregar un constructor a


la clase Vehiculo:

//Un ejemplo de constructor

class Vehiculo {
int pasajeros; //números de pasajeros

int capacidad; //capacidad del combustible en galones

int mpg; //combustible consumido en miles por galon

//Esto es un constructor para Vehiculo

Vehiculo (int p, int c, int m) {

pasajeros=p;

capacidad=c;

mpg=m;

//Retornando el rango

int rango(){

return mpg*capacidad;

//Calcular el combustible necesario para una distancia dada

double capacidadnueva(int miles){

return (double)miles/mpg;

class DemoVehiculo {

public static void main(String[] args) {

Vehiculo minivan = new Vehiculo(9,15,20);

Vehiculo sportscar = new Vehiculo(10,25,30);

double galones;
int distancia = 250;

galones=minivan.capacidadnueva(distancia);

System.out.println("Para ir a "+distancia+" en
minivan, se necesita "+galones+" galones");

galones=sportscar.capacidadnueva(distancia);

System.out.println("Para ir a "+distancia+" en
sportscar, se necesita "+galones+" galones");

Salida:

Para ir a 250 en minivan, se necesita 12.5 galones

Para ir a 250 en sportscar, se necesita 8.333333333333334


galones

Ambos, minivan y sportscar, son inicializados por el constructor


Vehiculo() cuando se crean. Cada objeto se inicializa como se especifica
en los parámetros para el constructor. Por ejemplo, en la siguiente línea,
Vehiculo minivan = new Vehiculo(9,15,20);

los valores 9, 15, 20 se pasan al constructor Vehiculo() cuando new


crea el objeto. Por lo tanto, la copia de pasajeros, capacidad y mpg de
minivan contendrá los valores 9, 15, 20, respectivamente. El resultado
de este programa es el mismo que la versión anterior.

SOBRECARGA DE CONSTRUCTORES EN JAVA

1. ¿Cuándo usar la sobrecarga de constructores?

Algunas veces hay una necesidad de inicializar un objeto de diferentes


maneras. Esto se puede hacer usando la sobrecarga de constructor.
Hacerlo le permite construir objetos de varias maneras.

Tomemos un ejemplo para comprender la necesidad de sobrecargar


constructores. Considere el siguiente programa:

//Demostración de Sobrecarga de constructores


class MiClase{

int x;

MiClase(){

System.out.println("Dentro de MiClase().");

x=0;

MiClase(int i){

System.out.println("Dentro de MiClase(int).");

x=i;

MiClase(double d){

System.out.println("Dentro de MiClase(double).");

x=(int)d;

MiClase(int i, int j){

System.out.println("Dentro de MiClase(int, int).");

x=i*j;

class DemoSobrecargaConstructor{

public static void main(String[] args) {

MiClase t1=new MiClase();

MiClase t2=new MiClase(28);


MiClase t3=new MiClase(15.23);

MiClase t4=new MiClase(2,4);

System.out.println("t1.x: "+ t1.x);

System.out.println("t2.x: "+ t2.x);

System.out.println("t3.x: "+ t3.x);

System.out.println("t4.x: "+ t4.x);

Salida:

Dentro de MiClase().

Dentro de MiClase(int).

Dentro de MiClase(double).

Dentro de MiClase(int, int).

t1.x: 0

t2.x: 28

t3.x: 15

t4.x: 8
MiClase() está sobrecargado de cuatro maneras, cada una construyendo
un objeto de manera diferente. El constructor apropiado se llama en
función de los parámetros especificados cuando se ejecuta. Al
sobrecargar el constructor de una clase, le da flexibilidad al usuario de
su clase en la forma en que se construyen los objetos.

2. Objeto inicializa otro objeto

Una de las razones más comunes por las que los constructores están
sobrecargados es permitir que un objeto inicialice otro. Por
ejemplo, considere este programa que usa la clase Summation para
calcular la suma de un valor entero:

//Inicializar un objeto con otro

class Suma{

int sum;

//Constructor desde un int

Suma(int num){

sum=0;

for (int i=1;i<=num;i++)

sum+=i;
}

//Constructor desde otro objeto

Suma (Suma obj){

sum=obj.sum;

class DemoSobrecargaConstructor{

public static void main(String[] args) {

Suma s1=new Suma(5);

Suma s2=new Suma(s1);

System.out.println("s1.sum: "+s1.sum);

System.out.println("s2.sum: "+s2.sum);

Salida:

s1.sum: 15

s2.sum: 15
A menudo, como muestra este ejemplo, una ventaja de proporcionar un
constructor que usa un objeto para inicializar otro es la eficiencia. En
este caso, cuando se construye s2, no es necesario recalcular la suma.

Por supuesto, incluso en los casos en que la eficiencia no es un


problema, a menudo es útil proporcionar un constructor que haga una
copia de un objeto.

3. Usando this() en sobrecarga de constructores

La referencia this() se puede utilizar durante la sobrecarga del


constructor para llamar implícitamente al constructor
predeterminado desde el constructor parametrizado. Tenga en cuenta
que debería ser la primera declaración dentro de un constructor.

//Programa Java para ilustrar el rol de this()

// en la sobrecarga del constructor

class DemoSCT

double largo, ancho, alto;


int numero;

// Constructor utilizado cuando todas las dimensiones

// se especifican

DemoSCT(double w, double h, double d, int num)

largo = w;

ancho = h;

alto = d;

numero = num;

// Constructor utilizado cuando no se especificaron


dimensiones

DemoSCT()

// Vacio

largo = ancho = alto = 0;

// Constructor utilizado cuando solo se especifica numero

DemoSCT(int num)

// this() se utiliza para llamar al constructor


predeterminado

// desde el constructor con parámetros

this();
numero = num;

public static void main(String[] args)

// crear DemoSCT usando solo numero

DemoSCT DemoSCT1 = new DemoSCT(5);

// obteniendo el valor inicial de largo en DemoSCT1

System.out.println(DemoSCT1.largo);

Salida:

0.0

Como podemos ver en el programa anterior, llamamos al constructor


DemoSCT(int num) durante la creación del objeto utilizando solo el
número. Al usar la instrucción this() dentro de ella, el constructor
predeterminado DemoSCT() es invocado implícitamente desde allí.
Nota: La llamada del constructor debe ser la primera declaración en el
cuerpo del constructor. Por ejemplo, el siguiente fragmento no es válido
y arroja un error de tiempo de compilación.

DemoSCT(int num)

numero = num;

/* La llamada de constructor debe ser la primera

instrucción en un constructor */

this(); /*ERROR*/

Puntos importantes que deben tenerse en cuenta al hacer sobrecarga


de constructores:

 La llamada del constructor con this(), debe ser la primera declaración


del constructor en Java.
 Si hemos definido cualquier constructor parametrizado, el compilador
no creará el constructor predeterminado. Y viceversa, si no definimos
ningún constructor, el compilador crea el constructor predeterminado
(también conocido como constructor sin-argumentos) de forma
predeterminada durante la compilación.

 La invocación recursiva al constructor no es válida en java.

4. Sobrecarga de constructores vs Sobrecarga de métodos

Estrictamente hablando, la sobrecarga del constructor es algo similar a


la sobrecarga de métodos. Si queremos tener diferentes formas de
inicializar un objeto usando un número diferente de parámetros,
entonces debemos hacer una sobrecarga de constructor ya que
hacemos sobrecarga de método cuando queremos diferentes
definiciones de un método basadas en diferentes parámetros.
PALABRA RESERVADA “SUPER” EN JAVA

En Java, la palabra reservada super es una variable de referencia que


se usa para referir objetos de clase padre.

1. Uso de super con variables

Este escenario ocurre cuando una clase derivada y una clase base tienen
los mismos miembros de datos. En ese caso, existe una posibilidad de
ambigüedad para la JVM. Podemos entenderlo más claramente usando
este fragmento de código:

/* clase base vehicle */

class Vehicle

int maxSpeed = 120;

/* subclase Car extendiendo de vehicle */

class Car extends Vehicle

{
int maxSpeed = 180;

void display()

/* imprime maxSpeed de la clase base (vehicle) */

System.out.println("Velocidad máxima: " +


super.maxSpeed);

/* Programa de controlador Test */

class Test

public static void main(String[] args)

Car small = new Car();

small.display();

Salida:

Velocidad máxima: 120


En el ejemplo anterior, tanto la clase base como la subclase tienen un
miembro maxSpeed. Podríamos acceder a maxSpeed de la clase base
en la subclase usando la palabra reservada super.

2. Uso de super con métodos

Esto se usa cuando queremos llamar al método de clase padre.


Entonces, cuando una clase padre e hijo tienen los mismos métodos
nombrados, entonces para resolver la ambigüedad utilizamos la palabra
reservada super. Este fragmento de código ayuda a comprender el uso
de la palabra reservada super:

/* Clase Base Person */

class Person

void message()

System.out.println("Esta es una clase persona");

/* Subclase Student */
class Student extends Person

void message()

System.out.println("Esta es una clase estudiante");

// Tenga en cuenta que display() solo está en la clase


Student

void display()

// invocará o llamará al método message() de la clase


actual

message();

// invocará o llamará al método message() de la clase


padre

super.message();

/* Programa Controlador Test */

class Test

public static void main(String args[])

Student s = new Student();


// llamando a display() de Student

s.display();

Salida:

Esta es una clase estudiante

Esta es una clase persona

En el ejemplo anterior, hemos visto que si solo llamamos al método


message(), entonces se invoca el message() de la clase actual, pero con
el uso de la palabra reservada super, también se puede invocar a
message() de la superclase.
3. Uso de super con constructores

La palabra reservada super también se puede usar para acceder al


constructor de la clase padre. Una cosa más importante es que ”super”
puede llamar constructores tanto con parámetros como sin parámetros
dependiendo de la situación. A continuación se muestra el fragmento de
código para explicar el concepto anterior:

/* superclase Person */

class Person

Person()

System.out.println("Constructor de la clase Person");

/* subclase Student extiende de la clase Person */

class Student extends Person

Student()

// invoca o llama al constructor de la clase padre


super();

System.out.println("Constructor de la clase Student");

/* Programa Controlador Test */

class Test

public static void main(String[] args)

Student s = new Student();

Salida:

Constructor de la clase Person

Constructor de la clase Student


En el ejemplo anterior hemos llamado al constructor de la superclase
usando la palabra reservada ‘super‘ a través del constructor de la
subclase.

4. Otros puntos importantes de ‘super’

 La llamada a super() debe ser la primera instrucción en el


constructor de la clase derivada (Student en el ejemplo).

 Si un constructor no invoca explícitamente un constructor de


superclase, el compilador de Java inserta automáticamente una
llamada al constructor sin argumento de la superclase. Si la
superclase no tiene un constructor sin argumentos, obtendrá un error
en tiempo de compilación. Por ejemplo, el objeto hace tener un
constructor, por lo que si objeto es la única superclase, no hay ningún
problema.

 Si un constructor de una subclase invoca un constructor de su


superclase, ya sea explícita o implícitamente, puede pensar que se
llamó a toda una cadena de constructores, todo el camino de regreso
al constructor de Object. Esto, de hecho, es el caso. Se llama
encadenamiento de constructores.

CLASES CON DOS O MÁS CONSTRUCTORES. SOBRECARGA DE


CONSTRUCTORES O MÉTODOS.

En este apartado vamos a ver cómo una clase en Java puede tener más
de un constructor y a entender qué implicaciones y significado tiene
esto. Escribiremos el código de una clase y lo compilaremos para ir
analizando con base a este código cómo se generan clases con varios
constructores y el significado del concepto de sobrecarga de
constructores o métodos.

Escribe y compila el siguiente código:

//Ejemplo de clase con dos constructores y un método

public class Persona {


private String nombre;
private int edad;

public Persona (String nombrePersona) { //CONSTRUCTOR 1


nombre = nombrePersona;
edad = 0; }

public Persona () { //CONSTRUCTOR 2


nombre = "";
edad = 0; }

public String getNombre () { return nombre; }


//Cierre del método
} //Cierre de la clase

Hemos definido una clase, denominada Persona, que nos permite crear
objetos de tipo Persona. Todo objeto de tipo Persona estará definido por
dos campos: nombre (tipo String) y edad (tipo entero), y admitirá un
método: getNombre(). Al realizar la invocación
nombreDelObjeto.getNombre() obtendremos el atributo nombre del
objeto.

La clase tiene dos constructores. ¿Qué significado tiene esto? Pues que
podremos crear personas de dos maneras distintas:

1. Personas que se creen con el constructor 1: habrá de indicarse,


además del nombre del objeto, el parámetro que transmite el
nombre de la persona.
2. Personas que se creen con el constructor 2: no requieren
parámetros para su creación y se inicializan a unos valores por
defecto (nombre cadena vacía y edad cero).

Cuando más de un constructor o método tienen el mismo nombre pero


distintos parámetros decimos que el constructor o método está
sobrecargado. La sobrecarga de constructores o métodos permite llevar
a cabo una tarea de distintas maneras (por ejemplo crear un objeto
Persona con un nombre ya establecido o crearlo sin nombre
establecido).

La existencia de dos constructores se ha visto reflejada en que


disponemos de más de una opción de new Persona para crear objetos.
Según la opción que elijamos, el objeto Persona se creará de una forma
u otra. Esto nos lleva a la conclusión de que cada constructor define una
forma de crear objetos.

EJERCICIO

Define una clase Profesor considerando los siguientes atributos de clase:


nombre (String), apellidos (String), edad (int), casado (boolean),
especialista (boolean). Define un constructor que reciba los parámetros
necesarios para la inicialización y otro constructor que no reciba
parámetros. Crea los métodos para poder establecer y obtener los
valores de los atributos. Compila el código para comprobar que no
presenta errores, crea un objeto usando un constructor y luego otro
objeto usando el otro constructor. Comprueba que se inicializan
correctamente consultando el valor de sus atributos después de haber
creado los objetos. Para comprobar si es correcta tu solución puedes
consultar en los foros aprenderaprogramar.com.

SOBREESCRIBIR MÉTODOS EN JAVA. MÉTODOS


POLIMÓRFICOS

Ya sabemos que en Java las variables que representan un objeto no


son el objeto mismo, sino referencias al objeto.

Cuando hablamos de una variable que apunta a un objeto podemos


distinguir, debido a la herencia, dos tipos:

1. El tipo declarado para la variable en el código fuente: se llama tipo


estático.

2. El tipo del objeto al que apunta la variable en un momento dado


(en tiempo de ejecución): puede ser el tipo declarado o un subtipo
del tipo declarado, y se llama tipo dinámico.
Ejemplo:

ProfesorInterino profesorInterino43 = new ProfesorInterino();

Persona p1 = new Persona(); p1 = profesorInterino43;

El tipo estático de p1 es Persona, mientras que tras la ejecución de


la tercera instrucción su tipo dinámico pasa a ser ProfesorInterino
porque el objeto al que pasa a apuntar es a un profesor interino.
Digamos que inicialmente el contenedor de p1 es una caja de tipo
“Persona” y que en la tercera línea la caja pasa a ser de tipo
“ProfesorInterino”.

Ejemplo:

Persona p1 = new ProfesorInterino();

El tipo estático es Persona y el tipo dinámico es ProfesorInterino.


¿Por qué? Porque estamos creando una variable de tipo Persona e
inmediatamente asignándole de forma dinámica un objeto anónimo
de tipo ProfesorInterino. Sin embargo, no podemos invocar un
método exclusivo de ProfesorInterino sobre p1 porque el compilador
se basa en los tipos estáticos para hacer comprobaciones.
El compilador controla los tipos basándose en el tipo estático
(declarado): no conoce si en un momento dado una variable está
apuntando a un subtipo. Debido a esto, no podremos llamar a un
método de un subtipo si la variable corresponde a una superclase,
aunque la variable apunte a la subclase, porque el compilador no
llega a conocer a qué tipo apunta la variable en tiempo de ejecución.
Como veremos a continuación, esto es una limitación relativa, porque
en los métodos que denominamos “sobreescritos” sí se llega a
identificar si un método corresponde a un supertipo o a un subtipo.

Decimos que un método está sobreescrito cuando está presente,


exactamente con la misma signatura, en una subclase y en una
superclase. En esta situación un objeto de la subclase tiene dos
métodos con el mismo nombre y la misma signatura: uno heredado
de la superclase y otro definido en su propia estructura. Una subclase
puede sobreescribir la implementación de un método. Para hacerlo,
la subclase declara un método con la misma signatura que la
superclase, pero con un cuerpo diferente (hacerlo con el mismo
cuerpo sería repetitivo y no tendría mucho sentido). El método
sobreescrito tiene precedencia cuando se invoca sobre objetos de la
subclase. Por el contrario, para los objetos de la subclase el método
de la superclase pierde visibilidad.

Si como hemos dicho, el compilador se basa en el tipo estático para


su trabajo, podríamos pensar que si invocamos a un objeto de tipo
estático “superclase” y tipo dinámico “subclase” con un método
sobreescrito, el método que se utilice sería el propio de la superclase.
Pero sin embargo, esto no es así: el control de tipos del compilador
se basa en los tipos estáticos pero en tiempo de ejecución los
métodos que se ejecutan dan preferencia al tipo dinámico. Es decir,
en tiempo de ejecución Java está constantemente “buscando”
(“ligando” o “despachando”) el método que corresponda en función
del tipo dinámico al que apunta una variable.

Si el método invocado no se encuentra definido en el tipo dinámico


de que se trate, Java busca el método en la superclase para tratar
de ejecutarlo. Si no lo encuentra en la superclase, continúa subiendo
niveles de clase hasta alcanzar a la clase Object. Si en la clase Object
tampoco se encontrara un método con ese nombre, el programa no
llegaría a compilar.

Decimos que los métodos en Java son polimórficos porque una


misma llamada en un programa puede dar lugar a la ejecución de
distintos métodos según el tipo dinámico de una variable. Ya
tenemos una segunda forma de expresión del polimorfismo en Java:
además de polimorfismo en variables, hablamos de polimorfismo en
métodos.
Tenemos que distinguir dos momentos temporales en un programa,
la compilación durante la cual el compilador realiza una serie de
verificaciones y transforma el código fuente en código máquina, y la
ejecución, durante la cual una variable puede cambiar de tipo debido
al polimorfismo que admite Java. El compilador solo conoce un tipo:
el tipo estático o declarado en el código fuente. No obstante, durante
la ejecución la máquina virtual Java es capaz de identificar el tipo
dinámico de las variables que apuntan a objetos y asignar un método
u otro en función de ese tipo dinámico.

Esto tiene ciertos riesgos. Si pensamos que hemos sobreescrito un


método para que haga una tarea específica por nosotros requerida,
pero no es así o lo hemos borrado, aparentemente el programa no
tendrá problemas de compilación si se encuentra el método en una
superclase. Sin embargo, los resultados pueden no ser los deseados
y esto generarnos un quebradero de cabeza al no saber determinar
con precisión por qué ocurre lo que ocurre. Debido a ello, muchos
programadores tienen por costumbre dentro de los primeros pasos
al definir una clase, sobreescribir algunos métodos como toString y
equals para esos métodos respondan en la clase exactamente como
se pretende y no de acuerdo a una especificación de una superclase
que puede no ser adecuada.
EJERCICIO

Responde a las siguientes preguntas:

Supongamos la superclase Vehiculo que representa a cualquier tipo de


vehículo y la subclase Taxi que representa a un tipo de vehículo
concreto.

1. ¿Un objeto de tipo estático declarado Taxi puede contener a un


objeto Vehiculo en tiempo de ejecución?

2. ¿Un objeto de tipo estático declarado Vehiculo puede contener a


un objeto Taxi en tiempo de ejecución?

3. Escribe el código de una clase Vehiculo con los atributos matricula


(String) y numeroDeRuedas (int), constructor, métodos getters y
setters y método toString() para mostrar la información de un
vehículo.

4. Escribe el código de una clase Taxi que herede de vehículo y que


además de los atributos de Vehiculo lleve un atributo adicional
nombreDelConductor (String) y numeroDePlazas (int),
constructor, métodos getters y setters y método toString() para
mostrar la información de un Taxi.

5. Escribe el código de una clase test con el método main que cree
un objeto cuyo tipo es Vehiculo, instanciado como Taxi. Establece
valores para sus atributos y usa el método toString(). ¿Qué
método toString() resulta de aplicación, el propio de la clase
Vehiculo o el propio de la clase Taxi? ¿Por qué?

También podría gustarte