26 de octubre de 2018

Objetos - Constructores

Para poder utilizar objetos lo primero que necesitamos es un constructor que nos construya un objeto de la clase donde tenemos el constructor. Esto se llama instanciar un objeto.

Los constructores se definen de manera similar a un método, pero su identificador coincide con el nombre de la clases y no tienen tipo de retorno. Se le pueden aplicar igualmente modificadores de acceso.

Para crear objetos necesitamos un constructor. El identificador del constructor coincide con el nombre de la clase


El constructor por defecto

Vamos a seguir con nuestra clase Coche para ver ejemplos. Éste es nuestro código actual:
  public class Coche {
    String modelo;
    String color;
    int numeroDeRuedas;

    @Override
    public String toString() {
        return modelo + " (" + color + "), " + numeroDeRuedas;
    }

  }
Como hemos dicho hace falta un constructor para instanciar un objeto pero no hemos declarado ninguno. A falta de la declaración de un constructor, Java nos proporcionará un constructor por defecto sin parámetros. Lo que hará será crear un objeto con los valores a los que hayamos inicializado sus variables de instancia. En este código no tenemos inicializada ninguna variable con lo que las variables modelo y color al ser del tipo String tendrán el valor null y numeroDeRuedas tendrá valor cero.

En Java instanciamos un objeto llamando al constructor mediante la palabra reservada new seguido del método constructor que queramos usar (en este caso el constructor por defecto Coche()):
  Coche miCoche = new Coche();
  System.out.println(miCoche);
La expresión con el new devuelve un nuevo objeto del tipo Coche. y al imprimirlo en consola nos sale:

Coche [modelo=null, color=null, numeroDeRuedas=0]

Asignación a un tipo de variable "compatible"

Al declarar de qué tipo es la variable se puede acceder a los miembros que forman parte de ese tipo de objetos a los que se tenga acceso. Cuando asignamos un valor a la variable vamos a apuntarla hacia el espacio de memoria que contiene al objeto creado, así podremos utilizar nuestro nuevo objeto del tipo Coche.

Recuerdo que es importante diferenciar el concepto de variable del concepto de objeto:
  1. Una variable puede usarse para referenciar a múltiples objetos compatibles, pero sólo a uno a la vez
  2. Las variables NO tienen una copia del objeto asignado ni ocupan espacio en la memoria como crear un nuevo objeto.
  3. Los objetos ocuparán el mismo espacio en memoria independientemente de las referencias que le apunten.
  4. Asignar de nuevo una variable no modifica el objeto que contenía antes, pero modificar un objeto hará que se observe ese cambio en todas las referencias al mismo objeto (ya lo vimos con el ejemplo de arrays).
  5. El tipo de la variable indica que el objeto deber "pertenecer" a ese tipo (en la línea jerárquica), aunque no quiere decir que sea una instancia de esa clase (podría ser de una clase que hereda o implementar esa interfaz entre otras). La línea jerárquica/herencia y la implementación de interfaces se verá en su sesión correspondiente.
  6. Si el objeto pertenece a una subclase/interfaz con más miembros, la variable nos dará acceso a los miembros del tipo declarado, independientemente del tipo real del objeto al que apunta.
  7. La variable puede apuntar a null lo que produciría un error en ejecución del tipo NullPointerException si intentamos acceder a métodos de instancia.
  8. Un objeto puede tener varias o ninguna referencia (en ese caso será destruido por el Recolector de Basura para liberar recursos porque ya no se puede volver a usar)
No se pretende entender todos los puntos anteriores porque se verán en sesiones más adelante. Lo importante es saber que "UNA VARIABLE NO ES UN OBJETO".

Una variable no es un objeto


Definición de constructores

Todos esperamos que un coche tenga cuatro ruedas. Vamos a declarar el constructor por defecto para que asigne este valor:
  public Coche () {
      numeroDeRuedas = 4;
  }
Este constructor ya es más correcto y ahora la salida del último código cambiará a:

Coche [modelo=null, color=null, numeroDeRuedas=4]

Siguiendo con el ejemplo, ahora que tenemos un objeto del tipo Coche, deberíamos tener métodos para manejar sus atributos. Por ejemplo vamos a crear métodos para leer el valor del color del coche y para establecerle uno nuevo. Éstos métodos los introdujimos en la sesión anterior sobre clases (getter y setter):
  public String getColor() {
      return color;
  }

  public void setColor(String color) {
      this.color = color;
  }
Hasta que yo no le asigne un valor al color del coche el valor será null. Imaginemos que quisiéramos establecer un color determinado en el momento en que instanciamos nuestro coche. Con el constructor por defecto no podemos porque no se puede usar para asignar ningún color. Vamos a hacer una sobrecarga del constructor para tener uno que admita el color:
  public Coche (String color) {
      numeroDeRuedas = 4;
      setColor(color); // como ya tengo el setter lo utilizo
  }
Observamos que se repite la inicialización numeroDeRuedas = 4; si queremos que tome este valor al instanciarlo. Esto es un problema, si en un futuro los coches no necesitaran ruedas tendríamos que cambiar nuestros constructores (hay que huir de cambiar la misma línea en varios sitios).

Para ello tenemos dos soluciones:
  1. Inicializar el valor en la declaración de variable. Para un número no hay problemas, pero para asignar otro valor que pueda dar error en su instanciación (un objeto) tenemos que protegernos de la posible excepción que rompería nuestro código. Este último problema lo solucionaremos en la sesión de inicializadores estáticos.
  2. Llamar al código que ya tenía.
Hay partidarios de las dos formas. Personalmente soy más de la segunda si no se usan inicializadores estáticos. Como todavía no los hemos visto vamos a usar la segunda:
  public Coche (String color) {
      this();
      setColor(color);
  }
Como vemos estamos llamando al constructor por defecto con this() (aunque si tuviéramos más podríamos llamarlos igualmente simplemente proporcionando los parámetros que coincidieran con otro constructor) en nuestra primera línea, y así ya se asigna el valor 4 a las ruedas, y sólo queda añadir el código particular de este nuevo constructor. La única regla a esta forma es que la llamada al constructor debe ser la primera sentencia de todas.

Te propongo como ejercicio que añadas otro constructor que también admita el modelo y cuando lo tengas acabado lo compruebes con la solución.



  public Coche (String color, String modelo) {
      this(color);
      this.modelo = modelo;
  }

No hay comentarios:

Publicar un comentario

Compárteme

Entradas populares