Está en la página 1de 69

Especialización

Java Standard
Edition
Nivel III.

Instituto de Nuevas Tecnologías


UNEWEB
Índice
 Tratamiento y Control de Excepciones. 1
o Definición. 1
o Tipos. 2
o Bloque try. 4
o Bloque catch. 5
o Bloque finally. 8
o Sentencia throw. 10
o Sentencia throws. 12
o Excepciones checked. 14
o Excepciones unchecked. 17
o Malas prácticas de uso. 19
o Recomendaciones de uso. 26
 Clases abstractas. 29
o Concepto. 29
o Implementación. 30
o Características. 31
o Cuándo utilizarlas 32
o Métodos abstractos 34
 Sintaxis. 34
 Tips 35
o Ejemplo número 1. 36
o Ejemplo número 2. 40
 Interfaces. 44
o Definición. 44

I
o Diferencias entre clases abstractas 45
e interfaces.
o Ventajas. 45
o Declaración. 46
o Ejemplo. 47
o Implementación de una interfaz en 49
una clase.
o Jerarquía entre interfaces. 51
o Utilización de una interfaz como un 55
tipo de dato.
o Ejemplo 55
 Variables de clase (static) 60
o definición. 60
o Ejemplo de Uso. 61
 Constantes o variables finales (final) 65
o Definición. 65
o Ejemplo de uso. 65
 Simulación de la Estructura Básica de 66
una Herencia Múltiple con uso de
interfaces.

II
TRATAMIENTO Y CONTROL
El tratamiento de excepciones en Java es un mecanismo del
lenguaje que permite gestionar errores y situaciones excepcionales.
Debido a que el tratamiento de excepciones es uno de los pilares
fundamentales del lenguaje, todo programador sabe
cómo lanzarlas y capturarlas, pero (como cualquier aspecto
fundamental en programación) es necesario conocer con cierta
profundidad el propósito por el cual tenemos disponible dicho
mecanismo, además de hacer un uso correcto de esta funcionalidad.

Definición de excepción

Una excepción en Java (así como en otros muchos lenguajes de


programación) es un error o situación excepcional que se produce
durante la ejecución de un programa. Algunos ejemplos de errores y
situaciones excepcionales son:

 Leer un fichero que no existe


 Acceder al valor N de una colección que contiene menos de N
elementos
 Enviar/recibir información por red mientras se produce una
pérdida de conectividad

Todas las excepciones en Java se representan, como vamos a ver


en la siguiente sección, a través de objetos que heredan, en última
instancia, de la clase java.lang.Throwable.

1
Tipos de excepciones

El lenguaje Java diferencia claramente entre tres tipos de


excepciones: errores, comprobadas (checked) y no comprobadas
(unchecked). El gráfico que se muestra a continuación muestra el árbol
de herencia de algunas las excepciones en Java.

2
La clase principal de la cual heredan todas las excepciones Java
es Throwable. De ella nacen dos ramas: Error y Exception. La primera
representa errores de una magnitud tal que una aplicación nunca
debería intentar realizar nada con ellos (como errores de la JVM,
desbordamientos de buffer, etc) y que por tanto no tienen cabida en este
curso. La segunda rama, encabezada por Exception, representa
aquellos errores que normalmente si solemos gestionar, y a los que
comúnmente solemos llamar excepciones.

De Exception nacen múltiples


ramas: ClassNotFoundException, IOException, ParseException, SQLE
xception y otras muchas, todas ellas de tipo checked. La
única excepción (valga la redundancia) es RuntimeException que es de
tipo unchecked y encabeza todas las de este tipo.

A pesar de que la diferencia entre las excepciones de tipo checked y


unchecked es muy importante, es también a menudo uno de los
aspectos menos entendidos dentro del tratamiento de excepciones.
Veamos cada una de ellas con un poco más de detalle.

3
Bloque try

Es el bloque de código donde se prevé que se genere una excepción.


Es como si dijésemos "intenta estas sentencias y mira a ver si se
produce una excepción". El bloque try tiene que ir seguido, al menos,
por una cláusula catch o una cláusula finally.

La sintaxis general del bloque try consiste en la palabra clave try y


una o más sentencias entre llaves.

try {
// Sentencias Java
}

Puede haber más de una sentencia que genere excepciones, en


cuyo caso habría que proporcionar un bloque try para cada una de ellas.
Algunas sentencias, en especial aquellas que invocan a otros métodos,
pueden lanzar, potencialmente, muchos tipos diferentes de
excepciones, por lo que un bloque try consistente en una sola sentencia
requeriría varios controladores de excepciones.

También se puede dar el caso contrario, en que todas las sentencias,


o varias de ellas, que puedan lanzar excepciones se encuentren en un
único bloque try, con lo que habría que asociar múltiples controladores
a ese bloque. Aquí la experiencia del programador es la que cuenta y
es el propio programador el que debe decidir qué opción tomar en cada
caso.

Los controladores de excepciones deben colocarse inmediatamente


después del bloque try. Si se produce una excepción dentro del

4
bloque try, esa excepción será manejada por el controlador que esté
asociado con el bloque try.

Bloque catch

Es el código que se ejecuta cuando se produce la excepción. Es


como si dijésemos "controlo cualquier excepción que coincida con mi
argumento". No hay código alguno entre un bloque try y un bloque
catch, ni entre bloques catch. La sintaxis general de la
sentencia catch en Java es la siguiente:

catch(UnTipoTrhowable nombreVariable ) {
// sentencias Java
}

El argumento de la sentencia declara el tipo de excepción que el


controlador, el bloque catch, va a manejar.

En este bloque tendremos que asegurarnos de colocar código que


no genere excepciones. Se pueden colocar sentencias catch sucesivas,
cada una controlando una excepción diferente. No debería intentarse
capturar todas las excepciones con una sola cláusula, como esta:

catch( Excepcion e ) { ...

Esto representaría un uso demasiado general, podrían llegar


muchas más excepciones de las esperadas. En este caso es mejor
dejar que la excepción se propague hacia arriba y dar un mensaje de
error al usuario.

5
Se pueden controlar grupos de excepciones, es decir, que se pueden
controlar, a través del argumento, excepciones semejantes. Por
ejemplo:

class Limites extends Exception {}


class demasiadoCalor extends Limites {}
class demasiadoFrio extends Limites {}
class demasiadoRapido extends Limites {}
class demasiadoCansado extends Limites {}
.
.
.
try {
if( temp > 40 )
throw( new demasiadoCalor() );
if( dormir < 8 )
throw( new demasiado Cansado() );
} catch( Limites lim ) {
if( lim instanceof demasiadoCalor ) {
System.out.println( "Capturada excesivo calor!" );
return;
}
if( lim instanceof demasiadoCansado ) {
System.out.println( "Capturada excesivo cansancio!" );
return;
}
}

La cláusula catch comprueba los argumentos en el mismo orden en


que aparezcan en el programa. Si hay alguno que coincida, se ejecuta
el bloque. El operador instanceof se utiliza para identificar exactamente
cuál ha sido la identidad de la excepción.

Cuando se colocan varios controladores de excepción, es decir,


varias sentencias catch, el orden en que aparecen en el programa es
importante, especialmente si alguno de los controladores engloba a

6
otros en el árbol de jerarquía. Se deben colocar primero los
controladores que manejen las excepciones más alejadas en el árbol de
jerarquía, porque de otro modo, estas excepciones podrían no llegar a
tratarse si son recogidas por un controlador más general colocado
anteriormente.

Por lo tanto, los controladores de excepciones que se pueden


escribir en Java son más o menos especializados, dependiendo del tipo
de excepciones que traten. Es decir, se puede escribir un controlador
que maneje cualquier clase que herede de Throwable; si se escribe
para una clase que no tiene subclases, se estará implementando un
controlador especializado, ya que solamente podrá manejar
excepciones de ese tipo; pero, si se escribe un controlador para una
clase nodo, que tiene más subclases, se estará implementando un
controlador más general, ya que podrá manejar excepciones del tipo de
la clase nodo y de sus subclases.

7
Bloque finally

Es el bloque de código que se ejecuta siempre, haya o no excepción.


Hay una cierta controversia entre su utilidad, pero, por ejemplo, podría
servir para hacer un log o un seguimiento de lo que está pasando,
porque como se ejecuta siempre puede dejar grabado si se producen
excepciones y si el programa se ha recuperado de ellas o no.

Este bloque finally puede ser útil cuando no hay ninguna excepción.
Es un trozo de código que se ejecuta independientemente de lo que se
haga en el bloque try.

A la hora de tratar una excepción, se plantea el problema de qué


acciones se van a tomar. En la mayoría de los casos, bastará con
presentar una indicación de error al usuario y un mensaje avisándolo de
que se ha producido un error y que decida si quiere o no continuar con
la ejecución del programa.

Por ejemplo, se podría disponer de un diálogo como el que se


presenta en el código siguiente:

public class DialogoError extends Dialog {


DialogoError( Frame padre ) {
super( padre,true );
setLayout( new BorderLayout() );

// Presentamos un panel con continuar o salir


Panel p = new Panel();
p.add( new Button( "¿Continuar?" ) );
p.add( new Button( "Salir" ) );

add( "Center",new Label(


"Se ha producido un error. ¿Continuar?" ) )

8
add( "South",p );
}

public boolean action( Event evt,Object obj ) {


if( "Salir".equals( obj ) ) {
dispose();
System.exit( 1 );
}
return( false );
}
}

Y la invocación, desde algún lugar en que se suponga que se


generarán errores, podría ser como sigue:

try {
// Código peligroso
}
catch( AlgunaExcepcion e ) {
VentanaError = new DialogoError( this );
VentanaError.show();
}

Lo cierto es que hay autores que indican la inutilidad del


bloque finally, mientras que desde el Java Tutorial de Sun se justifica
plenamente su existencia. El lector deberá revisar todo el material que
esté a su alcance y crearse su propia opinión al respecto.

9
Sentencia throw

La sentencia throw se utiliza para lanzar explícitamente una


excepción. En primer lugar, se debe obtener un descriptor de un
objeto Throwable, bien mediante un parámetro en una cláusula catch o,
se puede crear utilizando el operador new. La forma general de la
sentencia throw es:

throw ObjetoThrowable;

El flujo de la ejecución se detiene inmediatamente después de la


sentencia throw, y nunca se llega a la sentencia siguiente. Se
inspecciona el bloque try que la engloba más cercano, para ver si tiene
la cláusula catch cuyo tipo coincide con el del objeto o
instancia Thorwable. Si se encuentra, el control se transfiere a esa
sentencia. Si no, se inspecciona el siguiente bloque try que la engloba,
y así sucesivamente, hasta que el gestor de excepciones más externo
detiene el programa y saca por pantalla el trazado de lo que hay en la
pila hasta que se alcanzó la sentencia throw. En el programa siguiente,
se demuestra cómo se hace el lanzamiento de una nueva instancia de
una excepción, y también cómo dentro del gestor se vuelve a lanzar la
misma excepción al gestor más externo.

class Ejemplo {
static void demoproc() {
try {
throw new NullPointerException( "demo" );
} catch( NullPointerException e ) {
System.out.println( "Capturada la excepción en demoproc" );
throw e;
}
}

10
public static void main( String args[] ) {
try {
demoproc();
} catch( NullPointerException e ) {
System.out.println( "Capturada de nuevo: " + e );
}
}
}

Este ejemplo dispone de dos oportunidades para tratar el mismo


error. Primero, main() establece un contexto de excepción y después se
llama al método demoproc(), que establece otro contexto de gestión de
excepciones y lanza inmediatamente una nueva instancia de la
excepción. Esta excepción se captura en la línea siguiente. La salida
que se obtiene tras la ejecución de esta aplicación es la que se
reproduce:

Capturada la excepción en demoproc


Capturada de nuevo: java.lang.NullPointerException: demo

11
Sentencia throws

Si un método es capaz de provocar una excepción que no maneja él


mismo, debería especificar este comportamiento, para que todos los
métodos que lo llamen puedan colocar protecciones frente a esa
excepción. La palabra clave throws se utiliza para identificar la lista
posible de excepciones que un método puede lanzar. Para la mayoría
de las subclases de la clase Exception, el compilador Java obliga a
declarar qué tipos podrá lanzar un método. Si el tipo de excepción
es Error o RuntimeException, o cualquiera de sus subclases, no se
aplica esta regla, dado que no se espera que se produzcan como
resultado del funcionamiento normal del programa. Si un método lanza
explícitamente una instancia de Exception o de sus subclases,
excluyendo la excepción de runtime, se debe declarar su tipo con la
sentencia throws. La declaración del método sigue ahora la sintaxis
siguiente:

type NombreMetodo( argumentos ) throws excepciones { }

En el ejemplo siguiente, el programa intenta lanzar una excepción


sin tener código para capturarla, y tampoco utiliza throws para declarar
que se lanza esta excepción. Por tanto, el código no será posible
compilarlo.

class Ejemplo2 {
static void demoproc() {
System.out.println( "Capturada la excepción en demoproc" );
throw new IllegalAccessException( "demo" );
}
}

12
public static void main( String args[] ) {
demoproc();
}

El error de compilación que se produce es lo suficientemente explícito:

Ejemplo2.java:30: Exception java.lang.IllegalAccessException must be


caught, or
it must be declared in the throws clause of this method.
throw new IllegalAccessException( "demo" );
^

Para hacer que este código compile, se convierte en el ejemplo


siguiente, en donde se declara que el método puede lanzar una
excepción de acceso ilegal, con lo que el problema asciende un nivel
más en la jerarquía de llamadas. Ahora main() llama a demoproc(), que
se ha declarado que lanza una IllegalAccessException, por lo tanto
colocamos un bloque try que pueda capturar esa excepción.

class Ejemplo3 {
static void demoproc() throws IllegalAccessException {
System.out.println( "Dentro de demoproc" );
throw new IllegalAccessException( "demo" );
}

public static void main( String args[] ) {


try {
demoproc();
} catch( IllegalAccessException e ) {
System.out.println( "Capturada de nuevo: " + e );
}
}
}

13
Excepciones checked

Una excepción de tipo checked representa un error del cual


técnicamente podemos recuperarnos. Por ejemplo, una operación de
lectura/escritura en disco puede fallar porque el fichero no exista,
porque este se encuentre bloqueado por otra aplicación, etc. Todas
estas situaciones, además de ser inherentes al propósito del código que
las lanza (lectura/escritura en disco) son totalmente ajenas al propio
código, y deben ser (y de hecho son) declaradas y manejadas mediante
excepciones de tipo checked y sus mecanismos de control.

En ciertos momentos, a pesar de la promesa de recuperabilidad,


nuestro código no estará preparado para gestionar la situación de error,
o simplemente no será su responsabilidad. En estos casos lo más
razonable es relanzar la excepción y confiar en que un método superior
en la cadena de llamadas sepa gestionarla.

Por tanto, todas las excepciones de tipo checked deben ser


capturadas o relanzadas. En el primer caso, utilizamos el más que
conocido bloque try-catch:

import java.io.FileWriter;

import java.io.IOException;

public class Main {

public static void main(String[] args) {

FileWriter fichero;

14
try {

// Las siguientes dos líneas pueden lanzar una excepción de


tipo IOException

fichero = new FileWriter("ruta");

fichero.write("Esto se escribirá en el fichero");

} catch (IOException ioex) {

// Aquí capturamos cualquier excepción IOException que se


lance //(incluidas sus subclases)

ioex.printStackTrace();

En caso de querer relanzar la excepción, debemos declarar dicha


intención en la firma del método que contiene las sentencias que lanzan
la excepción, y lo hacemos mediante la cláusula throws:

import java.io.FileWriter;

import java.io.IOException;

public class Main {

15
// En lugar de capturar una posible excepción, la relanzamos

public static void main(String[] args) throws IOException {

FileWriter fichero = new FileWriter("ruta");

fichero.write("Esto se escribirá en el fichero");

Hay que tener presente que cuando se relanza una excepción estamos
forzando al código cliente de nuestro método a capturarla o relanzarla.
Una excepción que sea relanzada una y otra vez hacia arriba terminará
llegando al método primigenio y, en caso de no ser capturada por éste,
producirá la finalización de su hilo de ejecución (thread).

Las dos preguntas que debemos hacernos en este momento es:


¿Cuándo capturar una excepción? ¿Cuándo relanzarla? La respuesta
es muy simple. Capturamos una excepción cuando:

 Podemos recuperarnos del error y continuar con la ejecución


 Queremos registrar el error
 Queremos relanzar el error con un tipo de excepción distinto

En definitiva, cuando tenemos que realizar algún tratamiento del propio


error. Por contra, relanzamos una excepción cuando:

16
 No es competencia nuestra ningún tratamiento de ningún tipo
sobre el error que se ha producido

Excepciones unchecked

Una excepción de tipo unchecked representa un error de


programación. Uno de los ejemplos más típicos es el de intentar leer en
un array de N elementos un elemento que se encuentra en una posición
mayor que N:

int[] numerosPrimos = {1, 3, 5, 7, 9, 11, 13, 17, 19, 23}; // Array de diez
//elementos

int undecimoPrimo = numerosPrimos[10]; // Accedemos al undécimo


elemento mediante //el literal numérico 10

El código anterior accede a una posición inexistente dentro del array,


y su ejecución lanzará la excepción
unchecked ArrayIndexOutOfBoundsException (excepción de índice de
array fuera de límite). Esto es claramente un error de programación, ya
que el código debería haber comprobado el tamaño del array antes de
intentar acceder a una posición concreta:

int[] numerosPrimos = {1, 3, 5, 7, 9, 11, 13, 17, 19, 23};

int indiceUndecimoPrimo = 10;

17
if(indiceUndecimoPrimo > numerosPrimos.length) {

System.out.println("El índice proporcionado (" +


indiceUndecimoPrimo + ") es mayor que el tamaño del array (" +
numerosPrimos.length + ")");

} else {

int undecimoPrimo = numerosPrimos[indiceUndecimoPrimo];

// ...

El código anterior no sólo valida el tamaño de la colección antes de


acceder a una posición concreta (el propósito fundamental del ejemplo),
sino que evita el uso de literales numéricos asignando el índice del array
a una variable bien nombrada.

El aspecto más destacado de las excepciones de tipo unchecked es


que no deben ser forzosamente declaradas ni capturadas (en otras
palabras, no son comprobadas). Por ello no son necesarios bloques try-
catch ni declarar formalmente en la firma del método el lanzamiento de
excepciones de este tipo. Esto, por supuesto, también afecta a métodos
y/o clases más hacia arriba en la cadena invocante.

18
Malas prácticas de uso

Para convertirnos en maestros de las excepciones, debemos evitar


el uso de aquellas malas prácticas que se han generalizado a lo largo
de los años (y por supuesto no inventar las nuestras...). La primera que
vamos a ver es la más peligrosa y, a pesar de ello, también la más
común:

try {

// Código que declara lanzar excepciónes

} catch(Exception ex) {}

El código anterior ignorará cualquier excepción que se lance dentro


del bloque try, o, mejor dicho, capturará toda excepción lanzada dentro
del bloque try pero la silenciará no haciendo nada (frustrando así el
principal propósito de la gestión de excepciones checked: gestiónala o
relánzala). Cualquier error de diseño, de programación o de
funcionamiento en esas lineas de código pasará inadvertido tanto para
el programador como para el usuario. Lo mínimamente aceptable dentro
de un bloque catch es un mensaje de log informando del error:

try {

// Código que declara lanzar excepciónes

} catch(Exception ex) {

19
logging.log("Se ha producido el siguiente error: " +
ex.getMessage());

logging.log("Se continua la ejecución");

Algo más razonable sería pintar una traza completa del error
mediante uno de los métodos informativos de Throwable:

try {

// Código que declara lanzar excepciónes

} catch(Excepcion ex) {

ex.printStackTrace(); // Podemos añadir cualquier tratamiento


adicional antes // y/o después de esta línea

Otro abuso del mecanismo de tratamiento de excepciones es cuando


se está intentando escribir código que mejore el rendimiento de la
aplicación:

try {

int i = 0;

20
while(true) {

System.out.println(numerosPrimos[i++]);

} catch(ArrayIndexOutOfBoundsException aioobex) {}

El ejemplo anterior itera nuestro fabuloso array de números primos


sin preocuparse de los límites del array (tal como haría de manera
formal un bucle for) hasta sobrepasar el índice máximo, momento en el
cual se lanzará una excepción de
tipo ArrayIndexOutOfBoundsException que será capturada y
silenciada. Esto es un error porque:

 El tratamiento de excepciones está diseñado para gestionar


excepciones y no para realizar optimizaciones
 El código dentro de bloques try-catch no dispone de ciertas
optimizaciones de las JVM más modernas (por ejemplo, y
aplicable a nuestro caso, iteración de colecciones)
 El resultado es estéticamente horrible

Otro error común se produce cuando estamos creando nuestra


propia librería de excepciones y nos excedemos declarando
excepciones checked. Las excepciones checked son fabulosas ya que,
al contrario que los códigos return de lenguajes como C, fuerzan al
programador a manejar condiciones excepcionales, mejorando así la

21
legibilidad del código. Sin embargo, esta obligación puede llegar
a cargar el código cliente:

try {

// Código que declara lanzar muchas excepciónes

} catch(UnTipoDeException ex1) {

// Gestionar...

} catch(OtroTipoDeException ex2) {

// Gestionar...

} catch(OtroTipoMasDeException ex3) {

// Gestionar...

} catch(OtroTipoTodaviaMasDeException ex3) {

// Gestionar...

El código anterior suele abrumar, y el cliente acabará tentado por la


siguiente alternativa:

22
try {

// Código que declara lanzar muchas excepciónes

} catch(Exception ex) {

// Gestionar cualquier excepcion, pues todas heredan de Exception

// Perdemos la ventaja de gestionar condiciones excepcionales


concretas

Por ello, debes pensar detenidamente si tu excepción es de tipo


checked o unchecked. Recuerda, cualquier situación excepcional que
deje la aplicación en un estado irrecuperable y/o no sea inherente al
propósito del código que la produce debe ser declarada como una
excepción de tipo unchecked.

La siguiente mala práctica que vamos a ver está íntimamente


relacionada con la anterior, y es la de lanzar excepciones de forma
genérica:

public void miMetodo() throws Exception {

// Código que declara lanzar muchas excepciones.

// Sin embargo, en la firma del método declaramos lanzar una única


super-clase //de todas éllas

23
}

Nunca hagas lo anterior. Y es un absoluto ERROR. Los clientes de


tu método no sabrán jamás con qué condiciones especiales se pueden
encontrar, y por tanto no podrán gestionarlas; no tendrán más remedio
que informar del error y detener la ejecución.

Por último, existe una técnica para convertir toda excepción checked
en unchecked:

public void noLanzoExcepcionesChecked() {

try {

// Código que lanza una o más excepciones de tipo checked

} catch(Exception ex) {

throw new RuntimeException("Se ha producido una excepción


con el mensaje: " + ex.getMessage(), ex);

El método del código anterior convierte cualquier excepción de tipo


checked en una excepción de tipo unchecked, de manera que ningún
cliente suyo esté forzado a declarar/gestionar ninguna de ellas. En los

24
últimos años ha crecido una comunidad de usuarios Java que abogan
por eliminar el sistema de excepciones checked, que es justo lo que
hace el código anterior. Si alguno de estos usuarios lee esto,
probablemente discrepe con la idea de incluir dicho código (o cualquiera
de sus variantes con menos ámbito de alcance y por tanto menos
agresiva) dentro de lo que se consideran malas prácticas. Unos ven
ventajas sobre el tratamiento de excepciones de tipo checked (porque
nos da control sobre los errores que se producen a través de estructuras
basadas en código legible y con un propósito claro), otros ven
desventajas (sobrecarga del lenguaje y en última instancia del código
con una funcionalidad que,no es absolutamente perfecta). Existe
actualmente un debate abierto sobre la verdadera utilidad de las
excepciones checked. Tal vez en próximas versiones de Java (o en el
próximo gran lenguaje orientado a objetos) se elimine cualquier
concepto actual de error comprobado, pero en el momento actual no es
así.

Existen ciertas situaciones en las que la conversión de una o más


excepciones de tipo checked en unchecked es útil o práctica, pero son
situaciones tan concretas y a menudo complejas.

25
Recomendaciones de uso

Un buen uso del tratamiento de excepciones es usar excepciones


que ya existen, en lugar de crear las tuyas propias, siempre que ambas
fueran a cumplir el mismo cometido (que es básicamente informar y, en
caso de las checked, obligar a gestionar). Se suelen usar excepciones
que ya existen cuando se dispone de un profundo conocimiento del API
que se está usando (en otras palabras, experiencia). Si un argumento
pasado a uno de tus métodos no es del tipo esperado, o no tiene el
formato correcto, lanza una excepción IllegarArgumentException en
lugar de crear tu propia excepción. Esto es bueno porque:

 Uno de los pilares de Java es la reutilización de código (no


reinventes la rueda)
 Tu código es más universal (FormatoInvalidoException puede no
significar nada para un germanoparlante)

Otra recomendación que no suele llevarse a cabo nunca o casi


nunca es la de lanzar excepciones acordes al nivel de abstracción en el
que nos encontramos. Imaginemos una serie de clases que actúan
como capas, una encima de otra (cuanto más arriba más abstracta,
cuanto más abajo más concreta). Cuando se produce un error en las
capas más bajas y éste se propaga hacia arriba, llega un momento en
que dicho error representando una condición excepcional muy concreta
se encuentra en un contexto muy abstracto. Esto tiene básicamente tres
problemas: el primero, que puede ser importante, es que estamos
contaminando el API de las capas superiores con suciedad de las
inferiores. El segundo, que es importante, es que estamos desvelando

26
detalles de nuestra implementación muchos niveles por encima de lo
deseable. El último problema, que es importante y puede ser crítico, es
que si en el futuro deseamos intercambiar una de las capas más
concretas y ésta ha cambiado su implementación, todas las capas por
encima se romperán. Por tanto, debemos lanzar excepciones
apropiadas a la abstracción en la que nos encontramos:

try {

// Código que declara lanzar excepciones de bajo nivel

} catch(BajoNivelException bnex) {

throw new AltoNivelException("Mensaje");

Por último, debes documentar adecuadamente las excepciones que


lanza tu código. Para ello, detalla en tus Javadoc todas las excepciones
que lanzan tus métodos, informando que condiciones van a provocar el
lanzamiento de cada una de ellas:

27
/**

* @author David Marco

* @throws MiExcepcion se lanza en caso de producirse [...] condición


especial.

*/

public class MiClase throws MiExcepcion {

// ...

28
CLASES ABSTRACTAS

Concepto

La abstracción permite resaltar lo más representativo de algo sin


importar los detalles.

Básicamente una clase Abstracta es similar a una clase normal


(como la vimos en el curso Anterior), la estructura
es prácticamente igual, ya que poseen nombre, atributos y métodos
pero para que una clase sea abstracta la condición es que al menos uno
de sus métodos sea abstracto (se le agrega la
palabra reservada abstract y no se especifica el cuerpo del método) su
uso depende de la aplicación del concepto de Herencia y
adicionaremos a la estructura básica de clase la palabra reservada
abstract.

29
Implementación

public abstract class Principal{

/**Método concreto con implementación*/


public void metodoConcreto(){
...............
...............
}

/**Método Abstracto sin implementación*/


public abstract void metodoAbstracto();

class subClase extends Principal{

@Override
public void metodoAbstracto() {
/**Implementación definida por la clase concreta*/
}

30
Características de una Clase Abstracta.

Esto es lo que debemos conocer sobre de Clases Abstractas.

 Una clase Abstracta No puede ser instanciada (no se pueden


crear objetos directamente - new ), solo puede ser heredada.
 Si al menos un método de la clase es abstract, esto obliga a que
la clase completa sea definida abstract, sin embargo, la clase puede
tener el resto de métodos no abstractos.
 Los métodos abstract no llevan cuerpo (no llevan los caracteres
{}).
 La primera subclase concreta que herede de una
clase abstract debe implementar todos los métodos de la superclase.

31
¿Cuándo Utilizarlas?

Al trabajar clases y métodos abstractos, no solo mantenemos


nuestra aplicación más organizada y fácil de entender sino que también
al no poder instanciar una clase abstracta nos aseguramos de que las
propiedades específicas de esta, solo estén disponibles para sus clases
hijas....

Con las Clases Abstractas lo que hacemos es definir un proceso


general que luego será implementado por las clases concretas que
hereden dichas funcionalidades, es decir, si tengo una clase que hereda
de otra Abstracta, estoy obligado a poner en el código, todos los
métodos abstractos de la clase padre, pero esta vez serán métodos
concretos y su funcionalidad o cuerpo será definido dependiendo de
para que la necesite, de esa manera si tengo otra clase que también
hereda del mismo padre, implementaré el mismo método pero con un
comportamiento distinto.

Hay ocasiones, cuando se desarrolla una jerarquía de clases en que


algún comportamiento está presente en todas ellas, pero se materializa
de forma distinta para cada una. Por ejemplo, pensemos en una
estructura de clases para manipular figuras geométricas. Podríamos
pensar en tener una clase genérica, que podría llamarse
FiguraGeometrica y una serie de clases que extienden a la anterior que
podrían ser Circulo, Poligono, etc. Podría haber un método dibujar dado
que sobre todas las figuras puede llevarse a cabo esta acción, pero las
operaciones concretas para llevarla a cabo dependen del tipo de figura

32
en concreto (de su clase). Por otra parte, la acción dibujar no tiene
sentido para la clase genérica FiguraGeometrica, porque esta clase
representa una abstracción del conjunto de figuras posibles.

Otro ejemplo sería un esquema de herencia que consta de la clase


Profesor de la que heredan ProfesorInterino y ProfesorTitular. Es
posible que todo profesor haya de ser o bien ProfesorInterino o bien
ProfesorTitular, es decir, que no vayan a existir instancias de la clase
Profesor. Entonces, ¿qué sentido tendría tener una clase Profesor?

El sentido está en que una superclase permite unificar campos y


métodos de las subclases, evitando la repetición de código y unificando
procesos. Ahora bien, una clase de la que no se tiene intención de crear
objetos, sino que únicamente sirve para unificar datos u operaciones de
subclases, puede declararse de forma especial en Java: como clase
abstracta. Sin embargo, sigue funcionando como superclase de forma
similar a como lo haría una superclase “normal”. La diferencia principal
radica en que no se pueden crear objetos de esta clase.

Declarar una clase abstracta es distinto a tener una clase de la que


no se crean objetos. En una clase abstracta, no existe la posibilidad. En
una clase normal, existe la posibilidad de crearlos, aunque no lo
hagamos. El hecho de que no creemos instancias de una clase no es
suficiente para que Java considere que una clase es abstracta. Para
lograr esto hemos de declarar explícitamente la clase como abstracta
mediante la sintaxis que hemos indicado. Si una clase no se declara
usando abstract se cataloga como “clase concreta”. En inglés abstract
significa “resumen”, por eso en algunos textos en castellano a las clases

33
abstractas se les llama resúmenes. Una clase abstracta para Java es
una clase de la que nunca se van a crear instancias: simplemente va a
servir como superclase a otras clases. No se puede usar la palabra
clave new aplicada a clases abstractas, y si intentamos crear objetos en
el código nos saltará un error.

A su vez, las clases abstractas suelen contener métodos abstractos:


la situación es la misma. Para que un método se considere abstracto ha
de incluir en su signatura la palabra clave abstract. Además, un método
abstracto tiene estas peculiaridades ya mencionadas anteriormente.

34
Métodos abstractos

Un método abstracto para Java es un método que nunca va a ser


ejecutado porque no tiene cuerpo. Simplemente, un método abstracto
referencia a otros métodos de las subclases. ¿Qué utilidad tiene un
método abstracto? Podemos ver un método abstracto como una
palanca que fuerza dos cosas: la primera, que no se puedan crear
objetos de una clase. La segunda, que todas las subclases
sobreescriban el método declarado como abstracto.

 Sintaxis tipo: abstract public/private/protected


TipodeRetorno/void ( parámetros … );
 Por ejemplo: abstract public void generarNomina (int
diasCotizados, boolean plusAntiguedad);

Que un método sea abstracto tiene otra implicación adicional: que


podamos invocar el método abstracto sobre una variable de la
superclase que apunta a un objeto de una subclase de modo que el
método que se ejecute sea el correspondiente al tipo dinámico de la
variable. En cierta manera, podríamos verlo como un método
sobreescrito para que Java comprenda que debe buscar
dinámicamente el método adecuado según la subclase a la que apunte
la variable.

35
 Tips

¿Es necesario que una clase que tiene uno o más métodos
abstractos se defina como abstracta? Sí, si declaramos un método
abstracto el compilador nos obliga a declarar la clase como abstracta
porque si no lo hiciéramos así tendríamos un método de una clase
concreta no ejecutable, y eso no es admitido por Java.

¿Una clase se puede declarar como abstracta y no contener


métodos abstractos? Sí, una clase puede ser declarada como
abstracta y no contener métodos abstractos. En algunos casos la clase
abstracta simplemente sirve para efectuar operaciones comunes a
subclases sin necesidad de métodos abstractos. En otros casos sí se
usarán los métodos abstractos para referenciar operaciones en la clase
abstracta al contenido de la sobreescritura en las subclases.

¿Una clase que hereda de una clase abstracta puede ser no


abstracta? Sí, de hecho, esta es una de las razones de ser de las
clases abstractas. Una clase abstracta no puede ser instanciada, pero
pueden crearse subclases concretas sobre la base de una clase
abstracta, y crear instancias de estas subclases. Para ello hay que
heredar de la clase abstracta y anular los métodos abstractos, es decir,
implementarlos.

36
Ejemplo Básico numero 1

Tenemos el Siguiente Diagrama de Clases:

Figura es una clase Abstracta (Nombre en cursiva en UML) porque


no tiene sentido calcular su área, pero si la del cuadrado y la del círculo.
Si una Subclase no Redefine a area(), deberá declararse también como
una clase abstracta.

Definimos la clase abstracta Figura:

37
Definimos la Subclase Circulo que hereda de Figura y observamos
que se implementa el método abstracto area();

Definimos la Subclase Cuadrado que hereda de Figura y


observamos que se implementa el método abstracto area() también:

38
39
Ejemplo Básico numero 2:

En el diagrama vemos una clase Abstracta Instrumento, la cual


posee una propiedad tipo y un método abstracto tocar(), vemos también
las clases hijas Guitarra, Saxofon y Violin que para este ejemplo solo
utilizaremos (mediante la herencia) las propiedades de la clase Padre.

Todos los instrumentos musicales se pueden tocar, por ello creamos


este método abstracto, ya que es un proceso común en todos los
instrumentos sin importar el detalle de cómo se tocan, pues sabemos
que una guitarra no se toca de la misma manera que el saxofón, así al
heredar de la clase Instrumento, todas sus clases hijas están obligadas
a implementar este método y darle la funcionalidad que le corresponda.

Veamos esto en Java:

40
/**
* Clase Abstracta Instrumento
*/
abstract class Instrumento{

public String tipo;

public abstract void tocar();


}

/**
* Clase Concreta Guitarra, hija de Instrumento
*/

class Guitarra extends Instrumento {

public Guitarra(){
tipo="Guitarra";
}

public void tocar() {


System.out.println("Tocar La Guitarra");
}
}

/**
* Clase Concreta Violin, hija de Instrumento
*/
class Violin extends Instrumento {

public Violin(){
tipo="Violin";
}

public void tocar() {


System.out.println("Tocar El violin");
}
}

41
/**
* Clase Concreta Saxofon, hija de Instrumento
*/
class Saxofon extends Instrumento {

public Saxofon(){
tipo="Saxofon";
}

public void tocar() {


System.out.println("Tocar el Saxofon");
}
}

public class Principal {

public static void main(String arg[]){


/**Objeto miGuitarra de tipo Instrumento */
Instrumento miGuitarra= new Guitarra();
System.out.println("Instrumento : "+miGuitarra.tipo);
miGuitarra.tocar();
System.out.println();
/**Objeto miSaxofon de tipo Instrumento */
Instrumento miSaxofon= new Saxofon();
System.out.println("Instrumento : "+miSaxofon.tipo);
miSaxofon.tocar();
System.out.println();
/**Objeto miViolin de tipo Instrumento */
Instrumento miViolin= new Violin();
System.out.println("Instrumento : "+miViolin.tipo);
miViolin.tocar();

42
Como vemos cada una de las clases concretas implementan el
método tocar()y le dan la funcionalidad dependiendo de cómo se toque
el instrumento, también en cada constructor de las clases definimos
el tipo, pero si nos fijamos bien en las clases concretas no tenemos la
variable tipo declarada, pues estamos usando la variable heredada de
la clase Instrumento.

Se tiene el Siguiente Resultado:

Hay que tener en cuenta que cuando trabajamos con clases


Abstractas, estas solo pueden ser heredadas mas no instanciadas, esto
quiere decir que no podemos crear objetos directamente de estas
clases.

Como vemos en la clase Principal tenemos la lógica para ejecutar


nuestra aplicación y usamos el concepto de Polimorfismo para crear los
objetos de tipo Instrumento por medio de sus clases Hijas, pero en
ningún momento creamos un objeto como instancia directa de la clase
abstracta.

43
INTERFACES

Definición

Una interfaz es una especie de plantilla para la construcción de


clases. Normalmente una interfaz se compone de un conjunto de
declaraciones de cabeceras de métodos (sin implementar, de forma
similar a un método abstracto) que especifican un protocolo de
comportamiento para una o varias clases. Además, una clase puede
implementar una o varias interfaces: en ese caso, la clase debe
proporcionar la declaración y definición de todos los métodos de cada
una de las interfaces o bien declararse como clase abstract. Por otro
lado, una interfaz puede emplearse también para declarar constantes
que luego puedan ser utilizadas por otras clases.

44
Diferencias entre clase abstracta e interfaces

Una interfaz puede parecer similar a una clase abstracta, pero


existen una serie de diferencias entre una interfaz y una clase abstracta:

 Todos los métodos de una interfaz se declaran implícitamente


como abstractos y públicos.
 Una clase abstracta no puede implementar los métodos
declarados como abstractos, una interfaz no puede
implementar ningún método (ya que todos son abstractos).
 Una interfaz no declara variables de instancia.
 Una clase puede implementar varias interfaces, pero sólo
puede tener una clase ascendiente directa.
 Una clase abstracta pertenece a una jerarquía de clases
mientras que una interfaz no pertenece a una jerarquía de
clases. En consecuencia, clases sin relación de herencia
pueden implementar la misma interfaz.

Ventajas

El uso de las interfaces Java proporciona las siguientes ventajas:

 Organizar la programación.
 permiten declarar constantes que van a estar disponibles para
todas las clases que queramos (implementando esa interfaz)
 Obligar a que ciertas clases utilicen los mismos métodos (nombres
y parámetros).
 Establecer relaciones entre clases que no estén relacionadas.

45
Declaración de una interfaz.

La declaración de una interfaz es similar a una clase, aunque emplea


la palabra reservada interface en lugar de class y no incluye ni la
declaración de variables de instancia ni la implementación del cuerpo
de los métodos (sólo las cabeceras). La sintaxis de declaración de una
interfaz es la siguiente:

public interface IdentificadorInterfaz {

// Cuerpo de la interfaz . . .

Una interfaz declarada como public debe ser definida en un archivo


con el mismo nombre de la interfaz y con extensión .java. Las cabeceras
de los métodos declarados en el cuerpo de la interfaz se separan entre
sí por caracteres de punto y coma y todos son declarados
implícitamente como public y abstract (se pueden omitir). Por su parte,
todas las constantes incluidas en una interfaz se declaran
implícitamente como public, static y final (también se pueden omitir) y
es necesario inicializarlas en la misma sentencia de declaración.

46
Por ejemplo, la interfaz Modificación declara la cabecera de un único
método:

/**

* Declaración de la interfaz Modificación

*/

public interface Modificacion {

void incremento(int a);

Segundo ejemplo: la interfaz constantes declara dos constantes


reales con el siguiente código fuente:

/**

* Declaración de la interfaz Constantes

*/

public interface Constantes {

double valorMaximo = 10000000.0;

double valorMinimo = -0.01;

47
Tercer ejemplo: la interfaz Numerico declara una constante real y
dos cabeceras de métodos con el siguiente código fuente:

/**

* Declaración de la interfaz Numerico

*/

public interface Numerico {

double EPSILON = 0.000001;

void establecePrecision(float p);

void estableceMaximo(float m);

48
Implementación de una interfaz en una clase

Para declarar una clase que implemente una interfaz es necesario


utilizar la palabra reservada implements en la cabecera de declaración
de la clase. Las cabeceras de los métodos (identificador y número y tipo
de parámetros) deben aparecer en la clase tal y como aparecen en la
interfaz implementada. Por ejemplo, la clase Acumulador implementa
la interfaz Modificacion y por lo tanto debe declarar un método
incremento:

/**

* Declaración de la Clase Acumulador

*/

public class Acumulador implements Modificacion {

private int valor;

public Acumulador (int i) {

valor = i;

public int daValor () {

return valor;

public void incremento (int a) {

49
valor += a;

Esta cabecera con la palabra implements. implica la obligación de


la clase Acumulador de definir el método incremento declarado en la
interfaz Modificacion. El siguiente código muestra un ejemplo de uso
de la clase Acumulador.

/**

* Declaración de la Clase PruebaAcumulador

*/

public class PruebaAcumulador {

public static void main (String [] args) {

Acumulador p = new Acumulador(25);

p.incremento(12);

System.out.println(p.daValor());

La clase Acumulador tendría también la posibilidad de utilizar


directamente las constantes declaradas en la interfaz si las hubiera.

50
Para poder emplear una constante declarada en una interfaz, las
clases que no implementen esa interfaz deben anteponer el identificador
de la interfaz al de la constante.

Jerarquía entre interfaces

La jerarquía entre interfaces permite la herencia simple y múltiple. Es


decir, tanto la declaración de una clase, como la de una interfaz pueden
incluir la implementación de otras interfaces. Los identificadores de las
interfaces se separan por comas. Por ejemplo, la interfaz Una
implementa otras dos interfaces: Dos y Tres.

public interface Una implements Dos, Tres {

// Cuerpo de la interfaz . . .

Las clases que implementan la interfaz Una también lo hacen con Dos
y Tres.

Otro ejemplo: pueden construirse dos interfaces, Constantes y


Variaciones, y una clase, Factura, que las implementa:

// Declaracion de la interfaz Constantes

public interface Constantes {

double valorMaximo = 10000000.0;

double valorMinimo = -0.01;

51
}

// Declaracion de la interfaz Variaciones

public interface Variaciones {

void asignaValor(double x);

void rebaja(double t);

// Declaración de la clase Factura

public class Factura implements Constantes, Variaciones {

private double totalSinIVA;

public final static double IVA = 0.16;

public double sinIVA() {

return totalSinIVA;

public double conIVA() {

return totalSinIVA * (1+IVA);

public void asignaValor(double x)

if (valorMinimo<x)

52
totalSinIVA=x;

else

totalSinIVA=0;

public void rebaja(double t) {

totalSinIVA *= (1-t/100);

public static void main (String [] args) {

factura a = new factura();

a.asignaValor(250.0);

System.out.println("El precio sin IVA es: " + a.sinIVA());


BNBNBNB System.out.println("El precio con IVA es: " + a.conIVA());
,BKBKCV System.out.println("Rebajado durante el mes de mayo un
20%");

a.rebaja(20);

System.out.println("Rebajado sin IVA es: " + a.sinIVA());


BVBVBCVBSystem.out.println("Rebajado con IVA es: " + a.conIVA());

53
Si una interfaz implementa otra, incluye todas sus constantes y
declaraciones de métodos, aunque puede redefinir tanto constantes
como métodos.

Nota: Es peligroso modificar una interfaz ya que las clases


dependientes dejan de funcionar hasta que éstas implementen los
nuevos métodos.

Una clase puede simultáneamente descender de otra clase e


implementar una o varias interfaces. En este caso la sección
implements se coloca a continuación de extends en la cabecera de
declaración de la clase. Por ejemplo:

public class ClaseDescendiente extends ClaseAscendiente


implements Interfaz {

...

54
Utilización de una interfaz como un tipo de dato.

Al declarar una interfaz, se declara un nuevo tipo de referencia.


Pueden emplearse identificadores de interfaz en cualquier lugar donde
se pueda utilizar el identificador de un tipo de dato (o de una clase). El
objetivo es garantizar la sustituibilidad por cualquier instancia de una
clase que la implemente. Por ejemplo, puede emplearse como tipo de
un parámetro de un método:

public class Calculos {

public void asignacion(Variaciones x){

...

Sólo una instancia de una clase que implemente la interfaz puede


asignarse al parámetro cuyo tipo corresponde al identificador de la
interfaz. Esta facultad se puede aprovechar dentro la propia interfaz. Por
ejemplo:

public interface Comparable {

// La instancia que llama a esMayor (this) y el parametro otra

// deben ser de la misma clase o de clases que implementen esta


interfaz

// La funcion devuelve 1, 0, -1 si this es mayor, igual o menor que


otra

55
public int esMayor(Comparable otra);

En algún caso puede ser útil declarar una interfaz vacía como, por
ejemplo:

public interface Marcador { }

Esta declaración es totalmente válida ya que no es obligatorio incluir


dentro de una interfaz la declaración de una constante o la cabecera de
un método. La utilidad de estas interfaces reside en la posibilidad de ser
empleadas como tipos de dato para especificar clases sin necesidad de
obligar a éstas a implementar algún método en concreto. Una interfaz
no es una clase pero se considera un tipo en Java y puede ser utilizado
como tal.

Otro Ejemplo de Interfaces:

 AeroSub “Viaje al fondo del mar”

public interface Aereo{

public void despegar();

public void acuatizar();

public interface Acuatico{

public void emerger();

56
public void sumergirse();

public class Aerosub implements Aereo,Acuatico{

public void despegar(){

public void acuatizar(){

public void emerger(){

public void sumergirse(){

/** Clase Que accede a las funciones de Aerosub */

public class Comandante{

57
public Aerosub vehiculo;

public Comandante (Aerosub vehiculo){

this.vehiculo=vehiculo;

public void comandar(){

vehiculo.emerger();

vehiculo.despegar();

vehiculo.acuatizar();

vehiculo.sumergirse();

/** Clases Que acceden Aerosub con funcionalidades limitadas*/

public class Aviador{

public Aereo vehiculo;

public Aviador(Aereo vehiculo){

this.vehiculo= vehiculo;

public void pilotear(){

58
vehiculo.despegar();

vehiculo.acuatizar();

public class Marino{

public Acuatico vehiculo;

public Aviador(Acuatico vehiculo){

this.vehiculo= vehiculo;

public void navegar(){

vehiculo.sumergirse();

vehiculo.emerger();

59
VARIABLES DE CLASE (static).

Definición

Las variables de clase son atributos diferentes de las variables de


instancia. Las variables de clase implican una sola zona de memoria
reservada para todas las instancias de la clase, y no una copia por
objeto, como sucede con las variables de instancia. Para diferenciarlas
de éstas en el código fuente de Java, las variables de clase se
distinguen con el modificador static en la declaración del atributo
correspondiente. Por defecto (si no se indica la palabra static), el
atributo declarado se considera variable de instancia.

Durante la ejecución del programa, el sistema reserva un único


espacio en memoria para cada variable estáticas o de clase
independientemente del número de instancias creadas de una clase.
Esta reserva se produce la primera vez que encuentra dicha clase en el
código, de forma que todas las instancias pertenecientes a una clase
comparten la misma variable de clase. A diferencias de las variables
globales fuera de la POO, las variables de clase garantizan la
encapsulación.

Las variables de clase sirven para almacenar características


comunes (constantes) a todos los objetos (número de ruedas de una
bicicleta) o para almacenar características que dependen de todos los
objetos (número total de billetes de lotería). Por ejemplo, la clase
CuentaBancaria tiene una variable de instancia, saldo, y una variable
de clase, totalCuentas.

60
Ejemplo de Uso.

public class CuentaBancaria {

// Atributos o variables miembro

public double saldo; // Variable de instancia

public static int totalCuentas=0; // Variable de clase

// Declaraciones de metodos ...

La creación de varias instancias de la clase CuentaBancaria no


conlleva la existencia de varias variables totalCuentas. Durante la
ejecución de un programa que utilice la clase CuentaBancaria sólo
existirá una variable de clase totalCuentas, independientemente del
número de instancias de la clase CuentaBancaria que se generen
(Figura mostrada abajo). Es más, no es necesario siquiera que exista
una instancia de la clase, para que lo haga la variable de clase. De
hecho, se inicializan a false, cero o null (dependiendo de su tipo) antes
de que se genere una instancia de la clase.

// Creacion de dos instancias de la clase CuentaBancaria

CuentaBancaria c1 = new CuentaBancaria();

CuentaBancaria c2 = new CuentaBancaria();

61
Las variables de clase se emplean cuando sólo es necesaria una
copia por clase que, además, esté accesible por todas las instancias de
la clase a la que pertenece. En este caso, al ser un atributo public y
static, puede accederse directamente a la variable de clase
(totalCuentas) a través de una instancia (c1 o c2) o de la clase en sí
(CuentaBancaria). Un atributo estático puede ser accedido desde
cualquier instancia de la clase, ya que es miembro de la propia clase.

public class PruebaCuentaBancaria {

public static void main (String [] args) {

CuentaBancaria c1 = new CuentaBancaria();

c1.totalCuentas++;

System.out.println("Total cuentas: " + c1.totalCuentas);

CuentaBancaria c2 = new CuentaBancaria();

c2.totalCuentas++;

System.out.println("Total cuentas: " + c2.totalCuentas);

// Acceso a traves de la clase:

62
CuentaBancaria.totalCuentas++; System.out.println("Total
cuentas: " + CuentaBancaria.totalCuentas);

// Resto de sentencias . . .

La ejecución del código anterior origina la siguiente salida por pantalla:

Total cuentas: 1

Total cuentas: 2

Total cuentas: 3

Para operar con variables de clase también podemos implementar


métodos en la clase. Por ejemplo, el siguiente método inctotalCuentas
incrementa en una unidad el valor de la variable de clase totalCuentas.

public static void inctotalCuentas() {

totalCuentas++;

Al ser declarado el método inctotalCuentas como un método public


y static, puede llamarse mediante cualquiera de las instancias (c1 o c2)
o de la clase en sí (CuentaBancaria).

63
Las variables de clase pueden declararse como public, protected o
como private y pueden pertenecer a cualquiera de los tipos de datos
primitivos de Java o bien, a otra clase existente en Java o declarada por
el usuario. En principio, la única limitación para el número de variables
de clase que puede declarar una clase es el espacio libre disponible por
el sistema que ejecute el programa.

64
CONSTANTES O VARIABLES FINALES (final)

Definición

Una clase puede contener atributos de valor constante o variables


finales. Este tipo de atributo se indica con la palabra reservada final. Las
variables finales se suelen declarar además como variables de clase
(static final) por razones de ahorro de memoria ya que, al no modificar
su valor sólo suele ser necesaria una copia en memoria por clase (y no
una por instancia).

Ejemplo de Uso

Por ejemplo, en la clase Circulo:

public class Circulo {

// Atributos o variables miembro

private static final double PI = 3.141592;

// Constante de clase

private double radio;

// Declaración de metodos ...

La palabra reservada final indica que el atributo debe comportarse


como una constante, es decir, no puede ser modificada una vez
declarada e inicializada. Por otro lado, se puede separar la declaración
de la inicialización de la variable final, realizándose ésta más tarde. En

65
este caso, el valor asignado a la variable final puede hacerse en función
de otros datos o de llamadas a métodos, con lo que el valor de la
constante no tiene porqué ser el mismo para diferentes ejecuciones del
programa.

Simulación de la Estructura Básica de Una Herencia múltiple


simulada con interfaces:

//Interfaces para herencia múltiple (estructura básica).


//Uso de las claves: interface e implement.

public interface b{
//código
}

public interface c{
//código
}

public class a implements b, c {


//código
}

public class Main {


public static void main(String[] args) {
a obj = new a();
}
}

66

También podría gustarte