Herencia Multinivel en Java

POO

Hasta este punto, hemos estado utilizando jerarquías simples de clase (Herencia única) que consisten solo en una superclase y una subclase. Sin embargo, puede crear jerarquías que contengan tantas capas de herencia como desee.

Como se mencionó, es perfectamente aceptable usar una subclase como una superclase de otra. Por ejemplo, dadas tres clases llamadas A, B y C, C puede ser una subclase de B, que a su vez es una subclase de A. Cuando se produce este tipo de situación, cada subclase hereda todos los rasgos encontrados en todas sus superclases. En este caso, C hereda todos los aspectos de B y A.

Lectura Recomendada
Tipos de Herencia

1. Ejemplo de Herencia Multinivel

Para ver cómo una jerarquía multinivel puede ser útil, considere el siguiente programa. En él, la subclase Triangulo se usa como una superclase para crear la subclase llamada ColorTriangulo. ColorTriangle hereda todos los rasgos de Triangulo y DosDimensiones y agrega un campo llamado color, que contiene el color del triángulo.

Ejemplo:

//Herencia Multinivel
//DosDimensiones.java
class DosDimensiones{
     private double base;
     private double altura;

     //Constructor por defecto
    DosDimensiones(){
        base=altura=0.0;
    }

    //Parametrizando constructor
    DosDimensiones(double b, double h){
        base= b;
        altura= h;
    }

    //Construir objeto con misma base y altura
    DosDimensiones(double x){
        base=altura=x;
    }

    //Métodos de acceso para base y altura
    double getBase(){return base;}
    double getAltura(){return altura;}
    void setBase(double b){base=b;}
    void setAltura(double h){altura=h;}

    void mostrarDimension(){
        System.out.println("La base y la altura son: "+base+" y "+altura);
    }
}
//Extendiendo de DosDimensiones
//Triangulo.java
class Triangulo extends DosDimensiones{
    private String estilo;

    //Constructor por defecto
    Triangulo(){
        super();
        estilo="ninguno";
    }

    Triangulo(String s, double b, double h){
        super(b,h);  //Llama al constuctor de la superclase
        estilo=s;
    }

    //Constructor con un argumento
    Triangulo(double x){
        super(x); //Llama al constuctor de la superclase
        estilo="Estilo 1";
    }

    double area(){
        return getAltura()*getBase()/2;
    }

    void mostrarEstilo(){
        System.out.println("El triángulo tiene: "+estilo);
    }
}
//Extendiendo de Triangulo
class ColorTriangulo extends Triangulo{
    private String color;

    ColorTriangulo(String c, String s,
                   double b, double h){
        super(c,b,h);
        color=c;
    }

    String getColor(){return color;}
    void mostrarColor(){
        System.out.println("El color es: "+color);
    }
}

class Test{
    public static void main(String[] args) {
        ColorTriangulo t1= new ColorTriangulo("Azul","Estilo x", 8.0,12.0);
        ColorTriangulo t2= new ColorTriangulo("Rojo","Estilo y", 4.0,3.0);

        System.out.println("Información para t1: ");
        t1.mostrarEstilo();
        t1.mostrarDimension();
        t1.mostrarColor();
        System.out.println("El área es: "+t1.area());

        System.out.println();

        System.out.println("Información para t2: ");
        t2.mostrarEstilo();
        t2.mostrarDimension();
        t2.mostrarColor();
        System.out.println("El área es: "+t2.area());
    }
}

Salida:

Información para t1: 
El triángulo tiene: Azul
La base y la altura son: 8.0 y 12.0
El color es: Azul
El área es: 48.0

Información para t2: 
El triángulo tiene: Rojo
La base y la altura son: 4.0 y 3.0
El color es: Rojo
El área es: 6.0

Debido a la herencia, ColorTriangulo puede hacer uso de las clases previamente definidas de Triangulo y DosDimensiones, agregando solo la información adicional que necesita para su propia aplicación específica. Esto es parte del valor de la herencia; permite la reutilización de código.

Este ejemplo ilustra otro punto importante: super() siempre se refiere al constructor en la superclase más cercana. El super() en ColorTriangulo llama al constructor en Triangulo. El super() en Triangulo llama al constructor en DosDimensiones.

En una jerarquía de clases, si un constructor de una superclase requiere parámetros, entonces todas las subclases deben pasar esos parámetros “arriba de la línea”. Esto es cierto tanto si una subclase necesita o no parámetros propios.

2. ¿Qué constructor se ejecuta primero?

En la discusión anterior de jerarquías de herencia y clase, se le puede haber ocurrido una pregunta importante: ¿cuándo se crea un objeto de subclase, cuál constructor se ejecuta primero, el de la subclase o el definido por la superclase?

Por ejemplo, dada una subclase llamada B y una superclase llamada A, ¿el constructor de A se ejecuta antes que B, o viceversa? La respuesta es que en una jerarquía de clases, los constructores completan su ejecución en orden de derivación, de la superclase a la subclase. Además, dado que super() debe ser la primera instrucción ejecutada en el constructor de una subclase, este orden es el mismo independientemente de si se usa super() o no. Si no se usa super(), entonces se ejecutará el constructor predeterminado (sin parámetros) de cada superclase.

El siguiente programa ilustra cuándo se ejecutan los constructores:

//Creando una superclase A
class A{
        A(){
            System.out.println("Constructor de A");
        }
}
//Creando una subclase B que extiende de A
class B extends A{
    B(){
        System.out.println("Constructor de B");
    }
}
//Creando otra subclase C que extiende de B
class C extends B{
    C(){
        System.out.println("Constructor de C");
    }
}

class OrdenConstructorExec{
    public static void main(String[] args) {
        C c=new C();
    }
}

Salida:

Constructor de A
Constructor de B
Constructor de C

Como puede ver, los constructores se ejecutan en orden de derivación.

Si lo piensas, tiene sentido que los constructores se ejecuten en orden de derivación. Debido a que una superclase no tiene conocimiento de ninguna subclase, cualquier inicialización que necesite realizar está separada y posiblemente sea un requisito previo para cualquier inicialización realizada por la subclase. Por lo tanto, debe completar primero su ejecución.

3. Referencias de superclase y objetos de subclase

Como saben, Java es un lenguaje fuertemente tipado. Además de las conversiones estándar y las promociones automáticas que se aplican a sus tipos primitivos, la compatibilidad de tipos se aplica estrictamente. Por lo tanto, una variable de referencia para un tipo de clase normalmente no puede referirse a un objeto de otro tipo de clase. Por ejemplo, considere el siguiente programa:

//Esto no compilará
class X{
    int a;

    X(int i){a=i; }
}

class Y{
    int a;

    Y(int i){a=i; }
}

class Prueba {
    public static void main(String[] args) {
        X x= new X(10);
        X x2;
        Y y= new Y (5);

        x2=x; //OK, ambos son del mismo tipo
        x2=y; //ERROR, no son del mismo tipo
    }
}

Aquí, aunque la clase X y la clase Y son estructuralmente iguales, no es posible asignar una referencia X a un objeto Y porque tienen diferentes tipos. En general, una variable de referencia de objeto puede referirse solo a objetos de su tipo. Sin embargo, existe una excepción importante a la estricta aplicación de tipos de Java.

A una variable de referencia de una superclase se le puede asignar una referencia a un objeto de cualquier subclase derivado de esa superclase. En otras palabras, una referencia de superclase puede referirse a un objeto de subclase. Aquí hay un ejemplo:

public class X {
    int a;
    X(int i){a=i;}
}
class Y  extends X{
    int b;
    Y(int i, int j){
    super(j);
    b=i;
    }
}

class Prueba{
    public static void main(String[] args) {
        X x=new X(10);
        X x2;
        Y y = new Y(5,6);

        x2=x; //OK
        System.out.println("x2.a: "+x2.a);

        x2=y; //Ok porque Y deriva desde X
        System.out.println("x2.a: "+x2.a);

        //X sólo conoce a los miembros de X
        //x2.a=28; //OK
        //x2.b=25; //ERROR
    }
}

Salida:

x2.a: 10
x2.a: 6

Aquí, Y ahora se deriva de X; por lo tanto, está permitido que a x2 se le asigne una referencia a un objeto Y. Es importante entender que es el tipo de variable de referencia, no el tipo de objeto al que se refiere, lo que determina a qué miembros se puede acceder.

Es decir, cuando se asigna una referencia a un objeto de subclase a una variable de referencia de superclase, tendrá acceso únicamente a aquellas partes del objeto definidas por la superclase. Esta es la razón por la cual x2 no puede acceder a b incluso cuando se refiere a un objeto Y. Si lo piensas bien, esto tiene sentido, porque la superclase no tiene conocimiento de lo que una subclase le agrega. Esta es la razón por la cual la última línea de código en el programa está comentada.

4. Ejemplo de referencia a un objeto de subclase

Aunque la discusión anterior puede parecer un poco esotérica, tiene algunas aplicaciones prácticas importantes. Uno se describe a continuación.

Un ejemplo importante donde se asignan las referencias de subclase a las variables de superclase es cuando se llama a los constructores en una jerarquía de clases. Como saben, es común que una clase defina un constructor que toma un objeto de la clase como parámetro. Esto permite que la clase construya una copia de un objeto. Las subclases de dicha clase pueden aprovechar esta característica.

Por ejemplo, considere las siguientes versiones de DosDimensiones y Triangulo. Ambos agregan constructores que toman un objeto como parámetro.

//Herencia Multinivel
class DosDimensiones{
     private double base;
     private double altura;

     //Constructor por defecto
    DosDimensiones(){
        base=altura=0.0;
    }

    //Parametrizando constructor
    DosDimensiones(double b, double h){
        base= b;
        altura= h;
    }

    //Construir objeto con misma base y altura
    DosDimensiones(double x){
        base=altura=x;
    }

    //Construir un objeto desde un objeto
    DosDimensiones(DosDimensiones dd){
        altura=dd.altura;
        base=dd.base;
    }

    //Métodos de acceso para base y altura
    double getBase(){return base;}
    double getAltura(){return altura;}
    void setBase(double b){base=b;}
    void setAltura(double h){altura=h;}

    void mostrarDimension(){
        System.out.println("La base y la altura son: "+base+" y "+altura);
    }
}
//Extendiendo de DosDimensiones
class Triangulo extends DosDimensiones{
    private String estilo;

    //Constructor por defecto
    Triangulo(){
        super();
        estilo="ninguno";
    }

    Triangulo(String s, double b, double h){
        super(b,h);  //Llama al constuctor de la superclase
        estilo=s;
    }

    //Constructor con un argumento
    Triangulo(double x){
        super(x); //Llama al constuctor de la superclase
        estilo="Estilo 1";
    }

    //Construir un objeto desde un objeto
    Triangulo(Triangulo t){
        super(t); //Pasa el objeto al constructor de DosDimensiones
        estilo=t.estilo;
    }

    double area(){
        return getAltura()*getBase()/2;
    }

    void mostrarEstilo(){
        System.out.println("El triángulo tiene: "+estilo);
    }
}

class TestRef{
    public static void main(String[] args) {
        Triangulo t1=new Triangulo("Estilo x", 8.0,12.0);

        //Hacer una copia de t1
        Triangulo t2=new Triangulo(t1);

        System.out.println("Información para T1: ");
        t1.mostrarEstilo();
        t1.mostrarDimension();
        System.out.println("El área es: "+t1.area());

        System.out.println();

        System.out.println("Información para T2: ");
        t2.mostrarEstilo();
        t2.mostrarDimension();
        System.out.println("El área es: "+t2.area());
    }
}

Salida:

Información para T1: 
El triángulo tiene: Estilo x
La base y la altura son: 8.0 y 12.0
El área es: 48.0

Información para T2: 
El triángulo tiene: Estilo x
La base y la altura son: 8.0 y 12.0
El área es: 48.0

Presta especial atención a este constructor de Triangulo:

Triangulo(Triangulo t){
       super(t); //Pasa el objeto al constructor de DosDimensiones
       estilo=t.estilo;
   }

Recibe un objeto de tipo Triangulo y pasa ese objeto (a través de super) al constructor DosDimensiones:

DosDimensiones(DosDimensiones dd){
       altura=dd.altura;
       base=dd.base;
   }

El punto clave es que DosDimensiones() está esperando un objeto DosDimensiones. Sin embargo, Triangulo() le pasa un objeto Triangulo. La razón por la que esto funciona es porque, como se explicó, una referencia de superclase puede referirse a un objeto de subclase. Por lo tanto, es perfectamente aceptable pasar a DosDimensiones() una referencia a un objeto de una clase derivada de DosDimensiones().

Debido a que el constructor DosDimensiones() está inicializando solo aquellas partes del objeto de subclase que son miembros de DosDimensiones(), no importa que el objeto también pueda contener otros miembros agregados por clases derivadas.

Herencia en Java
  • Herencia Multinivel

Sobre el Autor:

Hey hola! Yo soy Alex Walton y tengo el placer de compartir conocimientos hacía ti sobre el tema de Programación en Java, desde cero, Online y Gratis.

Deja una Respuesta

*

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.