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:
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.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; } }
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ónJava 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
El constructor de la clase base se puede invocar con super:class A { ... A(int ix, int iy){ ... }; } class B extends A { ... } B b= new B(1,2); // error, ningún // constructor calza
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.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 }
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^(
El número y tipo de los parámetros del método redefinido debe coincidir exactamente con los del método original.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^)
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á:
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.1 2 3
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
Esta clase no se puede usar para crear un objeto, por lo que lo siguiente es un error: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 }
La idea es que sólo se pueden crear objetos de clases derivadas de la clase anterior:GraphObj gf= new GraphObj(10,20); // error
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.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);
// 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:
Al redefinir un método, se puede invocar el método de la clase base usando super con sus respectivos argumentos.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); } }
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 class B extends A { ... } class C extends B // error B es final { ... }
De esta forma este método no podrá ser redefinido posteriormente en las clases Line, Box o ColorBox.final void Move(int dx, int dy) { x+= dx; y+= dy; }
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.
a1.Inc y a2.Inc incrementan las misma variable cv.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
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 }