Subclases y Herencia

Los métodos y variables que posee un objeto definen la clase a la cual pertenece. Por ejemplo, todos los objetos de la clase A poseen los métodos Set, Incx y Print y las variables x e y. En cambio los objetos de la clase Eslabon poseen el método Encadenar y las variables next y a.

Una variable de tipo Eslabon no puede contener una referencia a un objeto de la clase A.

Eslabon e= new A(); // error de tipos

Puede existir una clase B de objetos que poseen todos los métodos

y todas las variables de A, pero además poseen otros métodos y/o variables que no poseen los objetos de A. En ese caso se dice que B es una subclase de A.

Los objetos de la clase B también pertenecen a la clase A.

El principio es que todo el código que se haya escrito para objetos de la clase A también funcionará con objetos de la clase B.

Una subclase se define mediante:

class B extends A

{

  // variables que B agrega a A

  int z;

  // Métodos que B agrega a A

  // Observe que B también posee x

  void Incz() { z= z+x; }

}

Se dice que la clase B hereda todas las variables y métodos de A. También se dice que B se deriva de A o que A es la clase base para B.

La jerarquía de clases permite apreciar fácilmente qué clases son subclases de otras.

Observe que todos los objetos pertenecen a la clase Object.

Consideraciones importantes al usar subclases:

Una variable de la clase A, también puede contener referencias a objetos de la clase B, porque estos objetos pertenecen a la clase B y a la clase A. Este concepto se denomina proyección.

A a;

a= new B(); // Proyección

Se dice que A es el tipo estático de la variable a y B es el tipo dinámico de a. El tipo estático siempre se determina en tiempo de compilación mientras que el tipo dinámico en general sólo se puede conocer en tiempo de ejecución y puede cambiar.

Dada una variable a, Java sólo permite invocar los métodos y accesar las variables conocidas para el tipo estático de a.

a.Incx(); // Ok

a.x; // Ok

a.Incz(); // error, Incz no está definido para A

Del mismo modo, Java sólo permite asignar una expresión a una variable de tipo A si el tipo de la expresión es A o una subclase de A:

B b= new A(); // error, el objeto no pertence a

              // la clase B

A a= new B(); // Ok

B b= a; // error, la clase estática de a

              // no es una subclase de B.

Un objeto se puede convertir a una referencia de la clase B mediante un cast:

A a=new B();

B b=(B)a;

b.Incz(); // Ok

( (B)a ).Incz(); // Ok

No todo objeto se puede convertir a la clase B.

A a= new A();

B b=(B)a; // Ok, en compilación, pero

               // error en tiempo de ejecución

Java chequea durante la ejecución todas las conversiones explícitas (casts). Si el objeto no pertence a la clase a la cual se pretende convertir, entonces se produce una excepción.


El operador instanceof

Se puede consultar si un objeto pertenece a una clase mediante:

Object obj;

if (obj instanceof A)

  // obj pertenece a la clase A

  A a=(A)obj; // Ok, nunca hay error

Los objetos de la clase B también son instancias de la clase A:

Object obj= new B();

if (obj instanceof A) // true

  A a= (A)obj;        // Ok

El constructor en una subclase

Los constructores no se heredan:

class A

{

  …

  A(int ix, int iy){ … };

}

class B extends A

{

}

B b= new B(1,2); // error, ningún

                 // constructor calza

El constructor de la clase base se puede invocar con super:

class B extends A

{

  …

  B(int ix, int iy)

  {

    super(ix, iy);

    z= 0;

  }

  B(int ix, int iy, int iz)

  {

    super(ix, iy);

    z= iz;

  }

  B(B b)

  {

    z= b.z; // x=y=?

    super(b.x, b.y); // error, super debe ser

  }                  // la primera instrucción

}

La invocación del constructor de A siempre debe ser la primera instrucción del constructor de B. El principio es que en B las componentes de la clase base (A) deben inicializarse antes que las componentes que se agregan en la clase B.


Redefinición de Métodos

Un problema que tiene la clase B que heredó de A es que el método Print sólo imprime los campos x e y:

B b= new B(1, 2, 3);

b.Print(); // 1 2 >8^(

Al declarar una clase B derivada de A, aparte de agregar campos y métodos, también se pueden redefinir métodos. Por ejemplo, para B se puede redefinir el método Print:

class B extends A

{

  …

  void Print() // Redefinición

  { System.out.println(x+” “+y+” “+z); }

}

B b= new B(1, 2, 3);

b.Print(); // 1 2 3 8^)

El número y tipo de los parámetros del método redefinido debe coincidir exactamente con los del método original.

Observe que el método Print para la clase A no cambia:

A a= new A(1, 2);

a.Print(); // 1 2