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