This y super en Java

reset css

Las palabras reservadas this y super de Java nos sirven para poder acceder a los atributos, métodos y constructores de la clase en la que se encuentran y de la clase padre respectivamente.

This en java

This hace referencia al objeto actual de la clase, es decir, a una instancia concreta de la clase y nos sirve para usar los métodos y atributos de esa clase desde alguno de sus métodos, para llamar a otro de sus constructores o simplemente para pasarle el objeto completo a algún otro método u objeto.

Aunque normalmente podemos omitir el this cuando hacemos una llamada a un método o atributo de la misma clase ya que el compilador sabe que pertenece a la clase desde la que se usa porque si no ese método o atributo debería de llevar delante el nombre del objeto al que se refiere, si lo usamos el código puede ser un poco más claro y nos evitamos problemas si alguien crea una variable con el mismo nombre que uno de los atributos de la clase, porque en este caso sí que es necesario usar el this para acceder a la variable de la clase ya que la variable local «oculta» a la de clase.

Por eso normalmente siempre veremos el this. delante de los atributos en los constructores y en los setters ya que lo normal es que los parámetros tengan los mismos nombres que los atributos de la clase, mientras que en los getters no se suele poner porque no hay ninguna duda de a que es a lo que estamos accediendo.


package thisysuper;

public class Archivo {
    private String nombre;
    private String extension;
    private String ruta;
        
    public Archivo(String nombre, String extension) {
        this.nombre = nombre;
        this.extension = extension;
        this.ruta = "";
    }

    public Archivo(String nombre, String extension, String ruta) {
        this(nombre, extension);
        this.ruta = ruta;
    }

    public String getNombre() {
        return nombre;
    }

    public void setNombre(String nombre) {
        this.nombre = nombre;
    }

    public String getExtension() {
        return extension;
    }

    public void setExtension(String extension) {
        this.extension = extension;
    }

    public String getRuta() {
        return ruta;
    }

    public void setRuta(String ruta) {
        this.ruta = ruta;
    }

    @Override
    public String toString() {
        return this.getRuta() + this.getNombre() + "." + this.getExtension();
    }

}

En el código anterior también podemos ver como this también se puede usar para llamar a otro de los constructores de la clase.

Super en java

Si this nos sirve para hacer referencia a la instancia actual del objeto, super nos sirve para hacer lo propio con la clase padre de nuestro objeto (la clase de la se hace el extends), vamos que nos permite acceso a los constructores, métodos y atributos de la clase de la que hereda.


package thisysuper;

import java.awt.Dimension;

public class Imagen extends Archivo {
    private Integer ancho;
    private Integer alto;

    public Imagen(String nombre, String extension, String ruta) {
        super(nombre, extension, ruta);
        this.ancho = 1920;
        this.alto = 1080;
    }

    public Imagen(String nombre, String extension, String ruta, Integer ancho, Integer alto) {
        this.ancho = ancho;
        this.alto = alto;
    }

    public Imagen(String nombre, String extension, String ruta, Dimension dimensiones) {
        this(nombre, extension, ruta, dimensiones.width, dimensiones.height);
    }

    public Integer getAncho() {
        return ancho;
    }

    public void setAncho(Integer ancho) {
        this.ancho = ancho;
    }

    public Integer getAlto() {
        return alto;
    }

    public void setAlto(Integer alto) {
        this.alto = alto;
    }

    @Override
    public String toString() {
        return super.toString() + ", Dimensiones: " + this.ancho + " X " + this.alto;
    }

}

En este ejemplo vemos como en el método toString() usamos el super para hacer la llamada al método del padre, aunque en este caso estamos haciendo la llamada al método con el mismo nombre del padre no existe nada que nos impida llamar a otro de sus métodos o a todos los que queramos.


public class Main {

    public static void main(String[] args) {
        Archivo imagen1 = new Imagen("foto", "png", "/home/ivan/Documents/Proyecto/");
        System.out.println(imagen1.toString());
        // /home/ivan/Documents/Proyecto/foto.png, Dimensiones: 1920 X 1080
    }
}

Como el toString() de la clase Imagen llama al de la clase Archivo y luego le concatena los valores de los atributos de la clase imagen el resultado final es nombre del archivo junto con las dimensiones.

También tenemos un ejemplo de una llamada a uno de los constructores de la clase padre desde uno de los constructores para inicializar las propiedades de la clase padre al crear una nueva instancia de la clase hija (en realidad, se está llamando a 2 de los constructores de la clase padre, pero eso lo veremos más adelante).

En este caso no podemos acceder directamente a las propiedades de la clase padre con super. porque las declaramos como privadas y super solo nos permite acceder a los métodos y atributos declarados como public o protected, es decir que no nos otorga ningún privilegio de acceso extra.

Constructor this()

Cuando una clase tiene varios constructores podemos usar this() para hacer uso de otro de los constructores de la clase y de este modo nos ahorramos tener que repetir el mismo código en todos los constructores y así solo necesitamos hacer una llamada a otro constructor y añadir el código necesario para las diferencias.

Si se llama a otro constructor con this() siempre hay que hacerlo desde la primera línea del constructor (la primera línea de código, delante puede haber comentario o líneas en blanco) porque si intentamos hacerlo después de otra línea nuestro IDE nos mostrará un error como este Constructor call must be the first statement in a constructor, y no nos compilará.

Por ejemplo podríamos tener estos 4 constructores donde tenemos un constructor sin parámetros que rellena los atributos con los valores por defecto y un constructor con un parámetro, otro con 2 y otro con los 3 y cada uno de ellos llama al constructor que tiene un parámetro menos.


public Archivo() {
    this.nombre = "archivo";
    this.extension = "txt";
    this.ruta = "";
}

public Archivo(String nombre) {
    this();
    this.nombre = nombre;
}
    
public Archivo(String nombre, String extension) {
    this(nombre);
    this.extension = extension;
}
    
public Archivo(String nombre, String extension, String ruta) {
    this(nombre, extension);
    this.ruta = ruta;
}

Como la llamada desde cada constructor a otro de los constructores de la clase se hace en la primera línea en el caso de nuestro ejemplo primero se llama al constructor sin parámetros, luego al que tiene 1, después al que tiene 2 y finalmente al que tiene 3 y por lo tanto siempre conseguimos inicializar nuestro objeto correctamente.


public class Main {

    public static void main(String[] args) {
        Archivo archivo1 = new Archivo();
        System.out.println(archivo1.toString());
        // archivo.txt

        Archivo archivo2= new Archivo("lista_compra");
        System.out.println(archivo2.toString());
        // lista_compra.txt

        Archivo archivo3 = new Archivo("lista_compra", "doc");
        System.out.println(archivo3.toString());
        // lista_compra.doc

        Archivo archivo4 = new Archivo("lista_compra", "doc", "/home/ivan/Documents/");
        System.out.println(archivo4.toString());
        // /home/ivan/Documents/lista_compra.doc
    }
}

Constructor super()

Si una clase hereda de otra sus constructores en su primera línea siempre tienen que llamar a alguno de los constructores de clase padre con super() o a otro de sus constructores con this() de forma que al final siempre se llame a alguno de los constructores de la clase padre.

Si la clase padre tiene un constructor sin parámetros entonces no es necesario hacer la llamada con super() porque por defecto Java hace la llamada al constructor sin parámetros de la clase padre, que es lo que sucede con este constructor de nuestro ejemplo.


public Imagen(String nombre, String extension, String ruta, Integer ancho, Integer alto) {
    this.ancho = ancho;
    this.alto = alto;
}

Como no se hace una llamada explicita a ninguno de los constructores de la clase padre con super o a otro de los constructores de esta clase con this el compilador hace por nosotros la llamada al constructor sin parámetros de la clase padre.

El resultado de utilizar cada uno de los 3 constructores de esta clase es el siguiente:


public class Main {

    public static void main(String[] args) {
        Archivo imagen1 = new Imagen("foto", "png", "/home/ivan/Documents/Proyecto/");
        System.out.println(imagen1.toString());
        // /home/ivan/Documents/Proyecto/foto.png, Dimensiones: 1920 X 1080

        Archivo imagen2 = new Imagen("casa", "png", "/home/ivan/Documents/Proyecto/", 2500, 4000);
        System.out.println(imagen2.toString());
        // archivo.txt, Dimensiones: 2500 X 4000

        Dimension dimensiones = new Dimension(300, 50);
        Imagen imagen3 = new Imagen("logo", "svg", "/home/ivan/Documents/Proyecto/", dimensiones);
        System.out.println(imagen3.toString());
        // archivo.txt, Dimensiones: 300 X 50
    }
}

En el primero se llama al constructor de la clase padre que recibe el nombre, la extensión y la ruta y para los parámetros de esta clase se usan unos valores por defecto por eso en este caso tenemos rellenos todos los atributos.

En el segundo caso no se hace referencia a ninguno de los constructores de la clase padre y por eso se usa el constructor sin parámetros de la clase padre y por eso aunque al constructor le pasamos tanto los parámetros de la una clase como de la otra al final los parámetros de la clase Archivo se están perdiendo porque no hacemos nada con ellos.

Y en el último caso en lugar de hacer referencia a unos de los constructores de la clase padre se hace referencia al constructor anterior y este a su vez es el que hace referencia a uno de los constructores de la clase padre (aunque no tiene el super()).

Podemos ir haciendo llamadas a otros constructores de la clase tantas veces como queramos pero finalmente el último constructor al que se llame tiene que hacer la llamada a alguno de los constructores de la clase padre ya sea de forma explícita o implícita siempre que la clase padre tenga un constructor sin parámetros.

¿Qué sucedería en nuestro ejemplo si eliminamos el constructor sin parámetros de la clase padre?

Pues que nuestro código no va a compilar y nos mostraría el error Implicit super constructor Archivo() is undefined. Must explicitly invoke another constructor para que incluyésemos la llamada a otro de sus constructores.

Puedes consultar el código de este ejemplo aquí.