Una variable de tipo Eslabon no puede contener una referencia a un objeto de la clase A.
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.Eslabon e= new A(); // error de tipos
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:
A a; a= new B(); // ProyecciónSe 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.
a.Incx(); // Ok a.x; // Ok a.Incz(); // error, Incz no está definido para ADel 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.
A a=new B(); B b=(B)a; b.Incz(); // Ok ( (B)a ).Incz(); // OkNo 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.
Los objetos de la clase B también son instancias de la clase A:Object obj; ... if (obj instanceof A) // obj pertenece a la clase A A a=(A)obj; // Ok, nunca hay error
Object obj= new B(); if (obj instanceof A) // true A a= (A)obj; // Ok
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.
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:B b= new B(1, 2, 3); b.Print(); // 1 2 >8^(
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
¿Qué método se invoca en el siguiente caso?
El tipo estático de la variable a es la clase A en donde Print sólo imprime x e y, por lo que una posible respuesta es que se invoca el método definido en la clase A.A a= new B(1, 2, 3); a.Print(); // ?
Sin embargo, la respuesta correcta es que se invoca el método definido para el tipo dinámico de la variable a. Es decir la clase más específica a la cual pertenece el objeto referenciado por la variable a. Esta clase es B. Por lo tanto se invoca el Print definido para la clase B y la salida será:
1 2 3
Esta forma de enlazar el nombre de un método con el código que
se ejecutará para un objeto determinado se denomina
enlace dinámico, porque el método que finalmente se
invocará en general sólo se conoce durante la ejecución y no
durante la compilación.
Una clase abstracta es una clase que se introduce sólo para que se deriven nuevas clases de ella, no para que se creen objetos con su nombre. Del mismo modo, un método abstracto es un método que se introduce para que sea redefinido en una clase derivada. Por ejemplo
abstract class GraphObj
{
int x, y; // La posición central
GraphObj(int ix, int iy)
{ x= ix; y= iy; } // constructor
void Move(int dx, int dy)
{ x+= dx; y+= dy; }
abstract void Paint(Graphics g);
// Paint es abstracto
}
Esta clase no se puede usar para crear un
objeto, por lo que lo siguiente es un error:
La idea es que sólo se pueden crear objetos de clases derivadas de la clase anterior:GraphObj gf= new GraphObj(10,20); // error
class Line extends GraphObj
{
// x e y se heredan
int ix, iy;
GraphObj(int aix, int aiy,
int afx, int afy)
{
super((aix+afx)/2, (aiy+afy)/2);
ix= aix; iy= aiy;
}
void Paint(Graphics g)
{ g.DrawLine(xi,yi,x+(x-xi),y+(y-yi)); }
// Move se hereda de GraphObj
}
// Ahora sí!
Line line= new Line(0,0, 10,20);
El principio es que se use varias veces la clase abstracta
para definir varias otras clases que poseen un conjunto
común de métodos: Paint y Move.
// Una caja
class Box extends GraphObj
{
int height, width;
Box(int lx, int ly, int hx, int hy)
{
super( ... ); // Ejercicio
...
}
void Paint(Graphics g)
{
... // Ejercicio
}
}
Redefinición parcial de métodos
Supongamos que ahora se desea introducir una caja con color. Este objeto gráfico es similar a un caja sola. Por lo tanto derivamos la caja con color a partir de una caja simple. Conservamos casi todo, pero tenemos que redefinir Paint. Aún así podemos reutilizar el Paint de Box:
class ColorBox extends Box
{
int color;
// El mismo constructor
ColorBox(int lx, int ly,
int hx, int hy,
int acolor)
{
super(lx, hx, ly, hy);
color= acolor;
}
void Paint() // Redefinición
{
int savecolor= g.currColor();
g.setColor(color);
super.Paint(); // el Paint de Box
g.setColor(savecolor);
}
}
Al redefinir un método, se puede invocar
el método de la clase base usando super
con sus respectivos argumentos.
final class B extends A
{
...
}
class C extends B // error B es final
{
...
}
Un método final es un método que no se puede redefinir
en una subclase de la clase en donde se definió. Por ejemplo
el método Move asociado a un objeto gráfico se puede declarar
final en la clase GraphObj mediante:
final void Move(int dx, int dy)
{ x+= dx; y+= dy; }
De esta forma este método no podrá ser redefinido posteriormente
en las clases Line, Box o ColorBox.
Una campo final es una variable a la que no se puede asignar un valor. La variable se inicializa con un valor durante su declaración, pero luego no puede cambiar. Cumple el papel de las constante de otros lenguajes, pero observe que en Java se trata de constantes dinámica cuyo valor se calcula en ejecución.
class A
{
int iv;
static int cv;
void Inc()
{
iv++; cv++;
System.out.println(iv+" "+cv);
}
// Constructor para los objetos
A() { iv=0; }
// Inicializador de la clase
static { cv=0; }
}
A a1= new A();
A a2= new A();
a1.Inc(); // 1 1
a2.Inc(); // 1 2
a1.Inc(); // 2 3
a1.Inc y a2.Inc incrementan las misma variable cv.
Un método static de la clase es un método que sólo accesa variables de la clase. Se definen usando el atributo static:
static void Inc2() { cv++; }
static void Inc3()
{ iv++; } // error iv es de un objeto
static void Inc4()
{
Inc(); // error Inc necesita un obj.
Inc2(); // Ok, porque Inc2 es static
}