|
| índice |
En una de las evoluciones sucesivas de la programación, se llegó a la conclusión de que era mejor dividir un programa en subprogramas, con el fin de poder gestionar mejor su complejidad. Fue la llamada programación estructurada, que en realidad es un método de diseño.
Dicho sistema consistía en descomponer el programa en sucesivos subprogramas hasta llegar a niveles de complejidad elementales. Dio grandes satisfacciones hasta que se dieron con sus límites. Uno de éstos es la imposibilidad de reutilización del código. Otro problema asociado es el tipo de ciclo de vida al que induce, se trata de un ciclo en forma de V:

El inconveniente de este ciclo es su rigidez. En general, se considera que cada fase debe terminarse completamente antes de empezar la siguiente. Así, si se ha cometido un error en alguna fase, éste se propagará y amplificará en el transcurso de las fases siguientes.
Precisamente para resolver estas dificultadas ha aparecido el
diseño orientado a objeto. Mientras en la programación
estructurada se interesa por los procesos, y posteriormente por
los datos, el diseño orientado a objetos se interesa en
primer lugar por los datos, a los que posteriormente se les asociarán
los procesos. ¿ Por qué así ?, porque la experiencia
ha demostrado que lo más estable en la vida de un programa
son los datos, y por este motivo se construye el programa alrededor
de ellos.
En principio, el diseño orientado a objeto puede ser programado
en cualquier lenguaje. Los desarrolllos se organizan alrededor
de los datos, y no de las funcionalidades. Afortunadamente han
aparecido nuevos lenguajes que facilitan la programación
orientada a objetos, como es el caso de Java.
Una clase es un descripción estática de un conjunto de objetos. Una clase es una parte de un còdigo fuente. Un objeto es referenciado mediante la descripción de una clase. Un objeto es dinámico y puede tener una infinidad de objetos descritos por una misma clase. Un objeto es un conjunto de datos presente en memoria.
Así, si definimos una clase, ésta podrá instanciarse tanatas veces como se quiera. Pero todos los objetos creados tendrán la misma descripción y el mismo comportamiento. Es decir, la descripción de un objeto viene dada por sus atributos. Por ejemplo, se define la clase ventana como:
class ventana {
string titulo;
int coordx;
int coordy;
int altura;
int anchura;
}
El nombre de la clase es ventana y titulo, coordx, coordy, altura y anchura son los atributos de esta clase. Cada objeto que se cree con esta clase compartirá esta descripción, pero cada uno tendrá sus propio atributos (su titulo, sus coordenadas x e y, su altura y su anchura). Obsérvese que los atributos de un objeto pueden ser tipos simples (enteros, booleanos....) o bien objetos como aquí la cadena de caracteres del nombre de la ventana.
¿ Cómo crear un objeto ? Mediante la palabra clave new, la cual reserva el espacio de memoria necesario para el objeto, el cual, el espacio, se calcula a partir de la descripción de la clase, y se llama al constructor del objeto. Sólo reseñar, por ahora, que el constructor es un método cuyo nombre es idéntico al de la clase y que efectúa las operaciones que el programador le indica inmediatamente tras la creación del objeto.
Por ejemplo, se define los objetos ven1 y ven2 de la clase ventana
y se llama al constructor:
ventana ven1 = new ventana ("Word",20,20,100,100);
ventana ven2 = new ventana ("Excel",22,05,19,64);
Ambos objetos se encuentran en memoria. Aquí, los parámetros pasados al constructor sirven para dar un valor a los atributos. La sintaxis de este constructor será la siguiente:
ventana (string nombre, int x, int y, int b, int h, int l) {
titulo = nombre;
coordx = x;
coordy = y;
altura= h;
anchura = l;
}
Más adelante se detallarán más cosaa sobre
el constructor.
Por lo tanto, las clases describen los atributos de los objetos, y proporcionan también los métodos. Un método es una función que se ejecuta sobre un objeto. Salvo un excepción que ya se verá, no se puede ejecutar un método sin precisar el objeto sobre el que se aplica. Los atributos del objeto son implícitamente parámetros del método. Al ejecutarse un método lo hace como si estuviese dentrodel objeto, así si se quisiera duplicar el tamaño de la ventana (la clase antes creada) se haría de la siguiente forma:
// Fuente en Java
class ventana {
// atributos, constructor, etc
// . . .
void Duplicarmedidas () {
altura = altura * 2;
anchura = anchura * 2;
}
// resto de la clase ventana
}
// En otro lugar del c�digo fuente la llamada a ese m�todo:
Unaventana.Duplicarmedidas ();
// Donde Unaventana debe ser un objeto de esa clase, por supuesto.
Los métodos suponen una notación diferente que:
Un método definido en una clase también puede hacer referencia a otro mètodo dentro de la misma clase, como ocurre en este caso:
// Fuente Java
class ventana {
Etiqueta elTitulo;
Boton unBoton;
int altura;
int anchura;
// otros posibles atributos....
void Duplicarmedidas () {
altura = altura * 2;
anchura = anchura * 2;
Redibujar ();
}
void Redibujar () {
eltitulo.Redibujar ();
unboton.Redibujar ();
}
// . . .
}
Esta clase ventana tiene un botón y una etiqueta que muestra
un título. Se llama al método Duplicarmedidas sobre
la ventana. Este modifica los atributos altura y anchura y seguidamente
llama a Redibujar, otro método. Aquí se llama aal
método sin objeto predefinido, así que se aplica
al objeto actual. Una vez en este método, se llama a Redibujar
( ) sobre un boton de la ventana. Aunque éste tenga el mismo
nombre, no es el mismo método, porque aquí se trata
de un método de la clase Boton, llamado para un objeto
Boton. Lo mismo ocurre con elTitulo. Se trata de otro método
Redibujar ( ), aplicado a un objeto Etiqueta.
Para cada clase, pueden definirse uno o más métodos particulares: son los constructores.
El constructor se llama en el momento de la creación de un objeto.La utilización de new( ) implica la creación física del objeto y la llamada a uno de sus constructores. Si hubiera más de un constructor, éstos se diferen por los parámetros que se pasan en el new( ). Véase el ejemplo siguiente:
import java.io.*;
// una clase que sirve de main
public class democonstructor {
public static void main (string arg [] ) {
Ejemplo e;
e = new Ejemplo (2);
e = new Ejemplo ("2");
};
}
// una clase que tiene dos constructores diferentes
class Ejemplo {
public Ejemplo (int param) {
System.out.println ("ha llamado al constructor con un entero");
}
public Ejemplo (satring param) {
System.out.println ("ha llamado al constructor con una string");
};
};
Los constructores no tienen tipo de retorno. Si por error definimos un constructor que tenga un tipo de retorno, el compilador lo toma como un método normal. En tal caso se tendrá la impresión de que el constructor no es llamado en el momento de la creación del objeto. En realidad, lo que sucede es que se llamará a un constructor predeterminado, al no haberse definido ninguno.
En Java, no se liberan explícitamente los objetos creados, es el sistema quien los recupera cuando hace falta. Estos atributos del objeto son liberados por el núcleo de Java.
Es posible inicializar los atributos de un objeto indicando los valores que se les darán en la creación de éste. Estos valores se adjudican tras la creación física del objeto, pero antes de la llamada al constructor.
Véase el siguiente ejemplo:
import java.awt.* ;
class valor {
static public void main ( string [ ] arg ) {
new Reserva (1000);
};
}
class Reserva {
int capacidad = 2; // Valor predeterminado
public Reserva ( int laCapacidad) {
System.out .println (capacidad);
capacidad = laCapacidad;
System.out.println (capacidad);
};
}
Esto dará como resultado la visualización del valor que el núcleo le ha dado al atributo capacidad en la inicialización (2) y posteriormente el valor que le asigna el constructor (1000).
Si no se inicializan los atributos explícitamente, toman un valor predeterminado, cero.
Como se dijo antes, si no se define un constructor, el núcleo define uno predeterminado.
class pobre {
int atributo;
void CualquierMetodo();
}
Hacer un new pobre( ) sería correcto. Pero éste
deja de existir si se define uno., y por lo tanto no se le puede
llamar.
Como ya se ha dicho, cada objeto posee sus propios atributos y es posible que todos los objetos de una misma clase tengan atributos en común: son los atributos de la clase, introducidos por la palabra clave static. Estos atributos son legibles y modificables por todos los objetos de una misma clase. La modificación de un atributo static es tenida en cuenta inmediatamente por los demás objetos, porque lo comparten.
Ejemplo:
Import Java.awt.* ;
class demostatic {
static public void main { string [ arg }{
participante p1=new participante ( );
participante p2=new participante ( );
p1.modifica ( );
System.aut.println ("p1: " + p1.participado + "
" + p1.noparticipado);
System.aut.println ("p2: " + p2.participado + "
" + p2.noparticipado);
};
}
class participante {
static int participado=2;
int noparticipado=2;
void modifica ( ) {
participado=3;
noparticipado=4;
};
}
Esto dará como resultado:
p1: 3 3 p2: 3 2
La llamada a modifica ( ) ha modificado el atributo participado. Este resultado se extiende a todos los objetos de la misma clase, mientras que sólo ha modificado el atributo noparticipado del objeto actual.
Si un método trabaja únicamente sobre atributos estáticos, puede declararse también static. Esto significa que no modifica atributos de instancias, sino solamente atributos de clase, bien directamente (en su cuerpo) o indirectamente (en sus llamadas a otros métodos). Así, si volvemos a nuestra clase participante:
class participante {
static int participado;
int noparticipado;
...
el método:
void modifica ( ) {
participado + +;
}
puede ir prefijado por static, mientras que:
void modifica2 ( ) {
noparticipado + +;
}
así como
void modifica3 ( ) {
modifica2 ( );
};
no pueden aspirar a ese título, porque ambos modifican un atributo no static, directa o indirectamente.
El interés de prefijar los métodos con static es
doble:
Recuérdese que para llamar a un método, hay que referirlo a un objeto:
unobjeto.metodo ( ); y que metodo ( ); escrito sin objeto delante significa en realidad que designa el objeto actual en Java.
Un método static puede ser llamado poniendo delante el
nombre de la clase.
Se trata de proteger los datos en una clase. La encapsulación se basa en la noción de servicios prestados. Una clase proporciona un cierto número de servicios y los usuarios de esta clase no tienen que conocer la forma como se prestan estos servicios.
Hay que distinguir en la descripción de la clase dos partes:
Es recomendable poner los atributos de una clase en la parte privada.
En el ejemplo de la clase ventana, los métodos que modifican
el tamaño de la ventada no deben manipular directamente
los atributos del objeto. Si estos fuesen públicos:
Import Java.awt.* ;
class ventana {
public int x1, y1; // coordenadas arriba izquierda
public int h, anchura; // altura, anchura
}
Entonces se pueden modificar directamente estos atributos:
class pantalla {
void desplaza10endiagonal (ventana unaV) {
unaV.x1 += 10;
unaV.y1 + = 10;
}
}
Esto resulta muy práctico pero muy imprudente, si más adelante se deside añadir los atributos siguientes a la clase ventana:
int x2,y2; // coordenadas de la esquina inferior derechapor ejemplo, para no tener que recalcularla cada vez. El método Desplaza10EnDiagonal debe modificarse para tener en cuenta estos nuevos atributos:
class Pantalla {
void Desplaza10EnDiagonal (Ventana unaV) {
unaV.x1 += 10;
unaV.y1 + = 10;
unaV.x2 + = 10;
unaV.y2 + = 10;
}
}
Es demasiado complicado. Una modifición de la clase ventana que no ofrece
servicios suplementarios entraña entonces una modificación
de las otras clases que utiñlizan ventana. El mantenimiento
de este programa se complica. En realidad, se han cometido dos
errores sucesivos en el ejemplo anterior:
void Desplaza10EnDiagonal (ventana unaV) {
x1 + = 10;
y1 + = 10;
x2 + = 10;
y2 + = 10;
}
Se constata además que la escritura resulta más simple. Especialmente, al escribirlo así, se acerca el método a los datos que manipula. Finalmente, añadir los atributos x2 e y2 modidifica entonces un solo método de clase ventana, en lugar de modificar numerosos métodos de los usuarios de ventana.
Java proporciona varios niveles de encapsulado. La granularidad de la protección es el método o el atributo. Así, tal atributo podrá ser protegido, mientras que tal otro no lo será.
Se tiene una clase Madre alrededor de la cual gravitan otras
clases:
En los párrafos siguientes se examinarán sucesivamente los diferentes tipos de protección que Java ofrece a los atributos y a los métodos. Obsérvese ya desde ahora que hay cinco tipos de protección posibles y que, en todos los casos, la protección va sintácticamente al principio de definición, como en los dos ejemplos siguientes, donde private y public definen los niveles de protección:
private void Metodo ( ); public int Atributo;
La protección más fuerte que puede dar a los atributos o a un método es la protección private.
Esta protección impide a los objetos de otras clases acceder a los atributos o a los métodos de la clase considerada. En el dibujo siguiente, un muro rodea la clase Madre e impide a las otras clases acceder a aquellos de sus atributos o métodos declarados como private.
Se insiste en el hecho de que la protección no se aplica a la clase globalmente, sino a algunos de sus atributos o métodos, según la sintaxis siguiente:
private void MetodoMuyProtegido ( ) {
// ...
}
private int AtributoMuyProtegido;
public int OtraCosa;
Observe que un objeto que haya surgido de la misma clase puede acceder a los atributos privados, como en el ejemplo siguiente:
class Secreta {
private int s;
void Int (Secreta otra) {
s = otra.s;
};
}
La protección se aplica pues a las relaciones entre clases y no a las relaciones entre objetos.
Se abordará aquí un tipo de protección menos restrictivo. Una clase permite a sus subclases acceder a sus atributos o a sus métodos private protected. Esto es cierto tanto si la subclase pertenece al mismo paquete como si no.
Sin embargo, las subclases sólo pueden modificar estos atributos para los objetos subclase y no para el tipo Madre. Así, si atr es un atributo de Madre, HijaQuerida puede modificar un atr de una HijaQuerida pero no de una Madre. HijaQuerida no puede modificar tampoco atr de una de sus "hermanas". Si OtraHijaQuerida hereda de Madre y se encuentran en el mismo paquete, HijaQuerida no puede modificar atr en un objeto de la clase OtraHijaQuerida.
El tipo de protección siguiente viene definido por la palabra clave protected simplemente. Permite restringir el acceso a las subclases y a las clases del mismo paquete.
El tipo de protección predeterminado se llama friendly. Ello significa que si no indica ningún nivel de protección para un atributo o un método, el compilador considera que lo ha declarado friendly. No tiene por qué indicar esta protección explícitamente. Además, friendly no es una palabra clave reconocida. Esta protección autoriza el acceso a las clases del mismo paquete, pero no forzosamente a las subclases.
En este tipo de protección es public. Un atributo o un método calificado así es accesible para todo el mundo.
¿Un caso excepcional? No. Muchas clases son universales
y proporcionan servicios al exterior de su paquete: las librerías
matemáticas, gráficas, de sistema, etc., están
destinadas a ser utilizadas por cualquier clase. Por el contrario,
una parte de estas clases está protegida, a fin de garantizar
la integridad del objeto.
¿Cuándo debe utilizarse qué? O en otras palabras:
¿cuáles son los diferentes casos de utilización
de los mecanismos de protección? Se puede distinguir principalmente
dos casos:
¿Pero qué se quiere indicar por interfaz y cuerpo? En C++, por ejemplo, se sabe que hay la posibilidad de declarar la clase en un fichero .hpp y definirla en un fichero .cpp.
Se tiene pues dos definiciones de la interfaz en C++:
Los desarrolladores de C++ utilizan la misma palabra para designar estas dos nociones, lo que en ocasiones introduce confusiones.
Ahora bien, en Java:
La interfaz de la que se habla es pues la interfaz conceptual de la clase, es decir los atributos y las asignaturas de los métodos (tipo devuelto + nombre + parámetros), directamente utilizados desde el exterior porque corresponden a un servicio prestado.
El cuerpo de la clase es la implementación de dicho servicio.
Es decir, la interfaz de la clase es el qué -qué hace la clase-, mientras que su cuerpo es el cómo -cómo lo hace.
Por esto conviene hacer pública la interfaz y proteger
el cuerpo.
Los defensores de C++ se alegrarán de ver que C++ es superior
a Java, porque separa la interfaz del cuerpo. Se equivocan, porque
C++ define una interfaz física de las clases en un fichero
aparte, interfaz destinada únicamente a la compilación.
La noción de interfaz en C++ es pues una noción
bastarda pero menos eficaz.
Los apartados anteriores sobre private, protected y public han descrito los cinco tipos de protección posibles, pero sin dar absolutamente todos los casos, una clase madre puede hacerlo sobre un objeto de la clase madre, o de una clase hija. Los mecanismos de protección distinguen ambos casos.
Además, para ser exhaustivos, se presentará seguidamente
un ejemplo que retoma las clases siguientes:
Las tres últimas clases intentan acceder a los atributos de Madre o utilizar sus métodos. No se ha utilizado Extranjera, sin vínculos de familia con Madre, y que está en otro paquete, porque sólo los atributos y los métodos public de Madre le son accesibles.
La clase Madre tiene cuatro atributos y cuatro métodos, correspondientes a los diferentes niveles de protección. No se ha utilizado public, para no cargar aún más estos ejemplos. He aquí la clase Madre:
package Paquete A;
// significa que las clases pertenencen
// al PaqueteA
class Madre {
private int atrPrivate = 0;
private protected int atrPrivateProtected = 0;
protected int atrProtected = 0;
int atrFriendly = 0;
private void metPrivate ( ) { ;} ;
private protected void metPrivateProtected ( ) { ;} ;
protected void metProtected ( ) { ;} ;
};
He aquí la clase HijaQuerida. Las líneas en comentario son líneas incorrectas. Obsérvese que los atributos y métodos private protected de Madre no pueden utilizarse más que si se refieren a un objeto de la clase HijaQuerida; por el contrario, está prohibido acceder a ellos si se refieren a un objeto de la clase Madre.
class HijaQuerida extends Madre {
void AccessAMadre (Madre m) {
// m.atrPrivate = 0; // prohibido
// m.atrPrivateProtected =0; // prohibido
m.atrProtected = 0;
m.atrFriendly =0;
// m.metPrivate ( ); // prohibido
// m.metPrivateProtected ( ); // prohibido
m.metProtected ( );
m.metFriendly ( );
};
void AccessAHijaQuerida ( ) {
// atrPrivate = 0; // prohibido
atrPrivateProtected 0;
atrProtected = 0;
atrFriendly = 0;
// metPrivate ( ) ; // prohibido
metPrivateProtected ( );
metProtected ( );
metFriendly ( );
};
};
La clase HijaIndigna tiene menos derechos:
import PaqueteA. Madre;
class HijaIndigna extends Madre {
// extends significa hereda de
void AccessAMadre (Madre m) {
// m.atrPrivate = 0; // prohibido
// m.atrPrivateProtected = 0; // prohibido
// m.atrProtected = 0; // prohibido
// m.atrFriendly = 0; // prohibido
// m.metPrivate ( ); // prohibido
// m.metPrivateProtected ( ); // prohibido
// m.met Protected ( ); // prohibido
// m.metProtected ( ); // prohibido
// m.metFriendly ( ); // prohibido
};
void AccessAHijaIndigna ( ) {
// atrPrivate = 0; // prohibido
atrPrivateProtected = 0;
artProtected = 0;
// atrFriendly = 0; // prohibido
// metPrivate ( ) ; // prohibido
metPrivateProtected ( );
metProtected ( );
// metFriendly ( ); // prohibido
};
}
Finalmente, para terminar, la clase Colega situada en el mismo paquete que la clase Madre:
package PaqueteA;
class Colega {
void AccessAMadre (Madre m) {
// m.atrPrivate = 0;
// m.atrPrivateProtected = 0;
m.atrProtected = 0;
m.atrFriendly = 0;
// m.metPrivate ( );
// m.metPrivateProtected ( );
m.metProtected ( );
m.metFriendly ( );
};
};
La herencia es una de las nociones importantes del diseño y de la programación orientada a objetos. Es uno de los factores que permiten la reutilización del código.
La herencia es una relación entre clases definida por la palabra clave extends. Si se dice que una clase Hija, hereda de una clase Madre, quiere decir que éste asimila los atributos y métodos del Madre y que un objeto de la clase Hija es también de la clase Madre.
Esto último, no es recíproco, es decir, un objeto de la clase Madre no lo es de la clase Hija.
La herencia, desde este punto de vista supone:
La clase que hereda, llamada también subclase, retoma
los atributos y los métodos de la superclase. Pero puede:
class Madre{
int entero;
void metodo () { ;};
}
class Hija extends Madre { // extends por heredar
void metododeHija () {
// . . .
};
}
La herencia que recibe Hija es igual a:
class Madre {
int entero;
void metodo () {;};
}
class Hija {
int entero;
void metodo () {;};
}
La herencia, además, permite capturar nuevas formas de
abstracción, es decir, gracias a ésta, se puede
expresar formalmente ideas que provienen del nivel de diseño.
Esto permite:
Cuando se hace heredar una clase de otra, se pueden redefinir ciertos métodos con la intención de modificarlos o mejorarlos. El método lleva el mismo nombre y la misma sintaxis, pero sólo se aplica a los objetos de la subclase o a sus descendientes.
Véase el siguiente ejemplo:
import java.io.*;
public class redefinicion {
public static void main (string arg [ ) {
Madre m new Madre ();
m.habla ();
Hija h = new Hija ();
h.habla ();
nieta n = new Nieta ();
n.habla ();
};
}
class Madre {
void habla () {
System.out.println ("soy de la clase Madre");
};
};
class Hija extends Madre {
void habla () {
System.out.println ("soy de la clase Hija");
};
};
class Nieta extends Hija {
void nuevometodo () {
System.out.println ("no es llamado"),
};
};
Esto dará como resultado:
soy de la clase Madre
soy de la clase Hija
soy de la clase Hija
Los objetos de la clase Madre utilizan el métodode la
clase Madre, los de la clase Hija, el método redefinido
en la clase Hija y los de la clase Nieta utilizan el método
de la clase Hija, porque este método no ha sido redefinido
en la clase Nieta.
Como se ha dicho, un objeto de la clase Hija es también un objeto de la clase Madre. Se puede aprovechar esta dualidad gracias a lo que se denomina polimorfismo.
El polimorfismo es un aprovechamiento de la herencia imposible de realizar en un lenguaje que no sea orientado a objetos. Consiste en llamar a un método según el tipo estático de una variable, basándose en el núcleo para llamar a la versión correcta del método.
Conservando la definición de las clases anteriores, Madre, Hija y Nieta, y modificando la llamada a los métodos:
public static void main ( string arg [ ) {
Madre m;
m = new Madre () ;
m.habla ();
m = new Hija ();
m.habla ();
m = new Nieta ();
m.habla ();
};
Aquí se utiliza una sola variable, m, a la que se asignan sucesivamente objetos de las clases Madre, Hija y Nieta, mientras que m es siempre una variable del tipo Madre. Esto es correcto, porque como se ha dicho, un Hija es también un Madre, es decir, la herencia de la que se habló. Recuérdese que no es recíproco.
Si se hiciera
un Hija f = new Madre ( );sería rechazado por el compilador.
Si se ejecutase el ejemplo anterior, se obtendría el siguiente resultado:
soy de la clase Madre soy de la clase Hija soy de la clase Nieta
Aunque la variable, m, sea siempre de tipo Madre, los objetos designados con esta variable son sucesivamente del tipo Madre, Hija y Nieta, y se ejecutan los métodos de susu clases.
Se puede pensar que ésto es una compilación innecesaria, pero no es así, se trata de una funcionalidad potente de los lenguajes orientados a objetos, la cual permite suprimir una buena parte de los case y otros switch.
Hasta ahora se ha hablado de ventana Madre y ventana Hija para designar una ventana que contiene a otra. Esto no significa que la ventana Hija hereda de la ventana Madre. Una ventana no puede heredar de otra porque son objetos. Son las clases quienes heredan unas de otras.
En el ejemplo visto, la ventana Hija puede ser de la misma clase
que la ventana Madre, o bien de otra clase distinta. Por el contrario,
esa ventana Hija será sin duda un atributo de la ventana
Madre.
Se ha visto que una clase que hereda de otra podía redefinir ciertos métodos. También se vio que se podía dar el mismo nombre a dos métodos diferentes, a poco que tengan parámetros diferentes.
La sobrecarga permite distinguir dos métodos de la misma clase que pueden ser llamados uno u otro sobre el mismo objeto, pero que poseen parámetros distintos. La redefinición distingue dos métodos de dos clases de las cuales una es ancestro de la otra y que tiene ambos los mismos parámetros.
class Madre {
void metodo ( ) {
};
void metodo (int param) { // el m�todo est� sobrecargado
}
}
class Hija extends Madre{
void metodo ( ) { // redefinici�n de metodo () de Madre
}
}
Esta posibilidad de redefinición es peligrosa. Si diseñamos una clase algo sofisticada que asegura servicios potentes, todo esto puede desmoronarse si cualquiera puede heredar de ella y redefinir cualquier método.
Java ha previsto un mecanismo para impredir la redefinición de métodos. Es el empleo de la palabra clave final, que indica al compilador que está prohibido redefinir un método.
El programa siguiente:
class Madre {
final void metodo () { ;} ;
}
class Hija extends Madre {
void metodo () {
};
}
es rechazado por el compilador.
Los atributos también pueden ser final, lo que permite
de hecho definir constantes. Si un atributo es final:
Si un atributo es final static, es constante y accesible simplemente
dando el nombre de la clase. Se comporta, pues, como una verdadera
constante.
Si se tiene una clase Madre y otra Hija, cuando se construye un Hija, se llama al constructor de Hija. Se sabe, que un Hija es también e implícitamente un Madre. Es necesario, pues, que el constructor de Madre sea también invocado. Esta invocación es implícita o explícita según la presencia o no de constructores explícitos.
Puede ser implícita si el constructor de Madre no toma parámetros. En este caso, sea el constructor de Hija implícito o no, la llamada al constructor de Madre es automática.
En el caso de que el constructor de Madre esperase parámetros, hay que pasárselos. Es necesario definir un constructor de Hija y, en este constructor, utilizar la palabra clave super para indicar la llamada al constructor de Madre. Esta palabra clave se sitúa en la primera línea del constructor de Hija.
Ejemplo:
import java.io.*;
class herecons {
public static void main (string arg [ ] ) {
new Hija (2);
}
};
class Madre {
int m;
public Madre ( int mm ) {
System.out.println ('madre');
};
}
class Hija extends Madre {
int f;
public Hija (int ff) {
super (ff);
System.out.println("Hija");
};
}
Esto visualizará sucesivamente Madre y después
Hija. Si por casualidad se quiere cambiar a otro sitio el constructor
de Hija, el compilador indicará su oposición a éste.
Un método abstracto es un método del que sólo se da la declaración, pero sin describir su implementación.
void metnormal ( int numero){
// cuerpo del m�todo
}
abstract void metabstracto { } ; // sin cuerpo
void metvacio ( ) { ; }
// m�todo no abstracto: hay un cuerpo, aunque no
// haga nada.
void otrometodo ( ) {
// cuerpo de otrometodo ( ) ;
}
Una clase de la que uno de los métodos sea abstracto es a su vez llamada abstracta. Debe declararse como tal en el programa:
abstract class abstracta {
abstract void metabstracto ( ) ;
}
Si se define una clase como abstracta cuando ninguno de sus métodos lo es, el compilador no lo considera como un error. Pero la clase es entonces efectivamente considerada como abstracta.
Si se olvida de añadir abstract delante de la declaración de una clase que posee métodos abstractos, el compilador considera ésto como un error.
En definitiva, ¿ para qué sirven las clases abstractas ?, pra definir conceptos incompletos que deben ser completados en las subclases de la clase abstracta.
Así,
abstract class vehiculo {
int numpasajeros;
int peso;
abstract void arrancar ( );
abstract void correr ( );
void transpasajeros (int num) {
numpasajeros = num;
arrancar ( );
correr ( );
}
}
Se tienen los conocimientos comunes a sus subclases autobus y coche. En ambos casos hay pasajeros a embarcar antes de arrancar ( ) y de correr ( ). Por el contrario, arrancar ( ) y correr ( ) son diferentes en ambas subclases.
Si se hiciera una referencia a una clase abstracta, daría un error porque esta clase no está completamente definida.
Si las subclases autobus y coche no dieran un cuerpo a los métodos abstractos, éstas deben declararse también como abstractas y no se pueden utilizar directamente.
Si no se quiere hacer usando clases abstractas, hay dos formas
posibles para ello. La primera de éstas sería dar
a vehiculo un cuerpo para cada uno de sus métodos, incluyendo
los que no deben llamarse nunca porque deben ser redefinidos en
las subclases. Debe tenerse en cuenta que ha de protegerse las
clases de una inesperada llamada. a sus métodos. La otra
solución sería no definir vehiculo y copiar y pegar
entre las clases autobus y coche. Esto sería muy malo para
el mantenimiento del programa. Además, no se podría
utilizar el polimorfismo, que es posible con las clases abstractas.
Una variable que es estáticamente de un tipo abstracto
puede pasarse como parámetro o ser el atributo de una clase.
El compilador sabe que en la ejecución es en realidad un
objeto de una subclase que será el parámetro o atributo,
pero que este objeto respetará las especificaciones de
la clase abstracta.
Una interfaz es una clase:
No es necesario añadir las palabras clave abstract y final en la declaración de una interface, porque la palabra clave interface al principio de la declaración de la clase basta:
interface vehiculo{
/* final */ int numruedas = 2;
/* abstract */ void metodo ();
}
Una interfaz es en realidad una especificación formal de la clase. Se define lo que las subclases deben ofrecer como servicios, dejándoles la libertad de implementación.
Cuando se hereda de una interface, se implementa, lo que se traduce por la sintaxis siguiente:
class Hija implements Madreinterface {
Observe que una interfaz puede heredar (extends) en otra interfaz.
En Java no existe la herencia múltiple. El motivo es que definir una herencia múltiple presenta numerosos problemas. Si se tiene la siguiente herencia:

se presentan los siguientes problemas:
Sin embargo, la herencia múltiple es útil en un caso clásico: cuando queremos heredar de una clase que define el qué y de un aclase que defne el cómo. En este caso, Java autoriza a una clase a heredar de una superclase (cómo) y a implementar una interfaz (qué).
Así se evitan los problemas de las herencias múltiples,
porque toda herencia en forma de diamante, como la antes definida,
es imposible.
Los paquetes se hacen corresponder con un conjunto de clases
que trabajan conjuntamente sobre el mismo ámbito. Es una
facilidad ofrecida por Java para:
Las clases que pertenecen, por ejemplo, al paquete java.awt (que
son las gráficas):
Como se ve, es fácil crear paquetes. Estos, los paquetes,
permiten utilizar los modos de protección protected y friendly.
Cada fichero Java incluye una sola, y solo una, clase public. Esta clase debe llevar el mismo nombre que el fichero, exceptuando el sufijo. Esta es la única clase utilizable en las otras fuentes.
Las clases no public sólo son utilizables en el interior de la fuente en la que son definidas.
Sólo hay una clase public por fichero y la fuente de una clase está obligatoriamente en un solo fichero.
No hay clases private o private protected. La palabra clave public, colocada delante del nombre de una clase, no tiene consecuencias sobre la protección de los atributos de esta clase. Esto no significa que los atributos sean public de modo predeterminado, sino que la clase es visible desde el exterior del fichero.
En la mayoría de los casos se escribe sólo una clase por fichero.