Introduccion POO Con Java
Introduccion POO Con Java
Rafael Llobet Azpitarte Pedro Alonso Jord a Jaume Devesa Llinares Emili Miedes De El as Mar Idoia Ruiz Fuertes a Francisco Torres Goterris
Rafael Llobet Azpitarte Pedro Alonso Jord a Jaume Devesa Llinares Emili Miedes De El as Mar Idoia Ruiz Fuertes2 a Francisco Torres Goterris
ISBN: 978-84-613-0411-0
1 Java 2 Con
es una marca registrada de Sun Microsystems. el apoyo del programa de ayudas FPI del Ministerio de Ciencia e Innovacin (ref. BES-2007-17362). o
Licencia
Este texto se distribuye bajo una modalidad de licencia Creative Commons. Usted es libre de copiar, distribuir y comunicar pblicamente la obra bajo las condiciones siguientes: u Debe reconocer y citar a los autores originales. No puede utilizar esta obra para nes comerciales (incluyendo su publicacin, a travs o e de cualquier medio, por entidades con nes de lucro). No se puede alterar, transformar o generar una obra derivada a partir de esta obra. Al reutilizar o distribuir la obra, tiene que dejar bien claro los trminos de la licencia de esta e obra. Alguna de estas condiciones puede no aplicarse si se obtiene el permiso del titular de los derechos de autor. Los derechos derivados de usos leg timos u otras limitaciones no se ven afectados por lo anterior.
iv
Indice general
1. Introduccin a la Programacin Orientada a Objetos o o 1.1. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . o 1.2. La Orientacin a Objetos . . . . . . . . . . . . . . . . . . . . o 1.3. Los objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4. Las clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.5. La iniciacin de instancias . . . . . . . . . . . . . . . . . . . . o 1.6. Herencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.7. Evolucin histrica y diferencias entre la programacin clsica o o o a 2. Introduccin al lenguaje Java o 2.1. Introduccin . . . . . . . . . . . . . . . . . o 2.2. Portabilidad: la mquina virtual de Java . a 2.3. Cuestiones sintcticas . . . . . . . . . . . a 2.3.1. Indentacin . . . . . . . . . . . . . o 2.3.2. Comentarios . . . . . . . . . . . . 2.3.3. Identicadores . . . . . . . . . . . 2.3.4. Separadores . . . . . . . . . . . . . 2.3.5. Palabras reservadas . . . . . . . . 2.4. Estructura bsica de un programa en Java a 2.4.1. Programa de consola . . . . . . . . 2.4.2. Programa grco . . . . . . . . . . a 2.4.3. Applet . . . . . . . . . . . . . . . . 2.5. Tipos de datos . . . . . . . . . . . . . . . 2.5.1. Tipos de datos simples . . . . . . . 2.5.2. Tipos de datos objeto . . . . . . . 2.5.3. Variables y constantes . . . . . . . 2.5.4. Operadores . . . . . . . . . . . . . 2.5.5. Conversin de tipos . . . . . . . . o 2.5.6. Vectores . . . . . . . . . . . . . . . 2.6. Estructuras de control . . . . . . . . . . . 2.6.1. Estructuras condicionales . . . . . 2.6.2. Estructuras de repeticin . . . . . o 2.7. Sentencias de salto . . . . . . . . . . . . . 2.8. Ejercicios resueltos . . . . . . . . . . . . . 2.8.1. Enunciados . . . . . . . . . . . . . 2.8.2. Soluciones . . . . . . . . . . . . . . 2.9. Ejercicios propuestos . . . . . . . . . . . . v . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . y la POO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1 2 3 5 6 9 11 15 15 15 17 17 17 18 18 18 18 18 21 22 23 24 24 25 28 32 33 35 35 39 42 43 43 45 48
3. Fundamentos de la Programacin Orientada a o 3.1. Clases y objetos . . . . . . . . . . . . . . . . . . 3.1.1. Instanciacin de clases . . . . . . . . . . o 3.1.2. Destruccin de objetos . . . . . . . . . . o 3.2. Mtodos . . . . . . . . . . . . . . . . . . . . . . e 3.2.1. Constructores . . . . . . . . . . . . . . . 3.2.2. Ocultacin de atributos . . . . . . . . . o 3.2.3. Sobrecarga de mtodos . . . . . . . . . . e 3.2.4. Objetos como argumentos de un mtodo e 3.2.5. Devolucin de objetos desde un mtodo o e 3.3. Miembros de instancia y de clase . . . . . . . . 3.4. Encapsulacin de cdigo . . . . . . . . . . . . . o o 3.4.1. Modicadores de acceso . . . . . . . . . 3.5. Clases anidadas y clases internas . . . . . . . . 3.6. La clase String . . . . . . . . . . . . . . . . . . 3.7. Paquetes . . . . . . . . . . . . . . . . . . . . . . 3.7.1. La variable de entorno CLASSPATH . . . 3.8. Paso de argumentos al programa . . . . . . . . 3.9. Ejercicios resueltos . . . . . . . . . . . . . . . . 3.10. Ejercicios propuestos . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
49 49 51 54 54 56 57 58 59 61 62 66 68 70 71 73 74 74 75 79 81 81 82 84 85 89 89 90 90 93 93 97 100 103 105 107 107 108 111 115 115 115 115 119 120
4. Herencia y Polimorsmo 4.1. Herencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.1.1. Conceptos bsicos . . . . . . . . . . . . . . . . . . . a 4.1.2. Uso de la palabra reservada super . . . . . . . . . . 4.1.3. Constructores y herencia . . . . . . . . . . . . . . . . 4.1.4. Modicadores de acceso . . . . . . . . . . . . . . . . 4.1.5. La clase Object . . . . . . . . . . . . . . . . . . . . 4.2. Polimorsmo . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2.1. Sobreescritura de mtodos . . . . . . . . . . . . . . . e 4.2.2. Tipo esttico y tipo dinmico . . . . . . . . . . . . . a a 4.2.3. La conversin hacia arriba . . . . . . . . . . . . . . . o 4.2.4. Enlace dinmico y polimorsmo . . . . . . . . . . . a 4.2.5. Clases abstractas . . . . . . . . . . . . . . . . . . . . 4.2.6. La conversin hacia abajo: instanceof . . . . . . . o 4.2.7. Sobreescribiendo la clase Object . . . . . . . . . . . 4.3. Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3.1. El problema de la herencia mltiple . . . . . . . . . u 4.3.2. Declaracin e implementacin de interfaces . . . . . o o 4.3.3. Implementacin de polimorsmo mediante interfaces o 4.3.4. Denicin de constantes . . . . . . . . . . . . . . . . o 4.3.5. Herencia en las interfaces . . . . . . . . . . . . . . . 4.4. Ejercicios resueltos . . . . . . . . . . . . . . . . . . . . . . . 4.4.1. Enunciados . . . . . . . . . . . . . . . . . . . . . . . 4.4.2. Soluciones . . . . . . . . . . . . . . . . . . . . . . . . 4.5. Ejercicios propuestos . . . . . . . . . . . . . . . . . . . . . . vi
5. Manejo de excepciones 5.1. Introduccin a las excepciones . . . . . . . . . . . . . . . . . . . o 5.2. Captura de excepciones . . . . . . . . . . . . . . . . . . . . . . 5.2.1. La sentencia try-catch . . . . . . . . . . . . . . . . . . 5.2.2. Renando la captura de excepciones . . . . . . . . . . . 5.2.3. Capturando ms de una excepcin . . . . . . . . . . . . a o 5.2.4. try-catch anidados . . . . . . . . . . . . . . . . . . . . 5.2.5. La sentencia nally . . . . . . . . . . . . . . . . . . . . . 5.2.6. La forma general de try-catch . . . . . . . . . . . . . . 5.2.7. Excepciones no capturadas . . . . . . . . . . . . . . . . 5.3. La clase Throwable . . . . . . . . . . . . . . . . . . . . . . . . . 5.3.1. El mtodo getMessage . . . . . . . . . . . . . . . . . . e 5.3.2. El mtodo printStackTrace . . . . . . . . . . . . . . . e 5.4. Captura vs. propagacin de excepciones: la sentencia throws . o 5.4.1. Otra forma de propagar excepciones: la sentencia throw 5.4.2. Sentencias throw y bloques finally . . . . . . . . . . . 5.5. Lanzando nuevas excepciones . . . . . . . . . . . . . . . . . . . 5.5.1. Lanzando excepciones de tipos existentes . . . . . . . . 5.5.2. Lanzando excepciones de nuevos tipos . . . . . . . . . . 5.6. Cuestiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.7. Ejercicios resueltos . . . . . . . . . . . . . . . . . . . . . . . . . 5.8. Ejercicios propuestos . . . . . . . . . . . . . . . . . . . . . . . . 6. Interfaz grca de usuario y applets a 6.1. Introduccin . . . . . . . . . . . . . . . . . . o 6.2. Applets . . . . . . . . . . . . . . . . . . . . 6.2.1. Concepto de applet . . . . . . . . . . 6.2.2. Ciclo de vida de un applet . . . . . . 6.3. Grcos . . . . . . . . . . . . . . . . . . . . a 6.3.1. Sistema de coordenadas . . . . . . . 6.3.2. Figuras . . . . . . . . . . . . . . . . 6.3.3. Color . . . . . . . . . . . . . . . . . 6.3.4. Texto . . . . . . . . . . . . . . . . . 6.3.5. Imgenes y sonido . . . . . . . . . . a 6.4. Componentes de interfaz de usuario . . . . 6.4.1. Componentes principales . . . . . . 6.4.2. Un ejemplo completo . . . . . . . . . 6.5. Contenedores y gestores de ubicacin . . . . o 6.5.1. Contenedores . . . . . . . . . . . . . 6.5.2. Gestores de ubicacin . . . . . . . . o 6.6. Programacin dirigida por eventos . . . . . o 6.6.1. Eventos . . . . . . . . . . . . . . . . 6.6.2. Fuente y oyente de eventos . . . . . 6.6.3. Escuchando eventos . . . . . . . . . 6.7. Paso de parmetros en cheros html . . . . a 6.8. Restricciones y posibilidades de los applets 6.9. Gu de redibujado: el mtodo paint() . . as e 6.10. Cuestiones . . . . . . . . . . . . . . . . . . . 6.11. Ejercicios resueltos . . . . . . . . . . . . . . 6.12. Ejercicios propuestos . . . . . . . . . . . . . vii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
123 123 124 125 127 128 129 130 130 131 132 133 133 134 135 137 138 138 139 140 140 142 145 145 146 146 148 149 149 149 150 151 151 152 152 156 158 159 159 163 164 164 167 172 173 174 174 175 190
Cap tulo 1
Antes de empezar a desarrollar las caracter sticas propias de la programacin orientada a o objeto, conviene hacer una revisin de alto nivel de la programacin, sus fases y sus diferentes o o mtodos. e En el proceso de desarrollo de un sistema de informacin (un programa, software, en o general) hay una serie de etapas o fases en las que la programacin como tal es una de ellas, o ni tan siquiera la ms importante. Hay diferentes modelos de desarrollo en los que se denen a esas fases o etapas antes comentadas; uno de ellos es el mtodo en cascada (waterfall ) que e nos servir como gu en el desarrollo de estas ideas. a a Segn este modelo, a grandes rasgos, el desarrollo software consta de las siguientes fases: u Anlisis: Esta es una de las fases ms importantes puesto que se trata de denir y a a analizar el problema en su totalidad. En general, se trata de entender el enunciado del problema. Evidentemente, para una resolucin correcta (ecaz y eciente) de un o problema lo mejor es conocerlo. Diseo: Junto con la anterior, la ms importante y consiste en, dado el anlisis anten a a rior, disear una solucin del problema. Se tratar de denir los mdulos, patrones, n o a o algoritmos, etc. que nos ayudaran a su solucin. Entre esta fase y la anterior, se deber o a consumir un 70-80 % del tiempo del proyecto. Implementacin: Ser un equivalente a la programacin. En este caso, el diseo anterior o a o n se traducir a un lenguaje de programacin concreto; en esta fase es donde realmente a o se programa (codica). Pruebas: Periodo en el que el producto se somete a diferentes tipos de pruebas: de sistema, de integracin, etc. o Implantacin: Proceso de puesta en produccin del producto. o o Mantenimiento: Realizar mejoras varias sobre el producto desde el punto de vista tecnolgico, funcional, etc. o 1
Normalmente, siempre nos centramos en la fase de codicacin/implementacin pero, o o como vemos, este proceso es mucho ms complejo de lo que podr a amos pensar. Cuando se decide iniciar un desarrollo, lo primero que se debe decidir es el paradigma de trabajo. La eleccin del paradigma marca signicativamente la forma de anlisis y diseo de o a n la solucin del problema. As un mismo problema se podr abordar usando un paradigma o a procedural clsico (es el que se ha usado en cursos anteriores de programacin, cuando se ha a o programado en Pascal o C, por ejemplo) o bien un paradigma orientado a objetos (el que usaremos en este curso). Por ejemplo, el diseo de un software que implementara el juego del n ajedrez en un paradigma procedural y en un paradigma orientado a objeto tendr poco an que ver. La eleccin del paradigma marca la eleccin del lenguaje. As si hemos elegido un paradigo o , ma procedural para resolver el problema, lo normal es que lo implementemos en un lenguaje t picamente procedural (C o PASCAL, por ejemplo); por otro lado, si elegimos un paradigma orientado a objetos es normal elegir un lenguaje orientado a objetos (C++ o Java, por ejemplo). Ocurre bastantes veces que se cree que un programador de C o Pascal o cualquier otro lenguaje de programacin procedural puede convertirse en un programador de C++, Object o Pascal o Java sin ms que aprender una serie de nuevas estructuras e instrucciones. Por a desgracia, esto no es as en absoluto; el estudiar un lenguaje orientado a objetos va ms a all que aprender otro lenguaje ms. En realidad, el cambio es ms profundo y se trata a a a del estudio de un nuevo paradigma, esto es, una nueva forma de abordar los problemas y una manera de implementar esas soluciones mediante el lenguaje de programacin Java. o Por tanto, el objetivo del curso se deber ver desde la perspectiva de este cambio de a paradigma y el uso del lenguaje de programacin Java considerarlo como un medio, ms que o a como un n. Una vez revisado el paradigma, el aprendizaje de cualquier lenguaje orientado a objetos ser bastante ms simple. Analizaremos en los siguientes prrafos el porqu del a a a e estudio de un nuevo paradigma. Por otro lado, el objetivo de este tema es introducir conceptos para que de manera intuitiva se vaya comprendiendo qu signica cambiar de paradigma; y concretamente usar e el paradigma orientado a objetos. Cada uno de estos conceptos se vern desarrollados en a temas espec cos a lo largo del texto y del curso; este tema slo pretende dar un repaso o a nivel muy abstracto de cada una de las caracter sticas fundamentales que diferencian un lenguaje orientado a objetos.
1.2.
La Orientacin a Objetos o
La orientacin a objetos promete mejoras de amplio alcance en la forma de diseo, deo n sarrollo y mantenimiento del software ofreciendo una solucin a largo plazo a los problemas o y preocupaciones que han existido desde el comienzo en el desarrollo de software: La falta de portabilidad del cdigo y su escasa reusabilidad. o Cdigo que es dif de modicar. o cil Ciclos de desarrollo largos. Tcnicas de codicacin no intuitivas. e o Un lenguaje orientado a objetos ataca estos problemas. Tiene tres caracter sticas bsicas: a debe estar basado en objetos, basado en clases y capaz de tener herencia de clases. Muchos lenguajes cumplen uno o dos de estos puntos; muchos menos cumplen los tres. La barrera
ms dif de sortear es usualmente la herencia. El concepto de programacin orientada a a cil o objetos (POO) no es nuevo, lenguajes clsicos como SmallTalk se basan en ella. a Dado que la POO se basa en la idea natural de la existencia de un mundo lleno de objetos y que la resolucin del problema se realiza en trminos de objetos, un lenguaje se dice que o e est basado en objetos si soporta objetos como una caracter a stica fundamental del mismo. No debemos confundir que est basado en objetos con que sea orientado a objetos: para e que sea orientado a objetos al margen que est basado en objetos, necesita tener clases y e relaciones de herencia entre ellas. Hemos utilizado con mucha frecuencia la palabra paradigma, que convendr formalizar a de alguna forma: Se entiende paradigma como un conjunto de teor estndares y mtodos as, a e que juntos representan una forma de organizar el conocimiento, esto es, una forma de ver el mundo. La visin OO nos obliga a reconsiderar nuestras ideas a cerca de la computacin, o o de lo que signica ponerla en prctica y de cmo deber estructurarse la informacin en los a o a o sistemas de informacin. o
1.3.
Los objetos
El elemento fundamental de la POO es, como su nombre indica, el objeto. Podemos denir un objeto como un conjunto complejo de datos y programas que poseen estructura y forman parte de una organizacin. En este caso las estructuras de datos y los algoritmos o usados para manipularlas estn encapsulados en una idea comn llamada objeto. a u Esta denicin especica dos propiedades caracter o sticas de los objetos: En primer lugar, un objeto no es un dato simple, sino que contiene en su interior cierto nmero de componentes bien estructurados. u En segundo lugar, cada objeto no es un ente aislado, sino que forma parte de una organizacin jerrquica o de otro tipo. o a Para ilustrar mejor las diferencias entre la programacin procedural clsica y la POO o a vamos a plantear, de forma intuitiva, un pequeo ejemplo escrito en C y despus planteado en n e Java. Se trata de tener en un array una serie de guras geomtricas que tendrn lgicamente e a o los subprogramas encargados de leer los datos, dibujarlos, etc... La estructura de datos en C podr ser: a
struct Figura { int Tipo; // 1.-hex 2.-rect int Color; int Borde; float x, y, z, t, w; } struct Figuras [N]; void function LeeHex (...........); void function LeeRect (.......); void function DibujaHex (.......); void function DibujaRect (.....);
En el caso de que quisiramos dibujar todas las guras que aparecen en el array har e amos algo parecido a esto:
for (int switch case case } i=1; i<n; i++) { (f[i].tipo) { 1 : DibujaHex (....); break; 2 : DibujaRect (...); break; . .
En general, tendremos para cada gura sus correspondientes funciones que se encarguen de leer las coordenadas, de dibujarlos por pantalla, etc. Qu ocurre si quiero aadir una e n nueva gura geomtrica? Tendremos que modicar el programa aadiendo el tipo corree n spondiente y los subprogramas necesarios para manejarlos, adems es ms que probable que a a tengamos que revisar la mayor parte del cdigo para actualizarlo a la nueva situacin. Esta o o tarea ser ms o menos dicultosa en funcin de lo ordenado que se haya sido al crear el a a o cdigo. o Si resolviramos este mismo problema de forma OO la solucin a la hora de aadir una e o n nueva gura geomtrica no ser tan aparatosa. En primer lugar denir e a amos un nuevo tipo de gura, por ejemplo, para representar los nuevos objetos C rculos que tendrn sus atributos a propios (en este caso, por ejemplo, el punto que dene el centro y el radio del c rculo) y sus mtodos, como por ejemplo LeerCoordenadas y Dibujar. De forma que el for anterior se e podr reescribir de esta forma: a
for (int i=1; i<n; i++) { v[i].Dibujar(); }
Como vemos, el hecho de aadir un nuevo tipo de objeto no implica ninguna modicacin n o en la estructura. Lgicamente implicar aadir la nueva gura al cdigo, pero si se ha sido o a n o cuidadoso a la hora de encapsular los datos propios (atributos) y los subprogramas encargados de manejarlos (mtodos) ser bastante ms sencillo mantener y trabajar con el cdigo. En e a a o la gura 1.1 se muestra un esquema de cmo se ven los objetos desde el punto de vista que o acabamos de plantear.
Usando como ejemplo el caso de un objeto c rculo tendremos que, desde un punto de vista estructural, est compuesto de: a Atributos: como podr ser las coordenadas que denen el centro del c an rculo y el radio. Mtodos: por ejemplo, el que nos permite leer o modicar los valores de estos atributos, e dibujarlo o bien calcular su circunferencia o su rea. a Son conceptos fundamentales en este paradigma la idea de encapsulacin, en cuanto a o que atributos y mtodos forman el objeto como una unica unidad y el de ocultacin de e o informacin en cuanto que los atributos deben estar lo ms ocultos posibles al exterior y slo o a o ser manipulados o consultados a travs de los mtodos pertinentes que son los que conforman e e la interfaz del objeto.
1.4.
Las clases
En el punto anterior hemos denido de qu trata un objeto; pero en realidad el concepto e ms adecuado a la hora de denir las caracter a sticas de los objetos de nuestro sistema es el concepto de clase. Una clase es la descripcin de una familia de objetos que tienen la o misma estructura (atributos) y el mismo comportamiento (mtodos). En el caso que estamos e siguiendo tendr amos la clase Circuloen el que se denir los atributos y los mtodos que an e denen cualquier c rculo. Cada ejemplar de c rculo ser un objeto (o instancia) de la clase a c rculo en los que cada uno tendr sus propias coordenadas (su centro y su radio), y todos a compartir los mismos mtodos. an e De esta forma se organizan los conocimientos que tenemos del sistema; las clases son el patrn habitual que proporcionan los lenguajes orientados a objetos para denir la imo plementacin de los objetos. Por ejemplo, supongamos que en nuestro sistema detectamos o la necesidad de tener empleados, por tanto denimos las caracter sticas comunes de todo empleado en un pseudo-cdigo orientado a objetos: o
Clase Empleado { Atributos DNI: Cadena; NRP: Cadena; Nombre: Cadena; Direccin: Cadena; o AoEntrada: Integer; n AoNacimiento: Integer; n Complementos: Real; Metodos : : Integer AosEmpresa(); n : : Integer Edad(); : : Real Sueldo(Real Extras); : :
Todos los objetos son instancias de una clase, es decir, ejemplares o ejemplos concretos de la clase. Como vemos, la clase lo dene todo, es una entidad autocontenida en cuanto que contiene todo lo necesario para denir la estructura o los datos (atributos) que cada objeto tendr y la manera de manejarse esos objetos a travs de los mtodos. a e e Dada la clase anterior, podremos crear (ya veremos cmo) los empleados (objetos, ino stancias de la clase, ejemplares, etc..) que necesite de los que tendr una referencia, es decir, e una variable que apunte a ese empleado. As podr tener la referencia emp1 que coincide con e el empleado con DNI 00001, con NRP 2345, de nombre Jos Garc etc... Si quiero obtener e a, el sueldo de este empleado tendr que invocar al mtodo correspondiente en el objeto que lo e e representa a travs de la referencia de la siguiente forma: e
[Link](136000);
Esta invocacin tendr como contrapartida la ejecucin del mtodo Sueldo en el objeto o a o e especicado y no en otro. Recordando lo comentado en el apartado anterior, y siendo ms formales en la denicin, a o cada clase posee una doble componente: Una componente esttica, los datos, que son campos con nombres, que poseen ciertos a valores. Estos campos caracterizan el estado del objeto. A los campos les llamaremos usualmente atributos. Una componente dinmica, los procedimientos o funciones, llamados mtodos, que a e representan el comportamiento comn de los objetos que pertenecen a la misma clase. u Los mtodos son los encargados de manipular los campos de los objetos (adems, es e a una prctica muy mala el modicar atributos de forma directa: rompe la encapsulacin a o y la ocultacin de la informacin) y caracterizan las acciones que se pueden realizar o o sobre los mismos. Esta componente dinmica forma la llamada interfaz de la clase, y a suministra la unica forma de acceso a los objetos. Para efectuar una operacin asociada o con un objeto se debe mencionar el objeto implicado, que llamaremos el receptor del mensaje, el mtodo a activar, y los argumentos sobre los que se aplica. e En cuanto a los mtodos conviene comentar que no todos tienen por qu ser accesibles e e desde el exterior a cualquier usuario; puede que existan mtodos que slo sean visibles para e o un grupo concreto de clases o slo para la clase en la que se denen. Una visin sencilla de o o un objeto es la combinacin de estado y comportamiento. El estado se describe mediante los o atributos, en tanto que el comportamiento se caracteriza por los mtodos. e
1.5.
La iniciacin de instancias o
Como ya se ha comentado en el apartado anterior, una clase es una entidad conceptual que describe un conjunto de objetos. Su denicin sirve como modelo para crear sus o representantes f sicos llamados instancias u objetos. En la declaracin de un objeto establecemos la referencia que identicar al objeto meo a diante un identicador. Por creacin o iniciacin nos referiremos a la asignacin de espacio o o o de memoria para un nuevo objeto y la ligadura de dicho espacio a su identicador; adems, a por iniciacin aludiremos no slo a la puesta de valores iniciales en el rea de datos para un o o a objeto, sino tambin al proceso ms general de establecer las condiciones iniciales bsicas e a a para la manipulacin de un objeto. o La iniciacin se facilita usando constructores de objetos. Un constructor es un mtodo que o e tiene el mismo nombre que la clase de la cual es constructor. El mtodo se invoca siempre e
que se crea un objeto de la clase asociada. Veamos un ejemplo de una clase que representa nmeros complejos escrito esta vez ya en Java y no en pseudo-cdigo: u o
class Complejo { private int real; private int imag; Complejo (int pr, int pi) { real = pr; imag = pi; } Complejo () { real = 0; imag = 0; } int real () { return (real); } int imag () { return (imag); }
En primer lugar nos jamos en que hemos construido mtodos para acceder a los atributos e de la clase. Estos los hemos declarado privados para enfatizar que slo deben poder ser o accedidos desde fuera de la clase a travs de los mtodos que creemos a tal n. Si quisiramos e e e hacer uso de un nmero complejo, deber u amos tener una porcin de cdigo similar a la o o siguiente:
Complejo c; ... c = new Complejo(34, 4);
Con lo que se crear un objeto de tipo Complejo con los valores 34 y 4 para la parte real a e imaginaria, respectivamente. Una instancia es un objeto particular, que se crea respetando los planes de construccin dados por la clase a la que pertenece. Todas las instancias de o una misma clase tienen los mismos atributos con los valores que le corresponda. Todas las instancias poseen los mismos mtodos, slo que al invocarlos, slo se ejecuta el del objeto de e o o esa clase al que se le invoca. Aunque todas las instancias de la misma clase posean los mismos atributos, stos pueden e contener valores diferentes correspondientes a la naturaleza particular de cada instancia. La lista de atributos es retenida por la clase, mientras que las instancias poseen los valores de esos campos. El mecanismo de constructores se hace considerablemente ms poderoso cuando se coma bina con la capacidad de sobrecargar funciones (aunque como veremos, esta cualidad no es exclusiva de los constructores). Se dice que una funcin est sobrecargada cuando hay o a dos o ms cuerpos de funcin conocidos por el mismo nombre. Se rompe la ambigedad a o u de las funciones sobrecargadas por las listas de parmetros. En nuestro caso, el constructor a est sobrecargado, de forma que si escribimos a
Se crear un nmero Complejo en el que tanto la parte real como la imaginaria valen a u 0. El hecho de elegir uno de los dos mtodos se realiza en funcin de los parmetros. En la e o a gura 1.2 se muestra esta situacin esquemticamente. o a
Figura 1.2: Ejemplo de instanciacin. o Veamos cmo usar o amos la clase anteriormente presentada con alguna instruccin ms. o a Algunas de las instrucciones que aparecen no las hemos explicado todav pero no inuyen a en el objetivo del ejemplo:
Complejo c1 = new Complejo (5, 3); Complejo c2 = new Complejo (2, 4); [Link]("El complejo" + c1 + " es: "); [Link](" Parte Real: " + [Link]()); [Link](" Parte Imaginaria: " + [Link]());
F sicamente, los atributos de una instancia se pueden considerar como variables (en algunos casos aparecen como variables de instancia); cada vez que el valor de un campo es modicado el nuevo valor se sustituye en la correspondiente instancia. Cada instancia tendr su coleccin independiente de variables que slo se podrn modicar usando los mtodos a o o a e dispuestos para ello (si se han declarado como privadas). Existen otro tipo de atributos llamados atributos de clase (tambin llamados variables e de clase) que se utilizan para denir campos que tienen un valor que es comn a todas las u instancias de una misma clase (en Java son los atributos estticos). El valor de un atributo a de clase reside en la propia clase y es compartido por todos los objetos de la citada clase. Esta visin de objeto que acabamos de comentar tiene un trmino asociado de uso muy o e comn en los diferentes paradigmas de programacin que es la encapsulacin. u o o El comportamiento general de un programa orientado a objeto se estructura en trminos e de responsabilidades. Es decir, se invocan mtodos en objetos que tienen la responsabilidad e de responder mediante la ejecucin de los mismos. o Cuando en el ejemplo anterior escrib amos [Link]() estbamos invocando el mtodo a e imag() al objeto de tipo Complejo de referencia c1 que provocaba la ejecucin del mtodo o e imag() denido en el mismo. Volvemos aqu al principio de ocultacin de informacin en o o
1.6. Herencia
cuanto a que el cliente que env la peticin no necesita conocer el medio real con el que sta a o e ser atendida. a
1.6.
Herencia
Supongamos que estamos diseando un programa orientado a objetos y nos damos cuenta n de que aparece una Clase B que es idntica a una Clase A excepto que tiene unos atributos e y mtodos ms que sta. Una manera muy sencilla y poco OO de solucionar la situacin e a e o ser simplemente copiar el cdigo de la Clase A en la Clase B y aadir las l a o n neas de cdigo o propias de esta clase. El mecanismo ideal, sin embargo, es el concepto de herencia. En todo caso, ser un error pensar que el principal uso de la herencia es evitarnos escribir cdigo, a o como veremos en el tema correspondiente, la herencia tiene que ver con la relacin entre o tipos de datos. Una vez aceptada la losof bsica de diseo OO, el siguiente paso para aprender a a a n programar de forma OO implica adquirir conocimientos sobre cmo usar con efectividad o clases organizadas bajo una estructura jerrquica basada en el concepto de herencia. Por a herencia nos referimos a la propiedad por la que los ejemplares de una clase hija (subclase) pueden tener acceso tanto a los datos como al comportamiento (mtodos) asociados con una e clase paterna (superclase). La herencia signica que el comportamiento y los datos asociados con las clases hijas son siempre una extensin de las propiedades asociadas con las clases paternas. Una subclase o debe reunir todas las propiedades de la clase paterna y otras ms. Esquemticamente, la a a herencia la podemos representar como se muestra en la gura 1.3.
Figura 1.3: Esquema de herencia. Qu benecios nos aporta el uso de la herencia? Entre los muchos benecios que se e otorgan al uso de la herencia, los dos ms relevantes en el punto en el que nos encontramos a son: Reusabilidad del software: Cuando el comportamiento se hereda de otra clase, no necesita ser reescrito el cdigo que proporciona ese comportamiento. Otros benecios asoo ciados a la reusabilidad son una mayor abilidad (cuanto ms se use un cdigo ms a o a pronto aparecern los errores) y menor costo de mantenimiento gracias a la compartia cin de cdigo. o o Consistencia de la interfaz: Cuando mltiples clases heredan de la misma superclase, u nos aseguran que el comportamiento que heredan ser el mismo en todos los casos. Es a
10
ms fcil garantizar que las interfaces para objetos similares sean en efecto similares y a a dieran lo menos posible. Al principio es normal tener cierta inseguridad a la hora de reconocer las clases en nuestros sistemas y tambin el reconocer cuando una clase debe ser una subclase de otra. La regla ms e a importante es que, para que una clase se relacione con otra por medio de la herencia, debe haber una relacin de funcionalidad entre ambas. Al decir que una clase es una superclase o de una subclase, estamos diciendo que la funcionalidad y los datos asociados con la clase hija (subclase) forman un superconjunto de la funcionalidad y los datos de la clase paterna (superclase). El principio que dice que el conocimiento de una categor ms general es aplicable a a tambin a la categor ms espec e a a ca se llama herencia. Por ejemplo, un estudiante se puede ver como una clase cuya categor ms general (superclase) es la clase Humano que a su a a vez tiene otra categor ms general (otra superclase) que son los mam a a feros, formando una organizacin del conocimiento en forma de rbol, encontrndose en las hojas las instancias o a a de las clases. Por ejemplo podemos encontrarnos con una estructura jerrquica como la a mostrada en la gura 1.4 de una representacin de una realidad. o
Figura 1.4: Ejemplo de una estructura jerrquica. a Dentro de este marco general hay lugar para variaciones en la forma en la que hacer uso de la herencia, la ms habitual es la siguiente: a Especializacin: Es el uso ms comn de la herencia. Si se consideran dos conceptos o a u abstractos A y B, y tiene sentido el enunciado A es un B, entonces es probablemente correcto hacer a A subclase de B. Por ejemplo, un perro es un mam fero. Por otra parte, si tiene ms sentido decir A tiene un B entonces quiz sea mejor hacer que los objetos de a a la clase B sean atributos de la clase A. Por ejemplo, un nmero complejo tiene una parte u imaginaria. Por otro lado, existen dos enfoques alternativos en cuanto a cmo deben estructurarse o las clases como un todo en los lenguajes OO. Un enfoque sostiene que todas las clases deben estar contenidas en una sola gran estructura de herencia (Smalltalk, Objetive-C, Java). La
11
ventaja de este enfoque es que la funcionalidad proporcionada en la ra del rbol (clase z a Object) es heredada por todos los objetos. El otro enfoque sostiene que las clases que no estn lgicamente relacionadas deben ser por completo distintas (C++ y Object Pascal ). a o
1.7.
Iniciamos este punto de diferencias mediante la resolucin de un sencillo ejemplo mediante o la programacin algor o tmica clsica (en C) y mediante el paradigma orientado a objetos (en a Java). No se trata en ningn caso de traducir una solucin algor u o tmica clsica a una solucin a o orientada a objetos. Resaltar una vez ms que son maneras absolutamente distintas de ver a una realidad. El ejemplo es la representacin de la estructura de datos para representar una lista o enlazada y el subprograma necesario para insertar un nodo detrs de otro nodo dado: a
struct reg { int px; struct reg *seg; }; struct reg *lst ; void insdetras (struct reg **l, int i) { struct reg *p; if (*l == NULL) inscab(&(*l),i); // si est vacio se usa el insertar en cabeza a else { p = (struct reg *) malloc(sizeof(struct reg)) ; p -> seg = (*l)->seg; p -> px = i ; (*l)-> seg = p ; } return;
Como estamos en un lenguaje procedural clsico, hemos tenido que denir por un lado a la estructura de datos que dene un nodo y por otro los procedimientos que sirven para trabajar con l. Se cumple la regla de estructuras de datos + algoritmos = programas. e En el caso de Java tendr amos que identicar los objetos de nuestro sistema, en este caso solo uno: el nodo.
class Nodo { int info; Nodo prox; Nodo (int i) { Info = i; prox = null; } public void InsDetras (Nodo e) {
La diferencia podemos intuirla claramente con el ejemplo. En este caso, un nodo es una unidad auto-contenida en la que se dene la estructura del nodo y las operaciones necesarias para trabajar con l. e A efectos de potenciar la idea de encapsulacin y ocultacin de la informacin se deber o o o a acceder a los atributos slo a travs de los mtodos que se dispongan para ello. Para forzar o e e esta situacin, lo habitual es haber hecho que los atributos fueran privados a la clase: o
class Nodo { private int info; private Nodo prox; Nodo (int i) { info = i; prox = null; } Nodo prox() { return prox; } Int info() { return info; } void ChangeInfo (Int i) { info = i; } void ChangeProx (Nodo p) { prox = p; } public void InsDetras (Nodo e) { [Link] (prox()); ChangeProx(e); }
Desde un punto de vista histrico, la idea de la POO aparece por primera vez en SmallTalk o (a principios de los 70), pero conceptos similares a los que se usan en la POO ya aparec an en Simula (1967). De cualquier forma se considera que Smalltalk fue el primer lenguaje orientado a objetos: Es uno de los lenguajes orientados a objetos ms consistentes: todos sus tipos de datos a son clases y todas sus operaciones son mensajes. No es adecuado para aplicaciones de gran envergadura: es ineciente y no permite comprobacin de tipos esttica. o a
13
A mediados de los 80 aparecen lenguajes h bridos como son el C++, Objetive-C, ObjectPascal entre otros: Incluyen tipos de datos convencionales y clases. Incluyen procedimientos adems de mtodos. a e Permiten comprobaciones en tiempo de compilacin (estticas). o a Su eciencia es equivalente a la de los convencionales. La transicin de la programacin algor o o tmica clsica a la POO es ms fcil usando este a a a tipo de lenguajes. Existen muchas diferencias entre la programacin convencional y la POO, aunque las ms o a evidentes, es decir, las sintcticas son las menos importantes y en cambio, las diferencias ms a a importantes son las menos evidentes. Nos vamos a centrar en este apartado en las menos evidentes, aunque a lo largo del curso vamos a intentar que se detecten con mayor claridad: La POO se centra en los datos en lugar de en los procedimientos. Especial nfasis en la reutilizacin: e o El objetivo de los mtodos de diseo tradicionales (diseo descendente) es encone n n trar una solucin a medida para el problema espec o co. Los programas obtenidos son correctos y ecientes pero demasiado sensibles a cambios en los requisitos. En la POO el objetivo no es ajustar las clases a los clientes que las vayan a usar, sino adaptarlas a su contexto y adaptar los clientes a las clases. Las clases deben ser ms generales de lo necesario para una aplicacin espec a o ca, pero el tiempo adicional de desarrollo se recupera con creces a medio plazo, cuando esas clases se reutilizan en otros programas para los que no se hab diseado. an n Programacin mediante extensin. POO signica normalmente extender software exo o istente. Por ejemplo, se dispone de una serie de bibliotecas de clases como mens, u ventanas, etc... y pueden extenderse para cumplir los requisitos de una aplicacin eso pec ca. Estado distribuido y responsabilidades distribuidas: En programas convencionales, el estado se almacena en las variables globales del programa principal. Aunque el programa principal suele invocar procedimientos, stos no poseen estado, es decir, transforman datos de entrada en datos de salida e pero no almacenan nada. En POO el estado es distribuido entre mltiples objetos. Cada objeto posee su u propio estado y un conjunto de procedimientos que trabajan sobre dicho estado. La POO no es la solucin a todos los problemas. Si somos conscientes de sus puntos o fuertes y de sus debilidades y se usa de forma consciente, los benecios compensan sobradamente los costes. Sin embargo, los costes pueden desbordarnos si las clases se usan de forma indiscriminada, particularmente en situaciones en las que no simplican problemas, sino que aaden complejidad. Entre los benecios ms interesantes de la POO tenemos: n a
14
Gestin de la complejidad: El principal problema de la programacin es la complejidad. o o Para resolver la complejidad necesitamos mecanismos que nos permitan descomponer el problema en pequeos fragmentos fciles de comprender por separado. Las clases n a son un buen mecanismo en esa l nea porque: Permiten la construccin de componentes manejables con interfaces simples, que o abstraen los detalles de implementacin. o Los datos y las operaciones forman una entidad que no est dispersa a lo a largo del programa, como ocurre con las tcnicas procedurales. e La localidad de cdigo y datos facilita la legibilidad. o La POO permite construir sistemas extensibles. Podemos conseguir que un sistema existente trabaje con nuevos componentes sin necesidad de modicacin. Adems, esos o a componentes se pueden aadir en tiempo de ejecucin. n o La POO mejora la reutilizacin al permitir adaptar componentes a necesidades nuevas o sin invalidar los clientes existentes. Sin embargo, los benecios vienen pagando una serie de costes, muchos de los cuales hacen que la POO no sea efectiva para muchas empresas y que siga estando relegada a la solucin de problemas menores, aunque d a d va tomando mayor relevancia: o a a Esfuerzo de aprendizaje: El cambio a la POO no es conocer cuatro instrucciones nuevas. Se introducen conceptos important simos como clases, herencia y polimorsmo. La unica manera de hacer efectivos todos estos conceptos es mediante la experi encia. Problemas de comprensin y exibilidad. Las clases normalmente vienen en estructuras o jerrquicas; si no se documenta y establece el porque de estas relaciones, al nal las a ventajas de establecer estas relaciones se convierte en una desventaja. Los dos problemas anteriores suelen llevar a soluciones que, en el mejor de los casos, son poco ecientes. Muchas veces se critica a los lenguajes orientados a objetos de ser poco ecaces, cuando en realidad lo que subyace es un problema de mal diseo y n programacin. o
Cap tulo 2
2.2.
En Java, como en otros lenguajes, el cdigo fuente ha de ser compilado y ejecutado, pero o Java se deni buscando su independencia de la mquina donde se ejecutar los prograo a an mas. En la mayor de los lenguajes, al compilar se genera un cdigo mquina directamente a o a ejecutable en una mquina determinada (y de uso limitado a ese tipo de mquina). En cama a bio, al compilar en Java, el cdigo generado es independiente del hardware y no se puede o ejecutar directamente en ninguna mquina, sino que ha de ser interpretado por una mquina a a virtual. Al cdigo generado por el compilador Java se le llama bytecode. Y la mquina que intero a preta el bytecode, generando el cdigo ejecutable para un procesador concreto, es la mquina o a virtual de Java o, en ingls, JVM (Java Virtual Machine). La portabilidad del cdigo Jae o va entre mquinas con distintos procesadores y/o sistemas operativos es posible si en esas a mquinas se ha instalado la correspondiente versin de JVM. Entonces, el bytecode compilado a o en una mquina puede ser llevado a otra mquina diferente y ejecutado sin problemas. a a 15
16
La JVM ms el conjunto de clases necesarias para ejecutar los programas constituyen a el entorno de ejecucin de Java, o JRE (Java Runtime Environment). Para ejecutar cdigo o o Java es suciente con instalar el JRE. Si, adems, se quiere escribir y compilar cdigo fuente, a o se necesita el kit de desarrollo de Java, o SDK (Software Development Kit), que incluye el JRE. En la gura 2.1 se muestra la jerarqu (simplicada) del software que constituye la a plataforma Java 2. Se aprecia que la Java Virtual Machine es el nivel inmediato superior a los sistemas operativos, que el entorno JRE incluye la JVM y adems todas las librer a as estndar de Java (APIs), y que el kit SDK incluye las herramientas de desarrollo. a Los programas o utilidades principales del SDK son las siguientes: javac. Es el compilador de Java. Traduce el cdigo fuente a bytecode. o java. Es el intrprete de Java. Ejecuta el cdigo binario a partir del bytecode. e o jdb. Es el depurador de Java. Permite ejecucin paso a paso, insercin de puntos de o o ruptura y rastreo de variables. javadoc. Es la herramienta para documentacin automtica de los programas. o a appletviewer. Otro intrprete de Java, espec e co para applets (aplicaciones grcas a para usar en navegadores web). Estos programas se pueden ejecutar desde la l nea de comandos del sistema operativo. Por ejemplo, si se ha escrito un programa en Java y el chero que lo contiene se llama [Link], se puede compilar mediante:
javac [Link]
Si el cdigo fuente no contiene errores, se genera el correspondiente bytecode, y el prograo ma se puede ejecutar mediante el siguiente comando, que invoca al mtodo main (programa e principal) de la clase MiPrograma:
java MiPrograma
17
Sin embargo, normalmente se dispondr de herramientas visuales, kits de desarrollo con a entorno de ventanas, y los programas se compilarn y ejecutarn accediendo a las correa a spondientes opciones de men. En este texto, usaremos el entorno de desarrollo JCreator, u disponible en [Link].
2.3.
Cuestiones sintcticas a
En esta seccin se describen los aspectos relativos a legibilidad y documentacin del cdigo o o o (en las subsecciones de indentacin y comentarios), las normas para denir identicadores, o los usos de los caracteres separadores de cdigo y, nalmente, se listan las palabras reservadas o del lenguaje Java.
2.3.1.
Indentacin o
Para separar las instrucciones del cdigo fuente (por razones de legibilidad) se pueden o usar espacios en blanco, tabuladores y saltos de l nea. Adems, como en cualquier lenguaje de programacin, se recomienda sangrar o indentar a o las l neas para facilitar la lectura del cdigo e identicar rpidamente donde empiezan y o a terminan los bloques de cdigo (las estructuras condicionales o iterativas que incluyan un o cierto nmero de instrucciones o, a su vez, otros bloques de cdigo). u o El compilador no necesita que el cdigo contenga estas separaciones, pero su uso es o imprescindible para que el cdigo sea comprensible para los programadores que lo lean. o
2.3.2.
Comentarios
Los comentarios en el cdigo fuente son l o neas que el compilador ignora y, por tanto, el programador puede escribir en el lenguaje que preera, que suele ser su propio lenguaje natural, por ejemplo, espaol, ingls, etc. n e El texto incluido en los comentarios pretende facilitar la comprensin del cdigo fuente o o a otros programadores (o a su mismo autor, tiempo despus de haber terminado el desare rollo del programa). Se trata, pues, de explicaciones o aclaraciones a su funcionamiento y constituyen una documentacin del programa. o Al igual que otros lenguajes, Java dispone de indicadores de comentario de una sola l nea y de mltiples l u neas: Comentarios de una l nea. Si es de l nea completa, sta debe iniciarse con //. Si slo e o parte de la l nea es comentario, el separador // delimitar la frontera entre lo que es a cdigo a compilar (a la izquierda del separador) y lo que es comentario (a la derecha o del separador y hasta el nal de la l nea).
// Programa principal que escribe "Hola" en la consola public static void main (String[] args) { [Link]( "Hola" ); // escribir "Hola" }
Comentarios de varias l neas. Deben escribirse dentro de los separadores /* (para marcar el inicio) y */ (para marcar el nal del comentario).
/* El siguiente programa principal no tiene ninguna instruccin, o y por tanto, no hace nada cuando se ejecuta */
18
Cap tulo 2. Introduccin al lenguaje Java o public static void main (String[] args) { }
Java, adems, proporciona un tercer tipo de comentario, similar al segundo (/* . . . */), a que se denomina comentario de documentacin (/** . . . */). Java dispone de una herramienta o de documentacin, javadoc, que extrae este tipo de comentarios para generar automticao a mente documentacin de referencia de los programas. Se puede obtener toda la informacin o o relativa a esta herramienta en la siguiente pgina web: [Link] a
2.3.3.
Identicadores
Al escribir cdigo fuente, los programadores tienen que dar nombres a diferentes elementos o del programa: variables, constantes, objetos, mtodos. . . Para ello se usan identicadores. e En Java, los identicadores se forman con letras maysculas y minsculas (Java las u u a distingue), d gitos y tambin los caracteres dlar y guin bajo, $ . Un identicador vlido e o o debe comenzar siempre con una letra, y no puede coincidir con una palabra reservada del lenguaje.
2.3.4.
Separadores
En los lenguajes de programacin existen caracteres separadores del cdigo, como los o o parntesis o las llaves, con funciones bien denidas. e En la tabla 2.1 se enumeran los separadores de Java y para qu se usan. Si se ha proe gramado en otros lenguajes, como C, la mayor de estos usos sern conocidos. Sin embargo, a a algunos usos de los separadores no se comprendern en este momento. Ya se irn viendo a a a lo largo de este tema y los siguientes.
2.3.5.
Palabras reservadas
Las palabras reservadas del lenguaje Java se listan en la tabla 2.2. El uso de la mayor a de estas palabras reservadas se ir viendo a lo largo del presente texto. a
2.4.
En esta seccin se presentan los tres tipos de programas que se pueden escribir en Java: o programas de consola, programas grcos y applets. Se muestra un ejemplo muy sencillo de a cada uno de estos tipos de programa. En Java, las aplicaciones grcas, en entorno de ventanas, pueden ejecutarse como proa gramas independientes o como applets, programas integrados en un navegador web. En el ultimo tema de este texto se tratarn los applets en Java. a
2.4.1.
Programa de consola
Se llama programas de consola a los programas que funcionan en modo texto, es decir, programas donde la entrada de datos y la salida de resultados se realizan mediante sucesivas l neas de texto. Para ilustrar la estructura bsica de un programa de consola, consideraremos el siguiente a sencillo ejemplo:
19
S mbolo ()
Nombre parntesis e
Uso Delimitar la lista de argumentos formales en la denicin de los mtodos. o e Delimitar la lista de argumentos actuales en las llamadas a mtodos. e Establecer cambios de precedencia de operadores, en expresiones que incluyan varios operadores. Realizar conversiones de tipo (casting). Delimitar partes en las estructuras de control (por ejemplo, en las condiciones lgicas de las instrucciones if y o while).
{}
llaves
Delimitar bloques de cdigo (en las estructuras condio cionales, en las iterativas, y en los mtodos completos). e Dar valores iniciales a variables de tipo vector.
[]
corchetes
; ,
Separador de instrucciones. Separar identicadores (por ejemplo, en una declaracin o de variables de un mismo tipo, o en una asignacin de o valores a un vector). Separar los argumentos formales y actuales de los mtoe dos.
punto
Separador de paquetes, subpaquetes y clases. Referenciar los atributos y mtodos de los objetos. e Tabla 2.1: Separadores
20
abstract cast default nal goto int operator rest synchronized try
break char double oat implements long package short throw void
byte class else for import native private static throws volatile
byvalue const extends future inner new protected super transient while
// [Link] (nombre del fichero de cdigo fuente) o public class HolaMundo { public static void main (String[] args) { [Link]( "Hola Mundo" ); } }
Se trata de un programa principal que se limita a mostrar por la consola un mensaje de saludo. En la gura 2.2 se muestra una captura de pantalla del entorno de desarrollo JCreator donde se aprecia la ventana de edicin del cdigo fuente y la ventana que muestra o o la salida obtenida al ejecutar el programa. En Java cualquier programa forma parte de una clase. Las clases constan de datos (llamados atributos) que guardan la informacin y de funciones (llamadas mtodos) que o e manipulan la informacin. o En este ejemplo, la clase (palabra reservada class) se llama HolaMundo y contiene el programa principal (palabra reservada main). En el ejemplo, como la clase se ha identicado como HolaMundo, el cdigo fuente se o guarda en un chero de nombre [Link]. Un chero de cdigo fuente (chero con la o extensin java) puede contener el cdigo de una o varias clases: o o Si el chero contiene slo una clase, su nombre debe dar nombre al chero y, cuando o se compile, se generar un chero con el mismo nombre y con la extensin class (en a o este caso, [Link]). Si el chero contiene varias clases, una de ellas contendr el programa principal main a (que es una funcin o mtodo dentro de esa clase) y su nombre ser el nombre del o e a chero. Cuando se compile un chero java con varias clases, se generarn varios cheros a class, uno por cada una de las clases existentes en el chero java. La estructura del programa principal (mtodo main) del ejemplo puede servirnos para coe mentar algunos aspectos de sintaxis generales para cualquier mtodo escrito en Java: e Presencia de calicadores antes del nombre del mtodo (en el ejemplo, aparecen tres e palabras reservadas: public, static y void).Veremos el signicado de estas palabras a lo largo del texto. Slo anticiparemos que cuando el mtodo no devuelve nada (como o e sucede aqu se indica mediante la palabra reservada void. ),
21
Presencia de la lista de parmetros o argumentos (son los datos que se suministran a a un mtodo en su llamada, al ser invocado). En el ejemplo, la lista es: (String[] e args), es decir, hay un parmetro, que es un array de objetos de la clase String. a El bloque de cdigo es el conjunto de instrucciones encerradas entre las correspondientes o llaves. En este caso, slo hay una instruccin: o o
[Link]( "Hola Mundo" );
Esta instruccin es una llamada al mtodo println. Este mtodo permite mostrar en o e e la consola los datos que se le pasen como argumento. En este caso, la cadena de texto Hola Mundo.
2.4.2.
Programa grco a
Para ilustrar la estructura bsica de un programa grco, de ventanas, consideraremos a a el siguiente sencillo ejemplo:
public class Grafico { public static void main (String[] args) { // Crear ventana Frame ventana = new Frame(); // Crear rea de texto a TextArea texto = new TextArea( "Hola Mundo" ); // Aadir rea de texto a la ventana n a [Link](texto); // Mostrar ventana [Link](true); } }
22
En este ejemplo, el programa principal crea una ventana y muestra ah un mensaje de saludo. En la gura 2.3 se muestra una captura de pantalla donde se aprecia la ventana de edicin del cdigo fuente, y (superpuesta, a la derecha) la ventana generada al ejecutar el o o programa. El programa principal contiene cuatro instrucciones, que realizan las siguientes cuatro acciones: Se crea un objeto (de la clase Frame), llamado ventana, con lo que se crea la ventana de la aplicacin grca. o a Se crea un objeto (de la clase TextArea), llamado texto, y con el valor Hola Mundo. Se aade el texto a la ventana. n Se activa la visibilidad de la ventana. En las clases de estas aplicaciones grcas se incluir todo el cdigo necesario para que a a o las ventanas contengan todos los elementos (mens, paneles grcos, etc.) que se desee. u a
2.4.3.
Applet
Un applet es un caso particular de aplicacin grca de Java, pero que se ejecuta integrada o a en una pgina web (y es gestionado por el navegador web), en lugar de ejecutarse como a aplicacin independiente. o Para ilustrar la estructura bsica de un applet, consideraremos el siguiente sencillo ejema plo:
public class AppletBasico extends Applet { public void paint (Graphics g) { [Link]( "Hola Mundo" , 50, 50);
23
En este ejemplo, el applet muestra un mensaje de saludo en su ventana. En la gura 2.4 se muestra una captura de pantalla donde se aprecia la ventana de edicin del cdigo fuente, o o y (superpuesta, a la derecha) la ventana generada al ejecutar el programa. Los applets se describirn en detalle en el ultimo tema. Aqu slo anticiparemos que los a o applets carecen de programa principal (mtodo main) y disponen de una serie de mtodos e e (init, start, stop, destroy, paint) para regular su interaccin con el navegador web. o En el ejemplo, el mtodo paint contiene una sola instruccin, la llamada al mtodo e o e drawString, que muestra la cadena de caracteres Hola Mundo en la posicin de la ventana o cuyas coordenadas son (50,50).
2.5.
Tipos de datos
En esta seccin, trataremos los diferentes tipos de datos (tipos simples, objetos, veco tores) disponibles en Java, as como los operadores existentes para cada tipo y las reglas de conversin entre tipos diferentes. o Java, como la mayor de los lenguajes de programacin, dispone de tipos de datos a o para denir la naturaleza de la informacin que manipulan los programas. Para codicar la o informacin ms sencilla (como nmeros enteros, nmeros reales, caracteres, etc.), se dispone o a u u de los llamados tipos de datos simples. Adems, Java es un lenguaje orientado a objetos, lo que implica que existen tipos de a datos objeto. Por tanto, los datos con los que operan los programas en Java tambin pueden e almacenarse en entidades ms complejas, llamadas objetos, y dichos objetos deben crearse a como instancias de clases. En Java se hablar de objetos de una determinada clase de forma a
24
valores nmeros enteros u nmeros enteros u nmeros enteros u nmeros enteros u nmeros reales u nmeros reales u caracteres lgicos: true / false o
codicacin / tamao o n 8 bits 16 bits 32 bits 64 bits IEEE-754, de 32 bits IEEE-754, de 64 bits Unicode, de 16 bits Dependiente de la JVM
similar a como se habla de variables de un determinado tipo. El concepto de clase y objeto se estudiar en profundidad en el cap a tulo 3. Java es un lenguaje fuertemente tipado, y esta caracter stica es una de las razones de su seguridad y robustez. Cada variable tiene un tipo (o pertenece a una clase de objetos). Cada expresin tiene un tipo. Cada tipo o clase est denida estrictamente. En todas las asignao a ciones se comprueba la compatibilidad de tipos o clases. Sin embargo, como veremos en esta seccin, se puede operar con datos de tipos diferentes siempre que se respeten determinadas o reglas de conversin. o
2.5.1.
Los tipos primitivos disponibles en Java, los valores que pueden representar y su tamao, n se muestran en la tabla 2.3. Para estos tipos de datos ms simples o primitivos, Java proporciona un mecanismo a sencillo para declarar variables: especicar el tipo de la variable y su nombre (identicador elegido para la variable). Por ejemplo, si en un programa se necesitan tres variables, una de tipo entero que llamaremos contador y las otras de tipo real y de nombres dato y resultado, se pueden declarar mediante:
int contador; double dato, resultado;
2.5.2.
Cuando se escriben programas en Java, se pueden usar variables que sean objetos de determinadas clases (sea porque el mismo programador haya escrito el cdigo de esas clases, o sea porque dichas clases estn disponibles en librer pblicas). Es decir, se pueden declarar e as u objetos de clases denidas por el programador o de clases estndar (ya denidas en librer a as del lenguaje Java). Los objetos declarados en nuestros programas pueden verse como variables del programa, al igual que las variables de los tipos simples. Las principales diferencias son las siguientes: Los objetos no suelen almacenar un solo dato simple, sino varios tems de informacin o (que llamamos atributos). Los objetos disponen adems de una serie de funciones (que llamamos mtodos) para a e manipular la informacin que almacenan. o
25
Los objetos de cualquier clase se declaran de manera similar a las variables de tipos simples. Por ejemplo, si en un programa se necesitan tres objetos, uno de la clase Date que llamaremos ayer y dos de la clase String y de nombres unaPalabra y otraPalabra, se pueden declarar mediante:
Date ayer; String unaPalabra, otraPalabra;
Sin embargo, y a diferencia de las variables de tipo simple, los objetos han de ser instanciados (es decir, debe reservarse expl citamente la memoria necesaria para guardar la informacin que contienen). o Los objetos se instancian mediante el operador new y la invocacin de un mtodo especial o e de la clase llamado constructor (mtodo que tiene el mismo nombre de la clase). Por ejemplo, e con la siguiente instruccin se construir un objeto llamado hoy de la clase Date: o a
Date hoy = new Date();
El objeto ayer est declarado, el objeto hoy est declarado e instanciado. a a Todo lo relativo a los tipos de dato objeto ser tratado en profundidad en el siguiente a tema.
2.5.3.
Variables y constantes
Segn las caracter u sticas del problema a resolver, el programador necesitar declarar a tems de informacin cuyo valor var durante la ejecucin del programa y tambin o e o e tems cuyo valor se mantenga constante. Por ejemplo, en un programa que calcule per metros y a reas de c rculos, se podr declarar radio, per an metro y rea como a tems variables, y , el nmero pi, como u tem constante. Como ya se ha indicado, las variables se declaran precisando su tipo y eligiendo un nombre para las mismas. En el momento de la declaracin de una variable, se puede asignar su valor o inicial. Por ejemplo:
int contador = 0; int i, j, k; // variable entera con valor inicial // 3 variables enteras, sin inicializar
Las constantes (tambin llamadas variables nales) se declaran de forma parecida, pero e indicando que son constantes mediante la palabra reservada nal, y asignndoles valores. a Por ejemplo:
final int N = 100; final char ULTIMA = Z; final double PI = 3.141592; // constante entera, de valor 100 // constante carcter, de valor Z a // constante real, de valor $\Pi$
Valores de las variables y constantes Tanto para denir las constantes como para asignar valores a las variables, se debe tener en cuenta las formas vlidas de especicar sus valores, segn su tipo de dato. La tabla 2.4 a u resume las normas y muestra ejemplos para los tipos simples y para la clase String.
26
Tipo int
boolean char
Norma Valores en base decimal (la cifra tal cual), en octal (la cifra precedida de 0), o en hexadecimal (la cifra precedida de 0x o de 0X). Aadir a la cifra la letra L, n mayscula o minscula. u u Valores en coma ja o en coma otante. Aadir a la cifra la letra n F, mayscula o minscula. u u Valores en coma ja o en coma otante. Aadir a la cifra la letra n D, mayscula o minscula, o sin u u aadirla. n Slo dos valores posibles: true y o false. Caracteres visibles: Escribir el carcter entre comillas sima ples (b, 7, $). Caracteres especiales y no visibles: Usar las comillas simples con secuencia de escape (\,\n,. . . ). O, tambin, usar e la secuencia de escape seguida del cdigo octal o hexadecimal o que corresponda al carcter. a
Ejemplos int m = 75; int p = 024; int q = 0x36; long r = 22L; long s = 0x33L; float x = 82.5f;
boolean verdad = true; char letra = A; // salto de lnea: char salto = \n; // (cdigo en octal): a o char a1 = \141; // (en hexadecimal): a char a2 = \u0061;
String
27
Ambito de variables y constantes Las variables tienen un mbito (su lugar de existencia en el cdigo) y un tiempo de vida a o asociado a la ejecucin de las instrucciones de ese mbito. Un bloque de instrucciones entre o a llaves dene un mbito. Las variables declaradas en un mbito slo son visibles en l. Se a a o e crean y destruyen durante la ejecucin de las instrucciones de dicho mbito. o a Considrese el siguiente fragmento de cdigo: e o
{ int x1 = 10; { int x2 = 20; x1 = x2 * 2; } x2 = 100; x1++; // // // // inicio del 1${er}$ ambito x1 existe en el 1${er}$ mbito a inicio del 2o mbito a x2 existe en el 2o mbito a
// final del 2o ambito // incorrecto, x2 no existe en el 1${er}$ mbito a // final del 1${er}$ mbito a
Este cdigo contiene un error. Se han denido dos mbitos, y un mbito est anidado en o a a a el otro. La variable x1 se ha declarado en el mbito externo y existe tambin en el mbito a e a interno (que es parte del mbito externo). En cambio, la variable x2 se ha declarado en a el mbito interno y deja de existir cuando la ejecucin abandone ese mbito. Por ello, la a o a asignacin a dicha variable fuera de su mbito genera un error de compilacin. o a o Un programa que declara y muestra variables Como resumen de lo tratado en esta seccin, considrese el siguiente programa principal: o e
public static void main (String[] args) { final double PI = 3.141592; int contador = 0; boolean verdad = true; char letra = A; String modelo = "HAL 9000" ; [Link]( "PI = " + PI ); [Link]( "contador = " + contador ); [Link]( "verdad = " + verdad ); [Link]( "letra = " + letra ); [Link]( "modelo = " + modelo ); }
Este programa declara e inicializa variables de diferentes tipos y, luego, muestra en la consola los nombres y valores de las variables. En concreto, se obtiene la siguiente salida:
PI = 3.141592 contador = 0 verdad = true letra = A modelo = HAL 9000
En este programa, adems, se ha usado el operador + como operador que concatena a cadenas de caracteres (y no como operador aritmtico). As por ejemplo, en la instruccin: e , o [Link]( "PI = " + PI); se ha unido la cadena PI = con el valor de la constante . El resultado de la concatenacin es el dato que recibe el mtodo println y, por o e tanto, lo que se muestra en la salida del programa.
28
operador normal + * / %
operador reducido += = *= /= %=
operador auto++
2.5.4.
Operadores
Java proporciona los operadores bsicos para manipular datos de los tipos simples. As se a , dispone de operadores aritmticos, operadores de bits, operadores relacionales, operadores e lgicos (booleanos) y un operador ternario. En esta seccin se enumeran y describen los o o mismos. Operadores aritmticos e Para datos de tipo entero y de tipo real, se dispone de operadores aritmticos de suma, e resta, multiplicacin, divisin y mdulo (obtener el resto de la divisin entera). De todos o o o o ellos, existe una versin reducida, donde se combina el operador aritmtico y la asignacin, o e o que se puede usar cuando el resultado de la operacin se almacena en uno de los operandos. o Por ultimo, existen los operadores de autoincremento y autodecremento para sumar y restar una unidad. En la tabla 2.5 se muestran todos los operadores aritmticos. e El siguiente bloque de cdigo ilustra el uso de los operadores aritmticos. En los coo e mentarios de l nea se ha indicado el valor de la variable modicada despus de ejecutar la e instruccin de cada l o nea.
{ int i=2, j, k; j = i * 3; // resultado: j i++; // resultado: i j *= i; // resultado: j k = j \% i; // resultado: k i++; // resultado: i j /= i; // resultado: j double x=3.5, y, z; y = x + 4.2; // resultado: y x--; // resultado: x z = y / x; // resultado: z
= = = = = =
Operadores a nivel de bit Los operadores a nivel de bit realizan operaciones con datos enteros, manipulando individualmente sus bits. Existe un operador unario para la negacin (inversin bit a bit). o o Los dems son operadores binarios (requieren dos operandos) para realizar la conjuncin, la a o disyuncin, la disyuncin exclusiva o el desplazamiento de bits a la izquierda o a la derecha, o o
29
operacin o negacin (NOT) o conjuncin (AND) o disyuncin (OR) o disyuncin exclusiva (XOR) o desplazamiento de bits a la izquierda desplazamiento de bits a la derecha con signo desplazamiento de bits a la derecha sin signo
Tabla 2.6: Operadores a nivel de bit operacin o operandos iguales operandos distintos 1er operando mayor 1er operando menor 1er operando mayor 1er operando menor operador == != > < >= <=
que 2o operando que 2o operando o igual que 2o operando o igual que 2o operando
con signo o sin signo. Tambin existe la posibilidad de combinar las operaciones bit a bit y e la asignacin (operadores reducidos). o En la tabla 2.6 se muestran los operadores a nivel de bit. El siguiente bloque de cdigo ilustra el uso de los operadores bit a bit. En los comeno tarios de l nea se ha indicado el valor de cada variable, en binario, despus de ejecutarse la e instruccin de cada l o nea.
{ int a c = a c = a c = a c <<= c >>= = 18, b = 14, c; & b; | b; b; 2; 3; // // // // // // a c c c c c = = = = = = 10010 , b = 1110 10 11110 11100 1110000 1110
Operadores relacionales En Java, como en otros lenguajes de programacin, los operadores relacionales sirven o para comparar dos valores de variables de un mismo tipo y el resultado de la comparacin o es un valor de tipo booleano. En la tabla 2.7 se muestran los operadores relacionales. Considrese el siguiente bloque de cdigo como ejemplo del uso de los operadores relae o cionales:
{ int a = 18, b = 14;
30
operacin o negacin (NOT) o conjuncin (AND) o disyuncin (OR) o disyuncin exclusiva (XOR) o igualdad desigualdad
Tabla 2.8: Operadores lgicos o a false false true true b false true false true !a true true false false a&b false false false true a|b false true true true ab false true true false a == b true false false true a != b false true true false
: : : :
+ + + +
); ); ); );
Operadores lgicos o Los operadores lgicos (booleanos) proporcionan las habituales operaciones lgicas (neo o gacin, conjuncin, disyuncin. . . ) entre variables de tipo booleano o entre expresiones que o o o se evalen a booleano (verdadero o falso). u En la tabla 2.8 se muestran los operadores lgicos. En la tabla 2.9 se muestran sus tablas o de verdad. Todos los operadores lgicos son binarios, excepto la negacin (que es unario, y se escribe o o delante de la variable o expresin a negar). o En el caso de las operaciones AND y OR, existen dos versiones del operador: Si se usan los s mbolos &, |, siempre se han de evaluar los dos operandos para determinar el resultado de la operacin. o Si se usan los s mbolos &&, ||, puede conocerse el resultado segn el valor del primer u operando (y sin calcular o evaluar el segundo operando). En la tabla 2.10 se muestra el comportamiento de estos operadores. Se ha indicado con ??? cuando no es necesario evaluar el 2o operando de la expresin lgica. o o
31
Operador ternario El operador ternario ?: tiene la siguiente sintaxis: a ? b : c; Donde a es una expresin booleana, y b y c son valores o expresiones del mismo tipo. El o operador ternario devuelve un valor (y, por tanto, puede usarse en una asignacin). El valor o devuelto ser b si a se evala a verdadero, o ser c si a se evala a falso, tal como indica la a u a u tabla 2.11. Considrese el siguiente bloque de cdigo como ejemplo de su uso: e o
{ int a = 18, b = 14, c = 22, maximo; maximo = a>b ? a : b; // maximo = 18 maximo = maximo>c ? maximo : c; // maximo = 22
El ejemplo calcula el mayor valor de tres variables enteras y el resultado se almacena en la variable maximo. Para ello se usa el operador ternario dos veces. La primera vez se determina el mximo de las dos primeras variables. La segunda vez se determina el mximo a a de las tres variables, comparando el valor provisional del mximo con el valor de la ultima a variable. Para ampliar la descripcin del operador ternario y, adems, la explicacin del operador o a o &&, consideremos el siguiente bloque de cdigo: o
{ int a = 18, b = 14, c = 22; String s = ""; s = ( a>b && a>c ) ? "maximo = " +a : s+""; s = ( b>a && b>c ) ? "maximo = " +b : s+""; s = ( c>a && c>b ) ? "maximo = " +c : s+""; [Link](s);
Este segundo ejemplo tambin calcula el mayor valor de tres variables enteras y el resule tado lo muestra mediante un mensaje en la consola.
32
En esta ocasin, el resultado del operador ternario es una cadena de caracteres, que se o almacena en el objeto s de la clase String. Cuando la expresin booleana del operador se o evala a falso, no se aade nada a s (puesto que s+""; es concatenar la cadena vac Pero u n a). cuando se evala a verdadero, se asigna a s la cadena "maximo = "+c. u En cuanto a la evaluacin de las expresiones booleanas, hay que indicar que: o En la expresin ( a>b && a>c ) se evala (a>b) a verdadero y, entonces, es preciso o u evaluar (a>c), que es falso, siendo falso el resultado de la expresin completa. o En la expresin ( b>a && b>c ) se evala (b>a) a falso y, entonces, es falso el o u resultado de la expresin completa, sin necesidad de evaluar (b>c). o En la expresin ( c>a && c>b ) se evala (c>a) a verdadero y, entonces, es preo u ciso evaluar (c>b), que es verdadero, siendo verdadero el resultado de la expresin o completa. Por tanto, usando el operador &&, se han evaluado slo cinco de las seis comparaciones. o En cambio, si se hubiera usado el operador & se habr evaluado todas las comparaciones. an En consecuencia, los operadores && y || pueden ser ms ecientes que & y |, si el programador a elige con cuidado el orden de los componentes de una expresin booleana compleja. Adems, o a los operadores && y || proporcionan mayor seguridad frente a errores en ejecucin. o
2.5.5.
Conversin de tipos o
Existen situaciones en las que se realiza una conversin del tipo de un dato para asignarlo o a otro dato. Segn los casos, puede tratarse de una conversin automtica (impl u o a cita, hecha por el compilador) o expl cita, si la tiene que indicar el programador. La conversin automtica de tipos (promocin) se hace cuando los dos tipos implicados o a o en una asignacin son compatibles y el tipo destino tiene mayor capacidad de almacenamiento o que el tipo origen (valor o expresin evaluada en el lado derecho de la asignacin). Por o o ejemplo, en una asignacin de un valor byte a una variable int: o
{ int a; byte b = 7; a = b;
Si no se da la anterior situacin, y se requiere una conversin, el programador debe o o indicarlo expl citamente mediante una proyeccin o casting. La conversin se indica ano o teponiendo el nombre del tipo, entre parntesis, a la expresin a convertir. As por ejemplo, e o , en una asignacin de una expresin de tipo int a una variable byte: o o
{ int a = 7, c = 5; byte b; b = (byte) (a + c);
El valor de la suma de las variables a y c es de tipo int, y se convierte a tipo byte antes de asignarlo a la variable b. Si no se hiciera el casting del resultado (es decir, si la instruccin o fuera: b = a + c), el compilador genera el siguiente error:
2.5. Tipos de datos possible loss of precision found : int required: byte
33
Tambin se generar el mismo error si la instruccin fuera: b = (byte) a + c, ya que e a o la conversin es ms prioritaria que la suma, y en consecuencia se sumar un valor byte y o a a un valor int. Por tanto, son necesarios los parntesis que encierran la expresin a convertir. e o Consideremos otro ejemplo donde se usa el casting:
{ char ch1 = a; char ch2 = (char) ((int)ch1 + 3); [Link](""+ch2+ch1);
Al ejecutarse el cdigo de este ejemplo, se asigna el valor d la variable ch2. Por ello, o a mediante println, se escribe en la consola la cadena da. Obsrvese la expresin que asigna valor a ch2. En el lenguaje C se podr haber escrito e o a simplemente: ch2 = ch1 + 3; y la expresin se hubiera evaluado usando el cdigo ASCII o o de la letra apara obtener el de la letra d. En Java, sin embargo, el compilador rechaza esa instruccin por incompatibilidad entre los tipos, y para llevarla a cabo son necesarias dos o operaciones expl citas de conversin de tipos: o casting de ch1 a int, para poder sumarlo con el nmero 3. u casting del resultado de la suma a char, para poder asignarlo a la variable ch2. La conversin tambin se puede realizar con tipos no primitivos (clases). Este caso, que o e tiene implicaciones de gran trascendencia en la programacin orientada a objetos, se ver con o a detalle en cap tulos posteriores.
2.5.6.
Vectores
Los vectores o arrays son estructuras de datos que almacenan un nmero jo de elementos u de informacin, siendo estos elementos del mismo tipo (o de la misma clase, si se trata de o objetos). Vectores unidimensionales La declaracin de vectores en Java tiene la siguiente sintaxis: o identicadorTipo [] nombreVector; Donde: identicadorTipo ha de ser el nombre de un tipo conocido. nombreVector es el identicador o nombre que se le da al vector. Los elementos del vector son del tipo identicadorTipo. Los corchetes indican que se declara un vector, y no una variable de un solo elemento. Por ejemplo, una declaracin vlida de un vector de enteros: o a
34 int [] diasMeses;
Existe una segunda forma de declarar vectores. En esta alternativa, los corchetes se sitan u despus del nombre del vector: identicadorTipo nombreVector [];. Sin embargo, en este texto e no usaremos esta segunda forma, por homogeneidad del cdigo. o Para usar variables vector son necesarias dos acciones: la declaracin y la instanciacin o o de los vectores. En la declaracin de una variable vector simplemente se dene una referencia, un nombre o para el vector, pero no se reserva espacio de almacenamiento para sus elementos y, por tanto, no es obligatorio especicar su dimensin. o El tamao del vector quedar jado cuando se haga la instanciacin de la variable, del n a o siguiente modo: nombreVector = new identicadorTipo [tamao]; n Donde: new es el operador de instanciacin. o tamao es un nmero natural que ja el nmero de elementos. n u u Por ejemplo, la instanciacin del anterior vector de enteros: o
diasMeses = new int[12];
Declaracin e instanciacin pueden tambin realizarse simultneamente. Siguiendo con o o e a el mismo ejemplo, se har mediante la instruccin: a o
int [] diasMeses = new int[12];
Tambin se puede declarar el vector con inicializacin de sus valores, conforme a la sigue o iente sintaxis: identicadorTipo [] nombreVector = listaValores ; Para el vector diasMeses, la declaracin e instanciacin ser o o a:
int[] diasMeses = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
La enumeracin expl o cita de los valores del vector supone, impl citamente, la reserva de memoria necesaria para almacenarlos (del mismo modo que cuando se usa new, el operador de instanciacin). o Los elementos de los vectores se numeran empezando por cero. Si el tamao de un vector n es N, los ndices para acceder a sus elementos estn en el rango [0..N-1]. En los vectores a de Java, adems, se dispone de un atributo, llamado length, que almacena el tamao del a n vector. Los elementos de los vectores se inicializan al valor por defecto del tipo correspondiente (cero para valores numricos, el carcter nulo para char, false para boolean, null para String e a y para referencias). Considrese el siguiente ejemplo de declaracin, instanciacin e inicializacin de un vector: e o o o
2.6. Estructuras de control char[] vocales = new char[5]; // inicializar componentes vocales[0] = a; vocales[1] = e; vocales[2] = i; vocales[3] = o; vocales[4] = u; // mostrar tamao del vector (escribe: Tamao = 5) n n [Link]( "Tamao = " + [Link] ); n
35
Vectores multidimensionales Los vectores pueden tener varias dimensiones. La declaracin de vectores o arrays multio dimensionales (matrices) se hace de modo similar al caso de los vectores de una dimensin. o Slo hay que aadir un par de corchetes para cada dimensin que tenga la matriz o vector o n o multidimensional. Por ejemplo, una declaracin e instanciacin vlida de una matriz de enteros de tres las o o a y tres columnas ser la siguiente: a
int[][] mat3x3 = new int[3][3];
Otra forma equivalente de declarar e instanciar la misma matriz, pero reservando memoria independiente para cada la, ser la siguiente: a
int[][] mat3x3 = new int[3][]; mat3x3[0] = new int[3]; mat3x3[1] = new int[3]; mat3x3[2] = new int[3];
Por ultimo, la declaracin de la matriz con inicializacin de sus valores se har del o o a siguiente modo:
int[][] mat3x3 = { {1,2,3}, {4,5,6}, {7,8,9} }
2.6.
Estructuras de control
En esta seccin, trataremos las diferentes estructuras de control disponibles en el lenguaje o Java. En primer lugar, se describen las estructuras condicionales, que permiten la ejecucin o de bloques de cdigo dependiendo de la evaluacin de condiciones. A continuacin, se tratan o o o las estructuras de repeticin o iteracin, que permiten la ejecucin repetida de bloques de o o o cdigo. Finalmente, se describe brevemente el uso de las sentencias de salto. o
2.6.1.
Estructuras condicionales
Las sentencias de seleccin permiten establecer bifurcaciones en el cdigo, dando lugar o o a la ejecucin de diferentes bloques de instrucciones en funcin de la evaluacin de alguna o o o condicin lgica. o o
36
Sentencia if La estructura condicional ms importante en Java (y en otros lenguajes) es la sentencia a if. La sintaxis de esta sentencia es la siguiente: if ( condicin ) sentencias1; o else sentencias2; Donde: condicin es una expresin lgica, que se evala a verdadero o falso. o o o u sentencias1 es el bloque de instrucciones que se ejecuta si la condicin es verdadera. o sentencias2 es el bloque de instrucciones que se ejecuta si la condicin es falsa. o La presencia de la clusula else es opcional (no se usar si no se requiere ejecutar nada a a cuando la condicin del if no se cumple). Si el bloque sentencias1 o sentencias2 consta de o una unica sentencia, entonces las llaves del bloque correspondiente son opcionales. El siguiente bloque de cdigo muestra un posible uso de la sentencia de seleccin: o o
{ int a = 18, b = 14, c int maximo, minimo; if ( a>b ) { maximo = a; minimo = b; } else { maximo = b; minimo = a; } // if ( maximo<c ) { maximo = c; } // if ( minimo>c ) { minimo = c; } // = 22;
En este ejemplo, dados tres nmeros enteros, se obtienen los valores mximo y m u a nimo. Se indican, mediante comentarios al cdigo, los valores obtenidos despus de ejecutarse cada o e sentencia if. Se han utilizado tres sentencias condicionales: En la 1a sentencia if se comparan los valores de a y b, ejecutndose el 1er bloque de a instrucciones al cumplirse la condicin (esta sentencia if tiene clusula else pero su o a bloque de instrucciones, para los valores de este ejemplo, no se ejecuta). Con la 2a sentencia if se determina el valor mximo (esta vez no hay clusula else a a porque no se necesita hacer nada si no se cumple la condicin). o Con la 3a sentencia if se determina el valor m nimo (tampoco aqu se necesita clusula a else).
37
En Java, como en otros lenguajes, las sentencias if-else pueden concatenarse en una secuencia de vericacin de condiciones. Como ejemplo, considrese el siguiente cdigo: o e o
{ double nota = ... ; // llamar a un mtodo que calcule la nota e // mostrar la palabra correspondiente al valor de la nota if ( nota >= 9 ) [Link]( "Sobresaliente" ); else if ( nota >= 7 ) [Link]( "Notable" ); else if ( nota >= 5 ) [Link]( "Aprobado" ); else [Link]( "Suspenso" );
En este ejemplo, se escribe por consola la palabra Sobresaliente, Notable, Aprobado o Suspenso, segn la variable nota tenga un valor en los rangos [9, 10], [7, 9[, [5, 7[, u o [0, 5[, respectivamente. La sentencia if permite, con construcciones como la anterior, discriminar entre un nmero u amplio de condiciones y seleccionar los correspondientes bloques de instrucciones a ejecutar. Sin embargo, si el nmero de alternativas o valores a comprobar es extenso, resulta ms u a recomendable usar la sentencia switch en su lugar. Sentencia switch La sentencia de seleccin switch evala una expresin que devuelve un valor en funcin o u o o del cual se selecciona un determinado bloque de instrucciones a ejecutar. La instruccin o switch tiene la siguiente sintaxis: switch ( expresin ) { o case valor1: sentencias1; break; case valor2: sentencias2; break; ... case valorN: sentenciasN; break; default: sentencias por defecto; } Donde: Los valores de la lista {valor1, valor2, . . . , valorN} han de ser valores del mismo tipo que el devuelto por la expresin evaluada. o Cada case seala un punto de entrada en el bloque de cdigo de la sentencia switch. n o Cada break seala un punto de salida en el bloque de cdigo de la sentencia switch. n o La clusula default es opcional en la sentencia switch. a La expresin evaluada debe ser de un tipo simple enumerable (enteros, caracteres, booleanos). o No se permite usar nmeros reales. u Si la expresin del switch devuelve: o Un cierto valor-i para el que existe un case, entonces se ejecuta su bloque de instrucciones sentencias-i. Si el case termina con una sentencia break, slo se ejecuta su bloque. o Pero si no hubiera break, se ejecutar el bloque sentencias-(i+1) y siguientes, hasta an encontrar algn break o acabar el bloque del switch. u
38
Un valor que no corresponde al de ningn case, entonces se ejecuta, si existe, el bloque u de instrucciones sentencias por defecto, etiquetado como default. Como ejemplo, consideramos el siguiente cdigo switch: o
{ int contadorA ... // declarar e inicializar contadores char vocal = ... ; // llamar a un mtodo que obtenga una vocal e // incrementar el contador correspondiente a la vocal obtenida switch ( vocal ) { case A: case a: contadorA++; break; case E: case e: contadorE++; break; case I: case i: contadorI++; break; case O: case o: contadorO++; break; case U: case u: contadorU++; break; default: contadorNoVocal++; }
En este ejemplo, se actualizan los valores de unas variables enteras (llamadas contadorA, contadorE, etc.) que llevan la cuenta de las vocales obtenidas mediante la ejecucin de o algn mtodo. Supondremos que la llamada al mtodo, previa a la sentencia switch, devuelve u e e casi siempre vocales. Suponemos tambin que las variables enteras han sido correctamente e declaradas e inicializadas en otro bloque de cdigo (antes de ejecutarse el cdigo mostrado). o o La sentencia switch del ejemplo evala una variable de tipo char, llamada vocal. Si la letra u es una vocal, se entra en el case correspondiente, incrementndose el contador apropiado. a Obsrvese que el contador de cada vocal se incrementa tanto si la letra est en maysculas e a u como en minsculas. Para el caso de que la letra no sea vocal, se ha especicado un caso u default donde se actualizar un contador, contadorNoVocal, que lleva la cuenta de los a caracteres que no sean vocales. Hay que llamar la atencin sobre la importancia de incluir la instruccin break al nal o o de cada bloque case. Si se omite algn break en el ejemplo anterior, los contadores no se u incrementan correctamente. Por ejemplo, si se omite el primer break, dejando el cdigo de o la siguiente forma:
switch case case case case case ... } ( vocal ) { A: a: contadorA++; E: e: contadorE++; break; I:
El funcionamiento no ser el deseado. Al recibir una vocal E, se incrementa el contadorE. a Pero al recibir una vocal Ase incrementan dos contadores: contadorA y contadorE, dado que se ejecutan todas las instrucciones hasta llegar a un break. En consecuencia, la variable contadorE almacenar la cuenta de vocales Ay E, y no slo las Ecomo se pretend a o a.
39
2.6.2.
Estructuras de repeticin o
Las sentencias de repeticin permiten la ejecucin de un bloque de instrucciones de o o modo repetitivo. El bloque de instrucciones puede ejecutarse un nmero de veces jado de u antemano, o un nmero de veces variable en funcin del cumplimiento de alguna condicin. u o o En ambos casos, la estructura debe construirse garantizando que el nmero de ejecuciones del u bloque sea nito. De lo contrario, se entrar en un bucle innito y el programa en ejecucin a o no saldr nunca del mismo, y nunca terminar a a. Sentencia while La sintaxis de esta sentencia es la siguiente: while ( condicin ) { sentencias; } o Donde: condicin es una expresin lgica, que se evala a verdadero o falso. o o o u sentencias es el bloque de instrucciones que se ejecutar si la condicin es verdadera. a o Si el bloque slo contiene una instruccin, entonces las llaves son opcionales. o o La condicin se evala antes de entrar en el bloque. Por tanto, puede darse el caso de que o u el bloque no se ejecute ni una sola vez (cuando la condicin sea falsa inicialmente). Cada vez o que condicin sea verdadera, se ejecutar una iteracin del bloque sentencias, y se volver a o a o a evaluar la condicin. o Para que el bucle sea nito, ha de garantizarse que, en algn momento, la condicin u o dejar de cumplirse. Para ello, se debe incluir en el bloque sentencias alguna instruccin que a o modique alguna variable que forme parte de la condicin a evaluar. o Como ejemplo de uso de la estructura while, considrese el siguiente bloque de cdigo e o que calcula las sumas de los nmeros pares e impares almacenados en un vector de enteros: u
{ int[] a = {3, 7, 12, 22, 9, 25, 18, 31, 21, 14, 45, 2}; int sumaPares = 0, sumaImpares = 0; int i = 0; while ( i < [Link] ) { if ( a[i] \% 2 == 0 ) sumaPares += a[i]; else sumaImpares += a[i]; i++; } [Link]( "sumaPares = " + sumaPares); [Link]( "sumaImpares = " + sumaImpares);
El nmero de iteraciones del bucle while de este ejemplo se controla con la variable i. u Esta variable: Se inicializa a cero para acceder a la 1a componente del vector a en la 1a iteracin. o Se incrementa en una unidad (i++) al nal de cada iteracin (as se accede a como ponentes sucesivas del vector, y tambin se actualiza adecuadamente el nmero de e u repeticiones).
40
Y se compara con la longitud del vector (i < [Link]) de manera que el bucle concluye cuando la variable se iguala a esa longitud, porque entonces ya se han procesado todas las componentes del vector. Otro ejemplo de estructura while se muestra en el siguiente mtodo que comprueba si un e vector es capica: u
// mtodo capica e u public static boolean capicua (int[] v) { int i = 0; int j = [Link]-1; while ( i<j && v[i]==v[j] ) { i++; j--; } return i>=j; } // programa principal que usa el mtodo e public static void main (String[] args) { int[] v = { 1, 2, 3, 3, 2, 1 }; [Link]( "El vector " + ( capicua(v) ? "si " : "es capicua " ); }
"no
" ) +
En el mtodo capicua, el bucle while tiene una condicin doble: e o Verica que los dos ndices de exploracin del vector no se han cruzado o igualado o (i<j). Verica la igualdad de los componentes apuntados por estos ndices (v[i]==v[j]). En este caso, el bloque de instrucciones del while se limita a actualizar convenientemente las dos variables ndice (i++; j--;). Si el vector es capica, el bucle while concluir cuando u a los ndices se crucen o igualen, es decir, cuando (i>=j) sea verdadera. Obsrvese que el e resultado de esta expresin booleana es lo que devuelve el mtodo, mediante la instruccin o e o return. En el mtodo main, la invocacin del mtodo capicua se ha escrito dentro de una ine o e struccin println y, concretamente, formando parte del operador ternario ?: al aprovechar el o hecho de que el resultado del mtodo es un valor boolean. e Sentencia do-while Es una variante de la sentencia while. Tiene la siguiente sintaxis: do { sentencias; } while ( condicin ); o La unica diferencia respecto a la estructura while radica en cuando se verica la condicin o lgica para continuar o abandonar el bucle. En la sentencia do-while, la condicin se verica o o despus de ejecutar el bloque sentencias. Por tanto, el contenido del bucle se ejecuta por lo e menos una vez (a diferencia del while donde pod no ejecutarse ninguna vez). a Como ejemplo de la estructura do-while, consideremos otra versin del mtodo que como e prueba si un vector es capica: u
2.6. Estructuras de control public static boolean capicua2 (int[] v) { int i = -1; int j = [Link]; do { i++; j--; } while ( i<j && v[i]==v[j] ); return i>=j; }
41
La unica diferencia se encuentra en la inicializacin de las variables o ndice, al tener en cuenta que, dentro del do-while, esas variables se actualizan antes de vericar la condicin o del bucle. Sentencia for Java, como otros lenguajes, proporciona tambin una sentencia de repeticin llamada e o for. Esta estructura tiene la siguiente sintaxis: for (inicializacin; condicin; iteracin) { sentencias; } o o o Donde se usa una variable que habitualmente permite jar el nmero de iteraciones, u estableciendo: El valor inicial de dicha variable (en la asignacin llamada inicializacin en el esquema). o o La modicacin de esa variable al nal de cada iteracin (en la expresin llamada o o o iteracin). o El valor nal de esa variable (en la expresin booleana llamada condicin). o o Para cada valor de la variable de control del bucle, desde su valor inicial hasta su valor nal, se ejecuta una iteracin del bloque de sentencias. o La sentencia for es la recomendada cuando se conoce de antemano el nmero de iteraciones u del bucle. Esta situacin es muy frecuente cuando se trabaja con vectores. o Considrese el siguiente bloque de cdigo: e o
{ int[] v1 = { 16,22,31,41,65,16,77,89,19 }; int[] v2 = { 12,27,39,42,35,63,27,18,93 }; int[] v3 = new int[[Link]]; int[] v4 = new int[[Link]]; for ( int i=0; i<[Link]; i++ ) { v3[i] = v1[i] + v2[i]; v4[i] = v1[i] - v2[i]; }
En este ejemplo, dados dos vectores, v1 y v2, se obtienen otros dos vectores: v3, vector suma, componente a componente, de los anteriores; y v4, vector resta. La variable i del bucle for recorre el rango [0..[Link][. En consecuencia, dadas las sentencias dentro del bucle for, todas las componentes de los vectores v1 y v2 se suman y restan, almacenndose en v3 a y v4, respectivamente. Vase otro ejemplo, esta vez con matrices (vectores bidimensionales): e
42 {
int[][] m1 = { {16,22,31}, {41,65,16}, {77,89,19} }; int[][] m2 = { {12,27,39}, {42,35,63}, {27,18,93} }; int[][] m3 = new int[[Link]][m1[0].length]; for (int i=0; i<[Link]; i++) for (int j=0; j<m1[0].length; j++) m3[i][j] = m1[i][j] + m2[i][j];
En este ejemplo, dadas dos matrices, m1 y m2, se obtiene m3, matriz suma. Para sumar componente a componente las dos matrices, se requieren dos bucles for con anidamiento: el bucle controlado por la variable j es interno a (est anidado en) el bucle controlado por la a variable i.
2.7.
Sentencias de salto
En este apartado se tratan brevemente las instrucciones que Java proporciona para saltar de un punto del cdigo a otro. Son las instrucciones break, continue y return. o Sentencia return La sentencia return permite devolver el resultado obtenido al ejecutarse un mtodo e (como ya se ha visto en ejemplos anteriores). Tambin puede situarse al nal del programa e principal, puesto que el main es tambin un mtodo. e e En el cdigo de un mtodo pueden aparecer varias instrucciones return. Sin embargo, en o e cada ejecucin del mtodo, slo una de las instrucciones return se ejecutar, dado que la o e o a misma siempre supone la conclusin del mtodo. o e Como ejemplo, vase la siguiente versin del mtodo que verica si un vector es capica: e o e u
public static boolean capicua3 (int[] v) { int i = 0; int j = [Link]-1; while ( i<j ) { if ( v[i]!=v[j] ) return false; i++; j--; } return true; }
Sentencia break La sentencia break ya se ha visto en su uso para interrumpir la ejecucin de una sentencia o switch. En esa situacin, la ejecucin del break supone saltar a la primera instruccin que o o o est justo despus del nal de la estructura switch. e e Adems, break puede usarse dentro de los bucles para salir de los mismos sin vericar la a condicin de nalizacin del bucle. Considrese el siguiente ejemplo: o o e
public static int[] dividir_vectores (int[] v1, int[] v2) { int[] v3 = new int[[Link]]; for (int i=0; i<[Link]; i++) { if ( v2[i]==0 ) {
2.8. Ejercicios resueltos [Link]( "Vectores no divisibles:" + "un divisor es cero" ); break;
43
} return v3;
Este mtodo obtiene un vector de enteros resultante de dividir, componente a compoe nente, los dos vectores de enteros que recibe como argumentos. Para evitar una divisin por cero, en el caso de que algn elemento del segundo vector sea o u cero, se ha incluido una sentencia break para cancelar la operacin. La ejecucin del break o o causa la salida del bucle for, con independencia del valor de i, la variable del bucle. Esta solucin dista de ser la mejor, pero se presenta para ilustrar este posible uso de la sentencia o break. Sentencia continue La sentencia continue sirve, dentro de bucles, para salir anticipadamente de la iteracin o del bucle y continuar con la ejecucin de la siguiente iteracin del mismo bucle. o o Veamos su uso con una variante del anterior ejemplo de mtodo que divide dos matrices: e
public static int[][] dividir2_matrices (int[][] m1, int[][] m2) { int[][] m3 = new int[[Link]][m1[0].length]; for (int i=0; i<[Link]; i++) { for (int j=0; j<m1[0].length; j++) { if ( m2[i][j]==0 ) continue; m3[i][j] = m1[i][j] / m2[i][j]; } } return m3; }
En este ejemplo, cuando se encuentra una componente nula de la matriz m2, se interrumpe la iteracin actual (de modo que no se ejecuta la instruccin m3[i][j]=m1[i][j]/m2[i][j]; o o evitndose el error de la divisin por cero) y se pasa a la siguiente iteracin del bucle (es a o o decir, se incrementa la variable j). Las instrucciones break y continue se pueden usar tambin con etiquetas que indican e los lugares del cdigo a donde saltar. Sin embargo, no se recomiendan estos usos dado que o dicultan seriamente la legibilidad y comprensin del cdigo fuente. o o
2.8.
2.8.1.
Ejercicios resueltos
Enunciados
public static void main (String[] args) { int[] a = { 3, 7, 12, 22, 9, 25, 18, 31, 21, 14, 45, 2 estadistica_pares_impares(a); }
44
Escribir el cdigo del mtodo estadistica pares impares para que realice los siguo e ientes clculos con las componentes del vector de nmeros enteros que recibe como a u argumento: Suma de las componentes cuyo valor sea un nmero par. u Suma de las componentes cuyo valor sea un nmero impar. u Conteo de las componentes cuyo valor sea un nmero par. u Conteo de las componentes cuyo valor sea un nmero impar. u Valor mximo de las componentes cuyo valor sea un nmero par. a u Valor mximo de las componentes cuyo valor sea un nmero impar. a u Adems, el mtodo debe mostrar, mediante mensajes en la consola, los resultados a e obtenidos. 2. Dado el siguiente programa principal:
public static void main (String[] args) { char[] v = { a, e, A, q, i, U, a, E, p, O, u, A, i, e }; estadistica_vocales(v); }
Escribir el cdigo del mtodo estadistica vocales para que realice los siguientes o e clculos con las componentes del vector de caracteres que recibe como argumento: a Nmero de apariciones de cada vocal (sin distinguir maysculas y minsculas). u u u Nmero de apariciones de letras que no sean vocales. u Adems, el mtodo debe mostrar, mediante mensajes en la consola, los resultados a e obtenidos. 3. Implementacin de mtodos para realizar operaciones con matrices de nmeros enteros. o e u a) Escribir el cdigo de un mtodo que devuelva una matriz de nmeros aleatorios, o e u y que reciba como argumentos: el nmero de las, el nmero de columnas, y el u u rango de valores permitidos (rango al que deben pertenecer los nmeros aleatorios u que se elijan como valores). b) Implementar dos mtodos que devuelvan, respectivamente, el nmero de las y el e u nmero de columnas de una matriz dada. u c) Implementar tres mtodos que veriquen: e Si dos matrices tienen las mismas dimensiones. Si una matriz es cuadrada. Si una matriz es simtrica. e Se recomienda usar los mtodos denidos en el ejercicio anterior. e d ) Escribir el cdigo de un mtodo que devuelva una matriz que sea la suma de dos o e matrices dadas, previa comprobacin de que son sumables (en caso contrario, se o devolver una matriz con todos los valores a cero). a
45
e) Escribir el cdigo de un mtodo que devuelva una matriz que sea el producto o e de dos matrices dadas, previa comprobacin de que son multiplicables (en caso o contrario, devolver una matriz con todos los valores a cero). f ) Implementar un mtodo que muestre en la consola los valores de una matriz dada. e g) Escribir un mtodo main donde se invoquen todos los mtodos anteriores de modo e e que se compruebe su funcionamiento.
2.8.2.
1.
Soluciones
public class Estadistica { public static void estadistica_pares_impares (int[] a) { int sumaPares = 0, contPares = 0, mayorPar = 0; int sumaImpares = 0, contImpares = 0, mayorImpar = 0; boolean pp = true, pi = true; int i = 0; while ( i < [Link] ) { if ( a[i] \% 2 == 0 ) { sumaPares += a[i]; contPares++; if ( pp || a[i] > mayorPar ) mayorPar = a[i]; pp = false; } else { sumaImpares += a[i]; contImpares++; if ( pi || a[i] > mayorImpar ) mayorImpar = a[i]; pi = false; } i++; } [Link]("sumaPares= " + sumaPares); [Link]("sumaImpares= " + sumaImpares); [Link]("contPares= " + contPares); [Link]("contImpares= " + contImpares); [Link]("mayorPar= " + mayorPar); [Link]("mayorImpar= " + mayorImpar); } } public class Estadistica { public static void estadistica_vocales (char[] vocales) { int[] contadores = { 0, 0, 0, 0, 0, 0 }; char[] letras = { a, e, i, o, u, x }; for (int i=0; i<[Link]; i++) { switch ( vocales[i] ) { case a: case A: contadores[0]++; break; case e: case E: contadores[1]++; break; case i: case I: contadores[2]++; break; case o: case O: contadores[3]++; break; case u:
2.
46
Cap tulo 2. Introduccin al lenguaje Java o case U: contadores[4]++; break; default: contadores[5]++;
} } for (int i=0; i<[Link]; i++) [Link]("contador[" + letras[i] + " ]= " + contadores[i]);
3.
a)
public class Matriz { ... public static int generar_aleatorio (int min, int max) { return min+(int)([Link]()*(max-min)); } public static int[][] crear_matriz (int f, int c, int min, int max) { int[][] m = new int[f][c]; for (int i=0; i<f; i++) for (int j=0; j<c; j++) m[i][j] = generar_aleatorio(min, max); return m; } ... } public static int filas (int[][] m) { return [Link]; } public static int columnas (int[][] m) { return m[0].length; } public static boolean mismas_dimensiones (int[][] m1, int[][] m2) { return filas(m1)==filas(m2) && columnas(m1)==columnas(m2); } public static boolean es_cuadrada (int[][] m) { return filas(m)==columnas(m); } public static boolean es_simetrica (int[][] m) { if ( !es_cuadrada(m) ) return false; for (int i=0; i<filas(m); i++) for (int j=0; j<i; j++) if ( m[i][j]!=m[j][i] ) return false; return true; } public static int[][] sumar_matrices (int[][] m1, int[][] m2) { int[][] m3 = new int[filas(m1)][columnas(m1)]; if ( mismas_dimensiones(m1,m2) ) { for (int i=0; i<filas(m1); i++) for (int j=0; j<columnas(m1); j++) m3[i][j] = m1[i][j] + m2[i][j];
b)
c)
d)
2.8. Ejercicios resueltos } else { [Link]("distintas dimensiones: " ); [Link]("matrices no sumables" ); } return m3;
47
e)
public static int[][] producto_matrices (int[][] m1, int[][] m2) { int[][] m3 = new int[filas(m1)][columnas(m2)]; if ( columnas(m1)==filas(m2) ) { for (int i=0; i<filas(m1); i++) for (int j=0; j<columnas(m2); j++) for (int k=0; k<columnas(m1); k++) m3[i][j] += m1[i][k] * m2[k][j]; } else { [Link]( "matrices no multiplicables" ); } return m3; } public static void mostrar_matriz (int[][] m) { for (int i=0; i<filas(m); i++) { for (int j=0; j<columnas(m); j++) [Link](m[i][j]+" "); [Link](""); } [Link](""); } public static void main (String[] args) { int[][] m1 = crear_matriz(5,4,1,100); int[][] m2 = crear_matriz(5,4,1,100); int[][] m3 = crear_matriz(4,7,100,1000); mostrar_matriz(m1); mostrar_matriz(m2); mostrar_matriz(m3); int[][] m4 = sumar_matrices(m1,m2); mostrar_matriz(m4); int[][] m5 = sumar_matrices(m2,m3); mostrar_matriz(m5); int[][] m6 = crear_matriz(3,4,1,10); int[][] m7 = crear_matriz(4,3,1,10); int[][] m8 = producto_matrices(m6,m7); mostrar_matriz(m6); mostrar_matriz(m7); mostrar_matriz(m8); int[][] m9 = crear_matriz(5,5,1,10); mostrar_matriz(m9); if ( es_cuadrada(m9) ) [Link]("matriz cuadrada"); int[][] m10 = { {1,2,3,5}, {2,5,7,8}, {3,7,4,9}, {5,8,9,1} }; mostrar_matriz(m10); if ( es_simetrica(m10) ) [Link]("matriz simtrica"); e
f)
g)
48 }
2.9.
Ejercicios propuestos
Implementacin de mtodos para realizar operaciones de bsqueda de palabras (vectores o e u de caracteres) en una sopa de letras (matriz de caracteres). 1. Escribir el cdigo de un mtodo que devuelva una palabra (un vector de caracteres). o e El mtodo debe generar un vector de caracteres aleatorios, recibiendo como argue mentos: la longitud del vector y el rango de valores permitidos (rango al que deben pertenecer los caracteres aleatorios que se elijan como valores). 2. Escribir el cdigo de un mtodo que devuelva una sopa de letras (una matriz de o e caracteres). El mtodo debe generar una matriz de caracteres aleatorios, recibiendo como argumene tos: el nmero de las, el nmero de columnas, y el rango de valores permitidos (rango u u al que deben pertenecer los caracteres aleatorios que se elijan como valores). 3. Escribir el cdigo de mtodos que devuelvan: o e El nmero de las de una sopa de letras dada. u El nmero de columnas de una sopa de letras dada. u 4. Escribir el cdigo de mtodos para: o e Obtener (leer) el carcter de una casilla de una sopa de letras dada. a Establecer (escribir) el carcter de una casilla de una sopa de letras dada. a 5. Escribir el cdigo de un mtodo que compruebe si unas coordenadas dadas (nmeros o e u de la y columna) corresponden a una casilla vlida de una sopa de letras. a 6. Escribir el cdigo de un mtodo que compruebe si una palabra dada est en una detero e a minada posicin (descrita por las coordenadas de inicio de bsqueda y las coordenadas o u de sentido o direccin de la bsqueda) de una sopa de letras. o u 7. Escribir el cdigo de un mtodo que muestre en la consola los valores de una sopa de o e letras. 8. Escribir el cdigo de un mtodo main donde se genere una sopa de letras, se genere o e una palabra a buscar, y se realice la bsqueda de todas las apariciones de la palabra u en la sopa de letras, listando por pantalla las coordenadas de inicio y de direccin de o cada una de las apariciones.
Cap tulo 3
La estructura anterior permite observar dos partes claramente diferenciadas en las que se debe dividir una clase. En primer lugar, la declaracin de atributos de la clase y, en segundo o 49
50
lugar, la denicin de los mtodos. o e Los atributos: Sintcticamente son exactamente iguales a las declaraciones de variables a vistas en el cap tulo anterior. Sin embargo, su signicado en el diseo de programas n orientados a objetos es distinto. Las clases sirven para instanciar objetos y, durante todo el tiempo de vida de un objeto, sus atributos prevalecen y su valor representa el estado de dicho objeto en un momento dado. Conviene tener presente dos aspectos importantes de los atributos: Sintcticamente pueden ser declarados en cualquier parte dentro de la clase, sin a embargo, es conveniente hacerlo siempre al principio por estilo de programacin. o Los atributos pueden tener modicadores de acceso:
modificador_de_acceso tipo_de_dato nombre_de_atributo;
siendo este modicador una palabra reservada (o nada) que caracteriza el modo de acceso a dicho atributo desde fuera de la clase. Es conveniente utilizar el modicador que hace privado este acceso desde fuera aunque por simplicidad omitiremos esto hasta que veamos dichos modicadores (3.4.1). Los mtodos: Sintcticamente se parecen mucho a funciones o procedimientos de otros e a lenguajes, utilizados para encapsular partes de cdigo que tienen entidad propia como o para disearlas de manera independiente del resto del cdigo. Estas subrutinas (procedn o imientos o mtodos) reciben una serie de argumentos como entrada y pueden devolver e un valor. La diferencia con respecto a subrutinas de otros lenguajes es fundamental dado que los mtodos representan el comportamiento de los objetos. Los mtodos, por e e regla general, acceden a los atributos de la instancia para: consultar los atributos del objeto, tanto para simplemente devolver el valor de un atributo (dado que lo usual es que slo sean accesibles desde dentro de la propia o clase) como para devolver valores derivados de algn clculo que involucra a los u a atributos; y, modicar los atributos del objeto (los atributos no deber nunca modicarse de an otra forma que no fuera a travs de un mtodo de la clase 3.4). e e As pues, los mtodos constituyen un mecanismo para acceder a los atributos de un ob e jeto. La ejecucin de una aplicacin orientada a objetos puede verse como un conjunto o o de objetos que se crean y destruyen. Una vez creados los objetos stos van cambiando e su estado (a travs de sus mtodos) mientras dura su tiempo de vida. e e A continuacin se irn desarrollando las ideas anteriores a travs de la descripcin sintctio a e o a ca y semntica de los elementos que proporciona el lenguaje Java. a La denicin de las clases comienza por la palabra reservada class seguida de un identio cador vlido para el nombre de la clase. Es una prctica bastante aceptada que este nombre a a comience con una letra mayscula por lo que se recomienda seguir esta costumbre. Todo el u cuerpo de la clase est englobado en un bloque de cdigo entre llaves. La declaracin de a o o atributos suele estar al comienzo de la misma aunque la sintaxis de Java da libertad para declararlas donde se desee. Un ejemplo de denicin de clase, en el cual no se han declarado o mtodos, es el siguiente: e
class Esfera { double radio; }
51
Una vez declarada la clase, se puede utilizar como un nuevo tipo de dato para realizar dos cosas: declarar variables referencia o simplemente referencias, y crear objetos de dicha clase. Mediante una clase se dene un nuevo tipo de dato. A los objetos tambin se les suele e denominar instancias de la clase, instancias del tipo de dato o simplemente instancias. Por eso, tambin se suele decir que crear objetos es instanciar la clase. e La declaracin de una referencia del tipo denido por la clase se realiza de la misma o manera que se declaran variables simples, por ejemplo,
Esfera miesfera;
donde miesfera es una variable referencia que servir para apuntar o tener acceso a un a objeto o instancia de la clase Esfera. El concepto de referencia es sumamente importante para comprender el resto del material. Una referencia es lo ms parecido a un puntero en a otros lenguajes como el lenguaje C, es decir, es una variable que se puede considerar como simple, ya que su tamao es de 32 bits1 y contendr la direccin de memoria de comienzo del n a o objeto o instancia al que haga referencia. De la misma manera que una variable simple como int, double, etc. contiene bits que representan n meros enteros, reales, etc., una referencia u es una variable simple cuyos 32 bits representan direcciones de memoria. Cuando se declara una variable simple, se reserva un espacio de memoria de 8 hasta 64 bits para almacenar un dato de tipo simple. Cuando se declara una variable referencia a una cierta clase, se reserva espacio de memoria para esos 32 bits que necesita una direccin de memoria. o El concepto de referencia es importante puesto que el acceso a los objetos que se vayan creando se realiza siempre a travs de este tipo de variables. Al contrario que en otros e lenguajes, la aritmtica con las variables tipo referencia es muy limitada en Java. Esto e ofrece gran robustez a los programas escritos en Java dado que la posibilidad de equivocarse al utilizar aritmtica de punteros en otros lenguajes suele ser bastante alta. En Java se e pueden realizar asignaciones entre referencias siempre que los tipos sean compatibles. La compatibilidad de tipos en el caso de tipos denidos por el usuario, es decir, de clases, se ver en el apartado 4.2.6. Existe una constante denominada null que se puede asignar a a cualquier referencia para indicar que la variable no apunta a ningn objeto. Con una u referencia se pueden utilizar los siguientes operadores: ==, !=, =, ., instanceof.
3.1.1.
Instanciacin de clases o
La instanciacin de una clase tiene por objetivo la creacin de un objeto. Crear un o o objeto signica reservar espacio de memoria para el mismo y establecer una ligadura con una variable referencia. Es importante no confundir la declaracin de una variable referencia o con la creacin de un objeto. La declaracin de una variable referencia no implica la o o creacin de un objeto o instanciacin de una clase, slo implica la declaracin de una o o o o variable que ser necesaria para acceder a los miembros (atributos y mtodos) de un objeto. a e La instanciacin de una clase se realiza mediante el operador new, el mismo operador que se o vio para la declaracin de vectores o matrices. o Por ejemplo, la declaracin de una variable referencia de tipo Esfera y la instanciacin o o de un objeto del mismo tipo se realizar de la siguiente manera: a
1 Para
52
Cap tulo 3. Fundamentos de la Programacin Orientada a Objetos con Java o Esfera miesfera; miesfera = new Esfera();
La primera l nea del ejemplo constituye la declaracin de la variable referencia miesfera o de tipo Esfera. A partir de este momento la variable miesfera solo podr hacer referencia a o apuntar a objetos que pertenezcan a la clase Esfera. La segunda l nea del ejemplo constituye la instanciacin del objeto. La ejecucin del o o operador new supone la reserva dinmica de memoria para un objeto nuevo de una clase a determinada, clase que viene indicada por el nombre que viene dado a continuacin del o operador new. La memoria reservada por el operador new tiene una direccin de comienzo o que es guardada en la variable referencia miesfera. Esta variable ser utilizada a partir de a este momento para acceder a los miembros del objeto instanciado. La gura 3.1 muestra la ejecucin de estas dos l o neas de cdigo de manera grca. o a miesfera 1. Declaracin de una referencia o miesfera 2. Instanciacin de un objeto o E Esfera radio = 0.0
Figura 3.1: Declaracin de una variable referencia e instanciacin de un objeto. o o Se entiende por memoria dinmica a la memoria que es reservada en tiempo de ejecucin. a o La memoria dinmica es muy cmoda en el desarrollo de aplicaciones pero su utilizacin a o o consume tiempo de ejecucin. En algunos lenguajes de programacin orientados a objetos o o es posible elegir el tipo de memoria a utilizar mientras que en el caso de Java no se pueden instanciar objetos de otra forma que no sea la que acaba de explicarse, al igual que sucede con la declaracin de vectores. o De manera equivalente, puede realizarse la declaracin de la referencia y la instanciacin o o del objeto en una misma l nea de cdigo: o
Esfera miesfera = new Esfera();
El acceso a los atributos y mtodos del objeto se realiza a travs de la referencia y del e e operador de acceso (.). Por ejemplo, para asignar el valor 10 al atributo radio,
[Link] = 10;
Los objetos son entidades completamente independientes. Cada objeto tiene su propio espacio de memoria, y cada cual debe tener una referencia distinta para su acceso. As pues, en el siguiente cdigo o
Esfera miesfera1 = new Esfera(); Esfera miesfera2 = new Esfera();
se han instanciado dos objetos de la misma clase, cada uno con sus propios atributos. Se puede, por ejemplo, asignar valores a los atributos de cada uno de los objetos y calcular su a rea:
3.1. Clases y objetos class Ejemplo { public static void main( String args[] ) { Esfera miesfera1 = new Esfera(); Esfera miesfera2 = new Esfera(); [Link] = 1.0; [Link] = 2.0; [Link](" rea de la esfera 1: " + A 4.0 * 3.14159 * [Link] * [Link] ); [Link](" rea de la esfera 2: " + A 4.0 * 3.14159 * [Link] * [Link] );
53
Aunque en los ejemplos anteriores para cada objeto instanciado exist una variable a referencia que lo apuntaba, la utilizacin de las referencias para apuntar a los objetos es o verstil e independiente. Por ejemplo, se declara la referencia miesfera1 de tipo Esfera a y se instancia un objeto de tipo Esfera que ser apuntado por miesfera1. Seguidamente, a se declara la referencia miesfera2 de tipo Esfera y se le asigna el valor de la referencia miesfera1 (l neas 1-3 del cdigo siguiente). o
1: 2: 3: 4: 5: Esfera miesfera1 = new Esfera(); Esfera miesfera2; miesfera2 = miesfera1; [Link] = 3.0; [Link](" Radio de la esfera: " + [Link] );
Ahora, la referencia miesfera2 contiene la misma direccin de memoria que la referencia o miesfera1 y, por tanto, apunta al mismo objeto, es decir, que ahora habr dos referencias a apuntando al mismo objeto. El acceso a un miembro del objeto puede ser realizado utilizando cualquiera de las dos referencias. Las l neas 4 y 5 del cdigo anterior muestran el acceso al o atributo radio del unico objeto existente (obsrvese que slo se ha instanciado un objeto, slo e o o puede encontrarse un operador new) por medio de las dos referencias. Mediante miesfera1 se accede a radio para asignarle un valor (l nea 4) y mediante miesfera2 se accede a radio para consultar el mismo valor (l nea 5). El resultado de la ejecucin es: o
Radio de la esfera: 3.0
Esquemticamente esto puede observarse en la gura 3.2. a miesfera1 r j r miesfera2 B rr Esfera radio = 3.0
54
A continuacin se muestra un ejemplo que deja clara la idea de dos objetos iguales en el o sentido de que poseen el mismo valor para sus atributos y de dos referencias que apuntan al mismo objeto.
class Ejemplo { public static void main( String args[] ) { Esfera miesfera1 = new Esfera(); Esfera miesfera2; Esfera miesfera3 = new Esfera(); [Link] = 1.0; miesfera2 = miesfera1; [Link] = 1.0; [Link]("Las referencias miesfera1 y (miesfera1==miesfera2?"si":"no")+" apuntan al [Link]("Las referencias miesfera3 y (miesfera3==miesfera1?"si":"no")+" apuntan al miesfera2 " + mismo objeto"); miesfera1 " + mismo objeto");
donde se muestra que las referencias miesfera1 y miesfera2 apuntan al mismo objeto mientras que las referencias miesfera3 y miesfera1 apuntan a objetos distintos, aunque stos tengan el mismo valor para su atributo. e
3.1.2.
Destruccin de objetos o
Dado que la memoria que utilizan los objetos es dinmica, cabe suponer que ser necesario a a algn mecanismo para poder recuperar ese espacio de memoria cuando ya no sea necesario, es u decir, algo parecido al operador delete de C++, por ejemplo. Sin embargo, la liberacin de o memoria en Java es automtica. Java posee un mecanismo denominado Garbage Collection a mediante el cual, y de forma peridica, localiza objetos que no tengan ninguna referencia que o les apunte para liberar la memoria que estn utilizando. Un objeto sin referencia es, seguro, a un objeto que no se utilizar ms dado que no existe forma alguna de tener acceso a l. De esta a a e manera, la liberacin de memoria asignada a un objeto se puede forzar simplemente quitando o todas las referencias al mismo, por ejemplo, asignando el valor null a dichas referencias.
3.2.
Mtodos e
Como se ha mencionado anteriormente, un mtodo es un trozo de cdigo que se ene o capsula de manera independiente del resto del cdigo. Desde este punto de vista, coincide o con el concepto de funcin, procedimiento o subrutina de otros lenguajes. Esto es as dado o que puede recibir unos parmetros o argumentos, y puede devolver un valor como resultado. a Sin embargo, al contrario que en los lenguajes no orientados a objetos, los mtodos no son e completamente independientes, pertenecen siempre a una clase y slo tienen sentido dentro o de la misma dado que tienen como cometido acceder a sus atributos. La estructura de un mtodo es la siguiente: e
55
donde tipo_de_dato especica, bien un tipo de dato simple, bien un tipo de dato denido por el usuario o bien nada, en cuyo caso se utiliza la palabra reservada void. Despus, e gura un identicador vlido para el nombre del mtodo y, entre parntesis, una lista de a e e argumentos separados por comas que puede estar vac Los argumentos se componen de un a. nombre precedido de su tipo de dato. Aunque existan varios argumentos con el mismo tipo de dato, cada uno ha de llevar especicado su tipo. El cuerpo del mtodo es un bloque de e cdigo englobado entre llaves aun cuando est constituido por una sola instruccin. o e o El ejemplo siguiente muestra la clase Esfera a la que se ha incorporado el mtodo area. e
class Esfera { double radio; double area( ) { return 4.0 * 3.14159 * radio * radio; }
Cabe mencionar que, desde el punto de vista del diseo orientado a objetos, es ms n a apropiado incluir un mtodo como ste dentro de la clase que averiguar el rea del objeto e e a desde fuera de la misma tal y como se ha hecho en ejemplos anteriores. En general y como estrategia de diseo orientado a objetos, se pretende dotar al objeto de toda aquella funn cionalidad que pueda llevar a cabo, es decir, encapsular en la clase que dene su tipo de dato todo lo que le atae en cuanto a atributos y comportamiento. n Los mtodos pueden verse de alguna manera como solicitudes de servicio que se realizan e sobre el objeto sobre el que es llamado el mtodo desde fuera del mismo. El cdigo siguiente e o ser la versin correcta del ejemplo de la pgina 52. En lugar de realizar clculos externos a o a a sobre atributos de un objeto (clculo del rea en el ejemplo), desde el punto de vista de la a a programacin orientada a objetos resulta ms adecuado solicitar que el objeto realice por o a s mismo dichos clculos en el mtodo area y ofrezca el resultado tal como muestra el cdigo a e o siguiente:
class Ejemplo { public static void main( String args[] ) { Esfera miesfera1 = new Esfera(); Esfera miesfera2 = new Esfera(); [Link] = 1.0; [Link] = 2.0; [Link](" rea de la esfera 1: " + [Link]() ); A double a = [Link](); [Link](" rea de la esfera 2: " + a ); A
ofreciendo como resultado la misma salida que en el caso ejemplo de la pgina 52. a Como se ha observado en el ejemplo anterior, el mtodo area no tiene argumentos pero e s devuelve un valor de tipo double que se ha impreso por la salida estndar en el primer a caso y se ha asignado a la variable a en el segundo.
56
Un ejemplo de mtodo con argumentos podr ser el mtodo setRadio que se dene de e a e la siguiente manera:
void setRadio( double r ) { radio = r; }
La inclusin de un mtodo como setRadio en la clase Esfera permite asignar valores a o e los atributos del objeto de manera segura dado que el cdigo del metodo setRadio puede o comprobar que el valor pasado como argumento es vlido antes de asignarlo al atributo a radio. Desde este punto de vista, una implementacin ms correcta podr ser la siguiente: o a a
void setRadio( double r ) { if( r>=0.0 ) { radio = r; } }
de manera que ahora se puede estar seguro de que, aunque el mtodo reciba un valor negativo e como argumento, el radio de la esfera no tendr nunca un valor negativo. A continuacin se a o muestra la utilizacin del mtodo setRadio: o e
Esfera miesfera1 = new Esfera(), miesfera2 = new Esfera(); [Link]( 1.0 ); [Link]( -4.0 ); [Link]( 2.0 ); ...
Segn la implementacin del mtodo setRadio, una llamada al mismo con un valor negativo u o e no modicar el valor del radio de la esfera. a Conviene recordar en este punto que los argumentos de un mtodo y las variables denidas e dentro del mismo son variables locales al mtodo y, por lo tanto, no son visibles fuera de ste. e e Su tiempo de vida est limitado al tiempo en el que se est ejecutando el mtodo (por ejemplo, a a e el argumento r del mtodo setRadio). Por el contrario, los atributos declarados en el mbito e a de la clase (radio), son perfectamente visibles dentro de cualquier mtodo denido en la e misma y cualquier modicacin que se realice a travs de un mtodo permanecer mientras o e e a no se cambie expl citamente y el objeto siga en ejecucin. o Los mtodos pueden no devolver nada (void) o devolver algn valor. En este segundo e u caso es preceptivo que exista al menos una instruccin de tipo return. o Antes de pasar a describir con detalle las particularidades de los mtodos en Java, cabe e mencionar que existe la recursividad en Java, es decir, la posibilidad de llamar al mismo mtodo desde dentro del cul se est realizando la llamada. e a a
3.2.1.
Constructores
Como se ha venido observando, en la instanciacin de una clase para crear un objeto, o se utiliza el nombre de la misma seguido de un parntesis abierto y otro cerrado. En esta e instanciacin de la clase tiene lugar la ejecucin de un constructor. El constructor, aunque o o sintcticamente es semejante a un mtodo, no es un mtodo. El constructor puede recibir a e e argumentos, precisamente se utilizan los mencionados parntesis para hacerlo, de la misma e manera que se pasan argumentos en las llamadas a los mtodos. Aunque el constructor e no haya sido denido expl citamente, en Java siempre existe un constructor por defecto que posee el nombre de la clase y no recibe ningn argumento. Esa es la razn por la que u o
3.2. Mtodos e
57
puede utilizarse el constructor Esfera() de la clase Esfera aunque no se haya denido expl citamente. En el apartado anterior se utiliz el mtodo setRadio para asignar valores al atributo o e radio. Sin embargo, existe un mecanismo ms adecuado para asignar valores a los atributos a de una clase cuando esta asignacin se realiza en el momento de la creacin del objeto. Este o o mecanismo consistente precisamente en la utilizacin de los constructores. Los construco tores son mtodos especiales que tienen el mismo nombre que la clase en la que se denen, e no devuelven ningn valor y son llamados en la instanciacin de la misma para crear un u o objeto nuevo. El siguiente ejemplo muestra la implementacin de un constructor para la o clase Esfera:
class Esfera { double radio; Esfera( double r ) { radio = r; } ...
Varias cosas hay que tener en cuenta: Es perfectamente vlido denir un mtodo como el siguiente: a e
void Esfera( double r ) { ... }
Sin embargo, este mtodo es precisamente eso, un mtodo y no un constructor. El e e constructor no debe llevar especicador de tipo de retorno, ni siquiera void, dado que en este caso no podr utilizarse como constructor en la instanciacin de la clase. a o Cuando se implementa un constructor, el constructor por defecto del que se dispon es a, decir, el constructor sin argumentos, se pierde y ya no puede ser utilizado a no ser que se implemente expl citamente. Por ejemplo, segn la ultima clase Esfera mostrada, lo u siguiente ser un error, a
Esfera miesfera = new Esfera();
3.2.2.
Ocultacin de atributos o
Puede ocurrir que el nombre de los argumentos de un mtodo o constructor coincida con e el nombre de los atributos de la clase, como sucede en el siguiente ejemplo:
class Esfera { double radio; Esfera( double radio ) {
58
Cap tulo 3. Fundamentos de la Programacin Orientada a Objetos con Java o [Link] = radio;
Esto no origina ningn conicto para el compilador. Sin embargo, la utilizacin del mismo u o nombre oculta el atributo radio dentro del mtodo dado que las variables ms internas, es e a decir, las declaradas dentro del mtodo y los argumentos tienen prioridad sobre los atributos e de la clase. Tal como se ha mostrado en el ejemplo, mediante la utilizacin de la palabra o reservada this es posible acceder a los atributos de clase distinguindolos as de las variables e declaradas en el mtodo y/o los argumentos con el mismo nombre. e
3.2.3.
Sobrecarga de mtodos e
La sobrecarga de mtodos consiste en la posibilidad de denir dos o ms mtodos con e a e el mismo nombre dentro de una misma clase, diferentes en su nmero y/o tipo de dato u de los argumentos. El tipo de dato que devuelven dos o ms mtodos es insuciente para a e diferenciarlos, por lo tanto, no se pueden denir mtodos con el mismo nombre y nmero y e u tipo de argumentos que slo se diferencien en el tipo de retorno. o Los constructores tambin pueden ser sobrecargados de la misma manera. As por ejeme , plo, se pueden sobrecargar los constructores de la clase Esfera,
class Esfera { double radio; Esfera( ) { radio = 0.0; } Esfera( double r ) { radio = r; } } ...
Un constructor puede ser llamado desde otro constructor. La forma de hacerlo consiste en utilizar la palabra reservada this en lugar del nombre del constructor. El constructor que ser llamado vendr determinado por el nmero y tipo de argumentos. El siguiente ejemplo a a u muestra el constructor Esfera sobrecargado 4 veces.
class Esfera { double radio; Color color; Esfera( ) { color = [Link]; radio = 0.0; } Esfera( double radio ) { this();
3.2. Mtodos e [Link] = radio; } Esfera( Color color ) { this(); [Link] = color; } Esfera( Color color, double radio ) { [Link] = color; [Link] = radio; }
59
La clase Esfera incluye ahora dos atributos: el radio y el color. Este segundo atributo pertenece a una clase denida en el API de Java denominada Color. La instanciacin de o un objeto de tipo Esfera sin argumentos crea una esfera blanca y de radio 0.0. El segundo constructor permite crear una esfera con un determinado radio y el color por defecto (el blanco). En este ultimo caso se hace uso de this para llamar al constructor sin argumentos que asigna el color blanco al atributo color. El tercer constructor es anlogo al anterior y a se utiliza para asignar un color determinado a la nueva esfera sin que importe el radio. Estos dos ultimos constructores son un ejemplo de sobrecarga con el mismo nmero de argumentos. u El cuarto constructor permite la inicializacin de los atributos con los valores pasados como o argumentos. Puede llamarse a cualquier constructor con y sin argumentos utilizando this. En el caso de este ultimo constructor podr haberse pensado en implementarlo de la siguiente forma: a
Esfera( Color color, double radio ) { this(radio); this(color); }
Sin embargo, en este caso es incorrecto debido a que la utilizacin de this para llamar a un o constructor slo puede hacerse en la primera instruccin. La segunda llamada (this(color)) o o produce, por tanto, un error de compilacin. o Por ultimo, decir que desde un mtodo no es posible utilizar this() para ejecutar el e cdigo de un constructor. o
3.2.4.
En primer lugar conviene entender la manera en que se pasa un argumento cualquiera a un mtodo. Otros lenguajes como Pascal diferencian entre paso de parmetros por valor o e a referencia en llamadas a subrutinas. En C, sin embargo, se dice que todos los argumentos se pasan por valor y que, para pasar un argumento por referencia ha de pasarse su direccin o de memoria o puntero. En realidad, puede omitirse esta diferenciacin entre el concepto de o paso de argumentos por valor o por referencia en cualquier lenguaje si se analiza detenidamente el funcionamiento del paso de argumentos a una subrutina y sto puede comprenderse e fcilmente analizando el caso concreto de Java. a Puede armarse que un mtodo slo puede recibir datos de tipo simple, entre los cuales, e o se incluyen las referencias a objetos. Desde este punto de vista, en la llamada a un mtodo, los e argumentos pueden ser datos de tipo simple que representan nmeros enteros (byte, short, u int o long), caracteres (char), n meros reales (float o double), booleanos (boolean) o u referencias. El argumento es una variable que recibe el valor de la variable pasada al mtodo e en la llamada, de manera que su modicacin dentro del mtodo no afecta a la variable o e
60
pasada a dicho mtodo, es decir, se pasa unicamente su valor. El siguiente ejemplo muestra e este concepto.
class A { void metodo( double argumento ) { argumento = 4.0; } } public class Ejemplo { public static void main(String args[]) { A ref; double variable; variable = 2.5; ref = new A(); [Link](variable); [Link]("variable = "+variable); } }
Segn la explicacin dada, la salida por pantalla deber quedar claro que debe ser la siguu o a iente:
variable = 2.5
La asignacin del valor 4.0 al argumento argumento no afecta en absoluto al valor de la o variable variable. Adems, tal como se ha dicho, este es el comportamiento comn a la a u mayor de lenguajes de programacin. En lenguaje C, por ejemplo, el funcionamiento es a o idntico. Si se desea cambiar el valor de una variable pasada como argumento a una funcin e o de C, lo que se pasa en realidad no es el valor de dicha variable sino su direccin. A travs o e de su direccin, el mtodo tiene acceso a dicha variable y puede modicar su valor real. En o e Pascal sucede lo mismo aunque con una sintaxis diferente. Una diferencia fundamental entre estos lenguajes de programacin y Java en relacin a o o este tema es que, en el caso de Java, no existe ninguna posibilidad de pasar la direccin de o una variable perteneciente a un tipo simple. Esto permite decir que los argumentos en Java siempre se pasan por valor y no es posible pasarlos por referencia. Tambin es parte de la e razn por la que se suele decir falsamente que en Java no existen los punteros. Aunque lo o puede parecer, la imposibilidad de modicar una variable pasada como argumento a travs e de un mtodo no constituye una limitacin del lenguaje; sin embargo, simplica considere o ablemente el lenguaje. Si consideramos las referencias a objetos como variables o datos de tipo simple, las consideraciones anteriores pueden aplicarse inmediatamente para comprender el paso de objetos como argumentos. Al pasar como argumento la referencia a un objeto, ste se puede utie lizar para tener acceso a los miembros del mismo. Este acceso no tiene ninguna limitacin, o es decir, se puede utilizar la referencia para acceder a los atributos del objeto para leerlos e incluso para modicarlos tal como se ha venido haciendo hasta el momento. En denitiva, si se desea pasar un objeto a un mtodo, se pasar una referencia al mismo. De todo lo anterior, e a deber deducirse fcilmente que la modicacin del argumento referencia a un objeto dentro a a o del mtodo (asignarle, por ejemplo, null) no tiene efecto sobre la variable referencia que se e pas en la llamada al mtodo. o e En el ejemplo siguiente, se ha aadido un nuevo constructor a la clase Esfera cuya n funcin es crear una esfera con la misma dimensin que otra dada. o o
3.2. Mtodos e class Esfera { double radio; Esfera( Esfera e ) { radio = [Link]; } ...
61
de manera que se tendr la posibilidad de denir esferas con los mismos atributos de manera a sencilla. Las siguientes dos esferas tienen exactamente la misma dimensin: o
Esfera miesfera1 = new Esfera( 2.0 ); Esfera miesfera2 = new Esfera( miesfera1 );
Como se ha podido observar, dentro del nuevo constructor se ha accedido a los elementos de la clase Esfera a travs de su referencia. e El siguiente ejemplo muestra un mtodo que modica el valor del atributo de un objeto: e
void escalar( Esfera esfera, double escalado ) { [Link] *= escalado; }
lo que muestra la manera en la que se pueden modicar atributos de un objeto desde un mtodo. e
3.2.5.
De la misma manera que un mtodo devuelve datos de tipo simple, tambin puede dee e volver referencias a objetos. En el ejemplo siguiente se incorpora a la clase Esfera un mtodo e para mostrar la manera en la que se lleva a cabo la devolucin de objetos por parte de un o mtodo. e
class Esfera { double radio; ... Esfera doble( ) { Esfera c = new Esfera( 2*radio ); return c; }
El mtodo doble tiene por cometido crear una esfera nueva que tenga el doble de radio. e Una vez creada esta nueva esfera, el mtodo devuelve la variable referencia que contiene la e direccin de memoria del objeto creado dentro del mtodo. Esta referencia puede ser utilizada o e fuera del mtodo para tener acceso a dicha esfera tal como muestra el ejemplo siguiente, e
Esfera miesfera1 = new Esfera( 2.0 ); Esfera miesfera2 = [Link]( );
Despus de la ejecucin del cdigo anterior, se dispone de dos objetos de tipo Esfera: el e o o objeto apuntado por la variable referencia miesfera1 con radio 2.0 y el objeto apuntado por la variable referencia miesfera2 con radio 4.0. Los argumentos y las variables locales a un mtodo son variables simples y referencias. e Las primeras se utilizan para representar nmeros, caracteres y booleanos mientras que las u
62
segundas se utilizan para representar direcciones de memoria donde comienza el espacio asignado a un objeto. Su dimensin es conocida en tiempo de compilacin y su longitud o o puede ir desde los 8 hasta los 64 bits. Todas ellas, como argumentos de un mtodo o variables e locales del mismo, son temporales, es decir, son creadas cuando se ejecuta el mtodo y se e destruyen al acabar la ejecucin del mismo. Sin embargo, el espacio de memoria asignado a o un objeto cuando se crea (new) es dinmico, es decir, se reserva en tiempo de ejecucin. Este a o espacio de memoria no sigue las misma reglas de tiempo de vida que las variables simples y las referencias, ya que no se destruye cuando acaba la ejecucin del mtodo donde se cre. o e o El objeto creado en el mtodo doble() del ejemplo anterior no se destruye a causa de la e nalizacin de la ejecucin del mtodo. En dicho ejemplo, la referencia c de tipo Esfera o o e es local al mtodo y se destruye cuando acaba la ejecucin del mismo. Esto no sucede con e o el objeto creado dentro del mtodo con el operador new. El mtodo doble() devuelve el e e valor de la referencia c que es copiado en la referencia miesfera2 en la llamada al mtodo e [Link]().
3.3.
Los atributos y mtodos que se han visto hasta ahora en la clase Esfera se denominan e de instancia. Esto quiere decir que slo pueden ser utilizados cuando se ha instanciado un o objeto de la clase. Mientras el objeto no se haya instanciado mediante el operador new no es posible su acceso. Dicho de otra forma, los atributos y mtodos de instancia pertenecen a e las instancias, si no existe la instancia no existen dichos miembros. En contraposicin existen los miembros llamados de clase o estticos. Estos miembros o a existen siempre y, por lo tanto, pueden ser utilizados sin necesidad de haber instanciado un objeto de la clase. La introduccin de los miembros de clase dota a stas de una funcionalidad o e adicional a la de ser una especicacin de tipo de dato de usuario para crear objetos de dicho o tipo. Sintcticamente, se introduce la palabra reservada static precediendo la declaracin a o de atributos y mtodos de clase. Las implicaciones que tienen tanto la declaracin de un e o atributo de clase como la denicin de un mtodo de clase son algo distintas en cada caso. o e Los atributos de clase son accesibles sin necesidad de instanciar ningn objeto de la clase u puesto que pertenecen a la clase. Pero es importante saber tambin que son accesibles por e las instancias de la clase teniendo en cuenta que el atributo de clase es unico y com n u a todas ellas. Si una instancia modica dicho atributo, cualquier otra instancia de la clase ver dicho atributo modicado. Sea el ejemplo siguiente, a
class Atristatic { static int atributo; } public class Ejemplo { public static void main(String args[]) { Atristatic instancia1 = new Atristatic(); Atristatic instancia2 = new Atristatic(); [Link] = 3; [Link]("El atributo \"atributo\" desde " + "la instancia2 vale: " + [Link]); } }
63
Dado que no es necesaria la instanciacin de la clase para el acceso a atributos y mtodos o e de clase, es de esperar que exista un mecanismo para poder acceder a dichos elementos en caso de que no exista una referencia vlida. Los atributos y mtodos de clase (tambin llamados a e e estticos) pueden ser accedidos con el nombre de la clase. La tercera l a nea del mtodo main e de la clase Ejemplo anterior es equivalente a la siguiente:
[Link] = 3;
En el caso de los mtodos, las implicaciones son ms sencillas ya que el cdigo de un mtodo e a o e no puede cambiar durante la ejecucin de un programa tal y como lo hace una variable. Lo o usual en la utilizacin de los mtodos estticos es acceder a ellos por el nombre de la clase o e a a la que pertenecen. Hay que tener en cuenta que los mtodos y atributos de clase pueden ser utilizados e siempre dado que no es necesario que se haya instanciado una clase para poder acceder a ellos. Sin embargo, constituye un problema el acceso a un mtodo o atributo de instancia e desde un mtodo esttico. En el ejemplo siguiente e a
class Atristatic { static int atributo_estatico; int atributo_de_instancia; static void metodo_estatico( Atristatic objeto ) { atributo_estatico = 1; atributo_de_instancia = 1; // lnea errnea o objeto.atributo_de_instancia = 1; }
el acceso al atributo atributo_de_instancia desde el mtodo esttico metodo_estatico e a produce un error de compilacin. El compilador no permite el acceso a travs de this a miemo e bros de la clase (atributo_de_instancia = 1; y this.atributo_de_instancia = 1; son equivalentes). Otra cosa bien distinta la constituye el acceso a cualquier miembro a travs de la referencia a un objeto existente tal como muestra la ultima l e nea de cdigo del o ejemplo. El siguiente cdigo es un ejemplo completo de una posible implementacin del concepto o o de nmero complejo (clase Complejo) que muestra la utilizacin de los mtodos de instancia u o e y de clase para llevar a cabo parte de la aritmtica de complejos. e
class Complejo { double re, im; // Constructores Complejo ( double r, double i ) { ... } Complejo ( Complejo a ) { ... } // Mtodos de instancia e void inc( Complejo a ) { ... } void dec( Complejo a ) { ... } void por( Complejo a ) { ... } void por( double d ) { ... } void div( double d ) { ... } double modulo( ) { ... } Complejo conjugado( ) { ... }
64
Cap tulo 3. Fundamentos de la Programacin Orientada a Objetos con Java o // Mtodos de clase e static Complejo suma static Complejo resta static Complejo producto static Complejo division
( ( ( (
a, a, a, a,
b b b b
) ) ) )
{ { { {
} } } }
En la clase Complejo, existen siete mtodos de instancia (no estticos) cuya denicin es e a o la que gura en la Tabla 3.1. Se observa que el mtodo por est sobrecargado para permitir e a la multiplicacin de un nmero complejo por otro complejo o un escalar. o u
inc dec por div modulo conjugado
Tabla 3.1: Mtodos de instancia de la clase Complejo, donde a, c C, siendo c el complejo e representado por la instancia sobre la que se invoca el mtodo, y d R. e Desde el punto de vista del diseo de una aplicacin es importante notar cmo se han n o o utilizado y por qu los mtodos de instancia. Todos los mtodos de instancia trabajan sobre e e e el estado de un objeto concreto, bien para obtener alguna informacin del mismo (modulo y o conjugado), o bien para modicar su estado (inc, dec, por y div). Por ejemplo,
Complejo conjugado( ) { return new Complejo( re, -im ); } void por( double d ) { re *= d; im *= d; }
Los mtodos que modican el estado de una instancia de la clase Complejo pretenden simular e operaciones como c = c + a, c = c * a, . . . que pueden realizarse sobre variables simples pero no sobre objetos de una clase. A ra de este hecho, cabe mencionar que, en otros lenguajes de programacin orientados z o a objetos como C++, existe la posibilidad de sobrecargar operadores, lo que permitir dotar a a los operadores aritmticos de la posibilidad de operar sobre objetos de determinadas clases e tales como la clase Complejo. En la Tabla 3.2 se muestran los cuatro mtodos de clase de la clase Complejo y un e ejemplo de implementacin es el siguiente: o
static Complejo suma ( Complejo a, Complejo b ) { return new Complejo( [Link]+[Link], [Link]+[Link] ); }
Estos mtodos se implementan para ofrecer servicios que se considera adecuado que proe porcione la clase y de manera independiente de las instancias concretas que se hayan creado.
65
Tabla 3.2: Mtodos de clase de la clase Complejo, donde a, b, c C siendo c el complejo e resultado de la operacin. o
Dicho de otra forma, los mtodos de clase son la manera ms parecida en la que se implee a mentar dicha operacin en otros lenguajes de programacin no orientados a objetos. Los a o o mtodos suma, resta, producto y division permiten realizar cualquiera de estas operae ciones aritmticas sobre dos objetos de tipo Complejo que son pasados como argumentos y e ofrecer un resultado del mismo tipo. Hay que notar que no se modica el estado de ninguna instancia al realizar llamadas sobre estos mtodos, su funcin consiste en recibir dos objetos e o como argumento, crear uno nuevo que sea resultado de la operacin que implementan y o devolverlo. En muchos casos, una misma operacin puede implementarse como mtodo de instancia o e o como mtodo de clase. Es una decisin de estilo de programacin la que hay que tomar e o o para decantarse por una u otra solucin. La que se ha mostrado aqu a travs de la clase o e ejemplo Complejo se ha justicado convenientemente y resulta bastante adecuada aunque no siempre es la que se sigue. Por ejemplo, en el API2 de Java existe una clase denominada Object (apartado 4.1.5) con un mtodo equals que devuelve cierto si el objeto pasado como e argumento es igual que el objeto sobre el que es invocado el mtodo. Segn la estrategia de e u programacin planteada hasta el momento este mtodo deber haberse implementado como o e a un mtodo de clase. e Si se necesita ejecutar instrucciones para inicializar las variables estticas en el proceso a de carga de la clase stas se pueden especicar en un bloque esttico. Java utiliza lo que e a se denomina carga dinmica (dynamic loading) de cdigo. Este es un mecanismo mediante a o el cual la aplicacin Java carga o lee en memoria un chero ejecutable Java (extensin o o .class) en tiempo de ejecucin en el momento en que lo necesita. Este momento es la o primera vez que se utiliza la clase para instanciarla o para acceder a un miembro esttico. a La utilizacin de un bloque esttico tiene la forma que muestra el siguiente ejemplo: o a
class UsoStatic { static int a = 3; static int b; // Bloque estatico static { [Link]("Entra en el bloque esttico."); a b = a * 4; } static void metodo( int x ) { [Link]("x = " + x); [Link]("a = " + a);
2 API (Application Programming Interface): Conjunto de componentes sofware (funciones, procedimientos, clases, mtodos, . . . ) proporcionados para poder ser utilizados por otro software. e
66
Cap tulo 3. Fundamentos de la Programacin Orientada a Objetos con Java o [Link]("b = " + b);
} }
La carga de la clase UsoStatic se produce con la instanciacin de la misma y, por tanto, o tiene lugar la ejecucin del bloque esttico. A partir de ese momento, las variables a y b o a tienen los respectivos valores 3 y 12. La subsiguiente ejecucin del mtodo esttico metodo o e a muestra los valores concretos de las variables estticas y del argumento. a El siguiente es otro ejemplo de utilizacin de bloque esttico que muestra un uso ms o a a prctico de los bloques estticos. a a
public class Ejemplo { static int potencias2[]; static { potencias2 = new int[10]; potencias2[0] = 1; for( int i=1; i<10; i++ ) { potencias2[i] = 2*potencias2[i-1]; } } }
Cuando se carga la clase del ejemplo se calculan las 10 primeras potencias de 2. Este clculo a se lleva a cabo una sola vez mientras dure la ejecucin de la aplicacin. o o
3.4.
Encapsulacin de cdigo o o
Por un lado, las clases en un lenguaje orientado a objetos constituyen la solucin que o ofrece este paradigma de programacin al concepto de Tipo Abstracto de Datos (TAD) o perseguido durante gran parte de la historia del software con objeto de simplicar el proceso de programacin y de hacer abordables aplicaciones cada vez ms grandes y complejas. Por o a otro lado, el concepto subyacente a los TADs es el de la encapsulacin. o La programacin estructurada introduce las subrutinas (funciones y procedimientos) con o objeto de abordar problemas grandes subdividiendo o particionando dicho problema en problemas ms pequeos. Pero esta subdivisin se realiza desde el punto de vista funcional, es a n o decir, se describe un algoritmo grande en funcin de un conjunto de algoritmos ms pequeos o a n
67
que, a ser posible, tengan entidad propia. Una rutina tiene entidad propia cuando es independiente del resto y reutilizable. La reutilizacin de cdigo es una caracter o o stica altamente deseable dado que no solo supone reduccin de costes de programacin sino que tambin o o e proporciona abilidad: en la medida en que las rutinas son ms utilizadas son ms ables, y a a eciencia: al preocuparse de una subrutina de manera independiente del resto (incluso sta puede ser implementada por un equipo distinto de programadores) es ms fcil e a a dedicar un esfuerzo adicional para que sea lo ms eciente posible. a La programacin orientada a objetos da un salto de gigante en esta direccin reuniendo o o o encapsulando tanto atributos como funcionalidad en una sola entidad de cdigo. El partio cionado o descomposicin del problema ya no se realiza slo desde el punto de vista funcional o o sino que va ms all. Los objetos surgen de la necesidad de modelizar el problema del diseo a a n de software desde el punto de vista de las entidades que lo componen y de su clasicacin o en distintos tipos o clases. Bsicamente sta es la idea bajo la que se dene un TAD y las a e clases son, hoy en d una de sus mejores aproximaciones. a, La utilizacin de rutinas en la implementacin de un programa permite cierto grado de o o abstraccin dado que para utilizar una subrutina no es necesario conocer cmo dicha rutina o o resuelve un problema sino unicamente saber qu problema resuelve y cual es su interfaz. e La interfaz de una rutina viene dada por sus argumentos de entrada y de salida, es decir, qu datos y de qu tipo son los que hay que proporcionar a dicha rutina para que resuelva e e el problema y qu datos y de qu tipo son los que devuelve. Se puede decir que las rutinas e e encapsulan en su interior el algoritmo de resolucin. Asociado al concepto de encapsulacin o o se encuentra el de visibilidad, que viene dado por su interfaz o aquello que se desea dar a conocer y que sea accesible desde otra parte del cdigo. o La programacin orientada a objetos, evidentemente, incluye el mismo tipo de encapsuo lacin a nivel de mtodos que el proporcionado por la programacin estructurada con las o e o subrutinas. Sin embargo, eleva la encapsulacin de cdigo a un nivel de abstraccin superior, o o o el nivel de abstraccin proporcionado por las clases, las cuales pueden encapsular tanto los o atributos de las entidades como el comportamiento asociado a las mismas. La interfaz en el caso de las clases puede venir dada por el prototipo de algunos de sus mtodos y por e algunos de sus atributos. La interfaz de una clase deber estar bien denida previamente a a su implementacin unicamente por ese subconjunto de mtodos que la clase quiere ofrecer o e al resto de entidades y que denen los servicios que cualquiera de sus instancias ofrece. El conjunto de atributos y mtodos que se desea permanezcan ocultos es, por supuesto, e decisin del programador. Sin embargo, existen mecanismos, como es el caso de Java, para o dotar de distintos niveles de accesibilidad a los miembros de una clase y, de esta manera, construir una interfaz apropiada con la que otros objetos de la aplicacin puedan interactuar o con los objetos de dicha clase permitiendo la accesibilidad a unos miembros e impidindosela e a otros. Como regla general y con el objetivo de llevar a cabo una programacin adecuada o y lo ms prxima posible a la programacin orientada a objetos la interfaz deber estar a o o a constituida unicamente por el prototipo de un subconjunto de mtodos. No deber estar e an incluidos ciertos mtodos que se han implementado a la hora de subdividir un algoritmo e complejo en pequeos algoritmos (programacin estructurada), y nunca por los atributos, n o los cuales deber quedar encapsulados en la clase (gura 3.3). an El siguiente subapartado describe los mecanismos de Java para proporcionar accesibilidad a los miembros de la interfaz e impedirla a aquellos que no forman parte de ella.
68
atributo1 . . .
atributo n
mtodo privado 1 e . . .
mtodo privado m e
Figura 3.3: Encapsulacin de cdigo en una clase. Los mtodos pblicos constituyen la ino o e u terfaz de la clase con el exterior.
3.4.1.
Modicadores de acceso
Los miembros de una clase tienen una propiedad denominada accesibilidad. Esta propiedad se representa mediante un modicador de acceso. En principio, un miembro puede ser bsicaa mente pblico o privado. La cuestin que hay que abordar consiste en saber desde dnde se u o o entiende esa accesibilidad, es decir, los miembros de una clase pueden ser accedidos desde dentro de la propia clase, desde otra clase que pertenezca al mismo paquete o una clase que pertenezca a distinto paquete, desde una subclase, . . . Dado lo cual, la caracterizacin o pblica o privada de un miembro ha de ser desarrollada en cada punto explicativo en el que u tenga sentido hablar de ello y no se puede resumir hasta haber planteado los conceptos de herencia y de paquetes. Sin embargo, en este punto, es posible diferenciar dos modos de acceso a los miembros de una clase: acceso pblico y acceso privado, haciendo la salvedad de que el concepto completo u de la accesibilidad de los miembros de una clase no podr ser conocida hasta comprender a otros trminos bsicos. Por el momento se distinguen: e a atributos y mtodos privados como miembros de una clase que no pueden ser e accedidos desde otra clase, solo pueden serlo dentro de la propia clase. Estos miembros poseen el modicador de acceso private; y atributos y mtodos p blicos como miembros de una clase que pueden ser accedidos e u desde otra clase. Estos miembros poseen el modicador de acceso public o friendly. Se considera el acceso como friendly cuando no se posee ningn modicador expl u cito (modicador de acceso por defecto). La diferencia entre poseer el atributo public y el atributo de acceso por defecto (friendly) est relacionada con la utilizacin de paquetes por lo que se ver su diferencia en ese contexto. a o a Un ejemplo de utilizacin de modicadores de acceso es el siguiente: o
class A { int i; private int j; } class B { A a = new A();
69
a.i = 10; // a.j = 10 no est permitido a // [Link]("Variable j: " + a.j); tampoco est permitido a
En el ejemplo, la variable i es accesible desde fuera de la clase A. De hecho, desde la clase B se accede a dicha variable a travs de la referencia a de tipo A a la misma para modicar su e valor. Ser equivalente haber declarado dicha variable como public int i. Sin embargo, a el acceso a la variable j de la clase A no est permitido desde fuera de la clase A, ni para a escritura ni para lectura, es decir, no se puede consultar su valor aunque no se modique. A continuacin se muestra un ejemplo en el que se construye la clase Pila. Esta clase o sirve para almacenar nmeros enteros siguiendo esta estructura de datos en la que el ultimo u elemento introducido en la pila es el primer elemento en salir de la misma. La interfaz de la estructura de datos Pila viene dada por las siguientes operaciones:
public void add( int valor ): Mediante esta operacin se aade el nuevo nmero entero o n u proporcionado como argumento. La operacin no devuelve nada. o public int get(): Mediante esta operacin se extrae el ultimo nmero entero introducido o u en la estructura. Cada extraccin devuelve los nmeros de la estructura en orden inverso o u al que se introdujeron. La operacin devuelve el nmero entero extra o u do. public int top(): Esta operacin devuelve el ultimo nmero entero introducido en la pila. o u La operacin no modica la informacin de pila. o o
class Pila { private int n; private int vector[]; public Pila( int talla ) { vector = new int[talla]; n = 0; } public void add( int valor ) { if( n<[Link] ) { vector[n++] = valor; } } public int get( ) { int a = -11111; if( n>0 ) { a = vector[--n]; } return a; } public int top( ) { int a = -11111; if( n>0 ) { a = vector[n-1]; }
70
La clase Pila puede albergar nmeros enteros hasta un mximo de talla denido por u a el usuario de la clase en la instanciacin de la misma. Se utiliza un vector para almacenar o los nmeros de la pila y una variable para almacenar la cantidad de elementos almacenados. u Segn la implementacin propuesta la introduccin de un nuevo elemento cuando la pila u o o est llena no tiene efecto. La extraccin de un elemento de una pila vac devuelve un valor a o a de -11111. Se ver en el Cap a tulo 5 la forma correcta de realizar el control necesario para operaciones que pueden desencadenar errores como introducir datos en una pila llena o extraerlos de una pila vac Ntese, adems, que los atributos de la clase se han declarado a. o a como privados mientras que los mtodos que forman parte de la interfaz (en este caso todos) e se han declarados como pblicos. u
3.5.
Las clases anidadas son clases denidas dentro de otras. La clase anidada slo es visible o para la clase que la contiene. En otras palabras, la clase anidada es un miembro privado por defecto de la clase externa que la contiene. La clase externa no tiene acceso a los miembros de la clase anidada. Existen dos tipos de clases anidadas: las estticas y las no estticas. Las clases anidadas a a y estticas poseen el modicador static. Estas clases poseen ciertas restricciones que las a hacen poco utiles respecto de las clases no estticas por lo que su utilizacin es escasa. Las a o clases anidadas no estticas reciben tambin el nombre de internas. Las clases internas tienen a e acceso a todos los miembros (incluso a aquellos privados) de la clase en la que se ha denido. Un ejemplo de aplicacin es el siguiente (en el apartado 3.8 se explica el programa prino cipal que utiliza esta clase):
class Sistema_Solar { private Estrella estrella; private Planeta planeta[]; class Estrella { private String nombre; Estrella( String nombre ) { [Link] = nombre; } public String getNombre() { return nombre; } } class Planeta { private String nombre; Planeta( String nombre ) { [Link] = nombre; } public String getNombre() { return nombre; } }
71
Sistema_Solar( String astro[] ) { estrella = new Estrella(astro[0]); planeta = new Planeta[[Link]-1]; for( int i=1; i<[Link]; i++ ) { planeta[i-1] = new Planeta(astro[i]); } } void mostrar_planetas() { [Link]("Estrella del sistema solar = "+ [Link]()); for( int i=0; i<[Link]; i++ ) { [Link]("Planeta "+(i+1)+" = "+ planeta[i].getNombre()); } }
En este ejemplo, el constructor de la clase Sistema_Solar recibe como argumento un vector de referencias de tipo String (la clase String se explica en el apartado siguiente) y utiliza estas cadenas o strings para construir un sistema solar con un nombre para la estrella y nombres para los planetas. Obsrvese que en el constructor tambin se utiliza un vector de e e referencias a objetos de tipo Planeta. En general, un vector de referencias no es diferente a un vector de otros datos de tipo simple (nmeros reales y enteros, caracteres y booleanos) u como los estudiados en el Cap tulo 2, simplemente cada elemento del vector en este caso servir para referenciar a un objeto del mismo tipo del cual se ha declarado el vector de a referencias. Las clases internas pueden denirse en otros mbitos o bloques como, por ejemplo, los a mtodos o el cuerpo de un bucle. A este tipo de clases se les denomina clases annimas dado e o que no se les asigna un nombre. No darle un nombre tiene sentido si slo se va a instanciar o una vez. Actualmente, su utilizacin es muy frecuente y casi exclusiva en la implementacin o o de manejadores de eventos para el desarrollo de interfaces grcas de usuario, cuestin a o que se ver en el Cap a tulo 6.
3.6.
La clase String
Para terminar este cap tulo conviene hacer mencin de la clase String de Java. La o clase String es una clase que proporciona el propio lenguaje para el manejo de cadenas de caracteres. Se trata de una clase especial. Puede utilizarse como cualquier otra clase para instanciar objetos que almacenen cadenas de caracteres de la siguiente manera,
String st = new String("Esto es una cadena");
Dado que String es una clase especial, los dos cdigos anteriores son equivalentes. o Otra particularidad de la clase String es que tiene el operador + sobrecargado, es decir, se pueden sumar objetos de tipo String dando como resultado una cadena nueva formada por ambas cadenas concatenadas. Por ejemplo, la salida del cdigo siguiente, o
72
Cap tulo 3. Fundamentos de la Programacin Orientada a Objetos con Java o public class Ejemplo { public static void main(String args[]) { String string1 = "Esto es "; String string2 = "la cadena resultado"; String string3 = string1 + string2; [Link](string3); } }
ser a,
Esto es la cadena resultado
Esto explica la utilizacin del mtodo [Link] donde el argumento puede o e estar formado por una concatenacin de cadenas para formar la cadena resultado nal que o se desea mostrar en pantalla. Los tipos simples, adems, promocionan a un objeto de tipo a String cuando son concatenados con una cadena. De manera que, por ejemplo, la forma de mostrar en pantalla un mensaje como el siguiente:
x = 100
donde 100 es el valor de una variable entera declarada de esta manera: int x=100;, ser a
[Link]("x = "+x);
La clase String es el mecanismo que proporciona Java para la manipulacin de cadenas o de la misma manera que otros lenguajes no orientados a objetos lo hacen mediante librer as de subrutinas. A travs de los mtodos de la clase es posible realizar aquellas operaciones e e ms comunes sobre las cadenas de caracteres. Destacan los siguientes mtodos: a e length(): Devuelve la longitud de una cadena. Nota: obsrvese que length() es un mtodo e e de la clase String que no hay que confundir con el atributo length de los vectores. equals( String st ): Devuelve un valor lgico indicando si la cadena st pasada como o argumento es igual o no a aquella sobre la que se llama el mtodo. e charAt( int i ): Devuelve el carcter (tipo char) correspondiente a la posicin i, comena o zando desde 0 como en las matrices. Para el ejemplo siguiente:
public class Ejemplo { public static void main(String args[]) { String animal1 = "elefante"; String animal2 = "len"; o String animal3 = "len"; o [Link]("El primer y el segundo animal "+ ([Link](animal2)?"si":"no")+" son iguales" ); [Link]("El segundo y el tercer animal "+ ([Link](animal3)?"si":"no")+" son iguales" ); [Link]("La tercera letra de "+ animal1+" es la \""+[Link](2)+"\""); [Link]("El nmero de letras de \""+ u animal2+"\" es "+[Link]()); } }
3.7. Paquetes
73
3.7.
Paquetes
Los paquetes de Java son contenedores de clases que se utilizan para mantener el espacio de nombres dividido en compartimentos. Las clases se almacenan en dichos paquetes y tienen un nombre unico dentro de dicho paquete de manera que ninguna clase pueda entrar en conicto con otra clase con el mismo nombre pero en distinto paquete, ya que el nombre del paquete las diferencia. Los paquetes pueden estar a su vez dentro de otros paquetes estructurndose as de manera jerrquica. a a La denicin de un paquete consiste simplemente en la inclusin de la palabra reservada o o package como primera sentencia de una archivo Java. Por ejemplo, cualquier clase que se dena dentro del archivo que posee como primera l nea la siguiente:
package mi_paquete;
pertenecer al paquete mi_paquete. a Si se omite la sentencia package, las clases se asocian al paquete por defecto, que no tiene nombre. Java utiliza los directorios del sistema de archivos para almacenar los paquetes. El paquete mi_paquete declarado ms arriba ha de encontrarse en el directorio mi_paquete. Se a pueden crear subpaquetes dentro de otros paquetes respetando la jerarqu de directorios, a siendo la forma general de declaracin la siguiente: o
package paq1[.paq2[.paq3]];
hasta el nivel de profundidad necesario y, por supuesto, coincidiendo con la jerarqu de direca torios apropiada. Esta jerarqu de la que se ha hablado hacer referencia a los cheros objeto a Java, es decir, los .class. La organizacin de los cdigos fuente (.java) es responsabilidad o o del programador y no tiene porqu seguir esta estructura jerrquica. e a Dentro de un paquete, slo son visibles aquellas clases que pertenecen a dicho paquete. o La utilizacin de clases que se encuentran en otros paquetes diferentes al que pertenecen o las clases que se estn implementando en el chero actual se lleva a cabo mediante su a importacin. Por ejemplo, si se desea utilizar la clase mi_clase denida en el paquete o mi_paquete en el chero actual, se ha de incluir al principio de dicho chero (siempre despus de la declaracin de paquete) la siguiente instruccin: e o o
import mi_paquete.mi_clase;
Asimismo, es posible importar todas las clases del paquete mediante el comod *, n
import mi_paquete.*;
para poder as tener acceso a todas las clases de dicho paquete. La importacin de clases mediante el mecanismo explicado no es necesaria, por ejemplo, o si van a ser slo unas pocas las clases importadas. Siempre es posible denotar una clase de o otro paquete utilizando su nombre completo, por ejemplo,
[Link] cal = new [Link]();
Tampoco es necesaria para el caso particular del paquete [Link] del JDK de Java.
74
3.7.1.
La variable de entorno CLASSPATH contiene la ra de la jerarqu de directorios que z a utiliza el compilador de Java para localizar los paquetes. Para que funcione adecuadamente la clasicacin de clases dentro de paquetes es necesario o tener en cuenta algunas cuestiones: Crear el directorio con el mismo nombre que el paquete que se desea crear, por ejemplo, mi_paquete. Asegurarse que el chero con el bytecode correspondiente a las clases que pertenecen a dicho paquete (contienen la sentencia package.mi_paquete;) se encuentran en dicho directorio. Esto se puede omitir si se utiliza la opcin -d del compilador de Java. Mediante esta o opcin, el compilador coloca las clases (.class) en el directorio y subdirectorios correo spondientes, creando incluso las carpetas necesarias si stas no existen, para almacenar e las clases y reejar la estructura elegida segn los paquetes creados. u Para ejecutar una clase (por ejemplo, mi_clase) de dicho paquete es necesario indicar al intrprete Java el paquete y la clase que se desea ejecutar, e
java mi_paquete.mi_clase
Introducir en la variable CLASSPATH el directorio ra del paquete donde se encuentran z las clases que se van a utilizar para que el intrprete de Java las encuentre. e Ejemplos de valores de la variable CLASSPATH son: Windows. Para darle valores a dicha variable abrir amos una ventana del intrprete de e comandos de MS-DOS:
C:> set CLASSPATH=C:MisClasesDeJava;C:MisOtrasClasesDeJava
Hay que tener en cuenta que el comando exacto depende del tipo de shell de linux. La ejecucin de aplicaciones Java que utilizan paquetes denidos por el usuario tiene o algunos detalles a tener en cuenta por lo que se recomienda ver el Ejercicio 2.
3.8.
Las aplicaciones Java que se ejecutan en el entorno de ejecucin del sistema operativo o pueden recibir argumentos. Los argumentos son cadenas de texto, es decir, secuencias de caracteres. Cada cadena se diferencia de las adyacentes por caracteres separadores (espacios en blanco, tabuladores, . . . ). Estos argumentos son pasados a la aplicacin Java en un vector o de caracteres en la llamada al mtodo main. Por ejemplo, en el siguiente cdigo e o
3.9. Ejercicios resueltos public class Planetas { public static void main(String[] args) { Sistema_Solar s = new Sistema_Solar(args); s.mostrar_planetas(); } }
75
el argumento args del mtodo main es un vector de referencias a objetos de tipo String. e En la llamada al mtodo main: e 1. se instancia automticamente este vector con un tamao igual al nmero de argumena n u tos, 2. para cada argumento se crea un objeto de tipo String con el argumento, y 3. dicho objeto pasa a ser referenciado por el elemento del vector args correspondiente. Teniendo en cuenta la clase Sistema_Solar denida en el apartado 3.5, si ejecutamos la clase Planetas con el siguiente comando y argumentos:
> java Planetas Sol Mercurio Venus Tierra Marte Jpiter Saturno Urano u Neptuno Plutn o
Los argumentos pasados a una aplicacin Java siempre son cadenas de caracteres. Si se o desea pasar otros tipos de datos como nmeros es necesario realizar una conversin de tipo u o String al tipo de dato deseado. El paquete [Link] contiene clases que proporcionan esta funcionalidad. Por ejemplo, mediante
int n = [Link](args[0]);
3.9.
Ejercicios resueltos
Ejercicio 1 Implementacin de una lista simple (simplemente enlazada) de objetos de clase o Elemento que a ade nuevos elementos en la cabeza (void add(Elemento e) y los extrae n del nal Elemento get(). La clase ListaSimple debe tener un mtodo para obtener el e nmero de elementos en la lista actual. u Solucin: o
3 Esto es as suponiendo que la cadena de texto corresponde a un n mero entero. En realidad, este cdigo u o no puede ejecutarse aisladamente, es necesario realizarlo bajo el control de excepciones que se ve en el Cap tulo 5.
76
Cap tulo 3. Fundamentos de la Programacin Orientada a Objetos con Java o class Elemento { } class Elem { Elem sig; private Elemento elemento; public Elem( Elemento elemento, Elem e ) { [Link] = elemento; sig = e; } public Elemento get() { return elemento; }
class ListaSimple { private Elem primero; private int elementos; public ListaSimple() { primero = null; elementos = 0; } public void add( Elemento e ) { primero = new Elem( e, primero ); elementos++; } public Elemento get( ) { Elemento elemento = null; if( primero!=null ) { if( [Link]==null ) { elemento = [Link](); primero = null; } else { Elem ant = primero; Elem e = primero; while( [Link]!=null ) { ant = e; e = [Link]; } elemento = [Link](); [Link] = null; } elementos--; } return elemento; } public int size() { return elementos;
77
Ejercicio 2 Seguir los siguientes pasos para crear una clase dentro de un paquete denido por el usuario y utilizarla desde otra (las instrucciones proporcionadas son para un terminal de usuario linux). Crear un directorio para el paquete:
mkdir mi_paquete
y crear la clase
import mi_paquete.Mi_clase; class Clase_principal { public static void main(String[] args) { Mi_clase a = new Mi_clase(); } }
Si no aparecen errores en la compilacin y la ejecucin es que los pasos se han seguido o o correctamente. El resultado ha sido la creacin de un paquete con una clase que puede o ser utilizada desde el paquete por defecto (paquete sin nombre) al que pertenece la clase principal de la aplicacin Java. La variable CLASSPATH debe contener un punto en la lista o de directorios.
78
Ejercicio 3 Implementar una estructura tipo pila de nmeros enteros sin talla mxima. u a Utilizar una lista enlazada y clases internas. Solucin: o
class Pila { private Elemento primero; private int n; class Elemento { Elemento next; int valor; Elemento( int valor ) { next = primero; [Link] = valor; } int get() { primero = next; return valor; } int getValor() { return valor; } } public Pila( ) { primero = null; n = 0; } public void add( int valor ) { primero = new Elemento( valor ); n++; } public int get( ) { int a = -11111; if( n>0 ) { a = [Link](); n--; } return a; } public int top( ) { int a = -11111; if( n>0 ) { a = [Link](); } return a; }
Puede observarse que la clase Pila denida ahora posee la misma interfaz que la clase Pila de la pgina 69, slo que el constructor ahora no recibe argumentos. La estructura a o
denida ahora utiliza una lista enlazada de objetos que son creados cada vez que se aade n
79
un nuevo nmero entero. Los elementos se eliminan de la lista cuando se extrae un elemento. u Utilizando una lista enlazada se elimina la restriccin de tener una talla mxima de elementos o a para la pila. Obsrvese que desde la clase interna Elemento se accede al atributo primero e de la clase envolvente Pila.
3.10.
Ejercicios propuestos
Ejercicio 1 Tomando como ejemplo la clase Pila mostrada en el apartado 3.4.1 (pgia na 69), implementar la clase Cola para formar una cola de tipo FIFO (First In First Out) de nmeros enteros. Esta estructura de datos se caracteriza porque el elemento que se extrae u (mtodo get()) debe ser el ms antiguo de la cola. e a Ejercicio 2 Implementar una clase Lista con la misma interfaz que la clase Lista del ejercicio resuelto 3.9 pero que aada elementos al nal y los extraiga de la cabeza. n Ejercicio 3 Utilizando el ejercicio anterior, modicar la clase Elemento para que almacene objetos de tipo String y crear un constructor de ListaSimple a partir de los argumentos pasados. En el programa principal crear una lista con la clase Lista implementada con los argumentos pasados por linea de comandos. Ejercicio 4 instanciada. Crear una clase que almacene en una variable el nmero de veces que es u
Ejercicio 5 Tomando como base el Ejercicio 2 crear un subpaquete de mi_paquete que contenga al menos una clase y utilizar la misma en la clase principal.
80
Cap tulo 4
Herencia y Polimorsmo
Tal y como se ha visto en cap tulos anteriores, una clase representa un conjunto de objetos que comparten una misma estructura y comportamiento. La estructura se determina mediante un conjunto de variables denominadas atributos, mientras que el comportamiento viene determinado por los mtodos. Ya se ha visto en cap e tulos anteriores que esta forma de estructurar los programas ofrece grandes ventajas, pero lo que realmente distingue la programacin orientada a objetos (POO) de otros paradigmas de programacin es la capacidad o o que tiene la primera de denir clases que representen objetos que comparten slo una parte o de su estructura y comportamiento, esto es, que representen objetos con ciertas similitudes, pero no iguales. Estas similitudes pueden expresarse mediante el uso de la herencia y el polimorsmo. Estos dos mecanismos son, sin lugar a dudas, pilares bsicos de la POO. a
4.1.
Herencia
La herencia es el mecanismo que permite derivar una o varias clases, denominadas subclases, de otra ms genrica denominada superclase. La superclase rene aquellos atributos a e u y mtodos comunes a una serie de subclases, mientras que estas ultimas unicamente denen e aquellos atributos y mtodos propios que no hayan sido denidos en la superclase. Se dice e que una subclase extiende el comportamiento de la superclase. En ocasiones se utiliza el trmino clase base para referirse a la superclase y clase derivada para referirse a la subclase. e En la gura 4.1 se representa grcamente el concepto de herencia. a
Figura 4.1: La clase B (subclase o clase derivada) hereda los atributos y mtodos de la clase e A (superclase o clase base).
81
82
4.1.1.
Conceptos bsicos a
La herencia en Java se implementa simplemente especicando la palabra reservada extends en la denicin de la subclase: o
class nombreSubclase extends nombreSuperclase { // Atributos y mtodos especficos de la subclase e }
Por ejemplo:
class Esfera { private double radio; public double setRadio( double r ) { radio = r; } public double superficie() { return 4*[Link]*radio*radio; }
class Planeta extends Esfera { private int numSatelites; } public int setNumSatelites( int ns ) { numSatelites = ns; }
En este ejemplo la clase Planeta hereda todos los mtodos y atributos denidos en Esfera e que no sean privados. Al n y al cabo, un planeta tambin es una esfera (despreciaremos las e pequeas deformaciones que pueda tener). En el siguiente fragmento de cdigo vemos cmo n o o es posible acceder a los atributos y mtodos heredados. e
Planeta tierra = new Planeta(); [Link]( 6378 ); // (Km.) [Link]( 1 ); [Link]("Superficie = " + [Link]());
En el ejemplo anterior puede observarse cmo los objetos de la clase Planeta utilizan o los mtodos heredados de Esfera (setRadio y superficie) exactamente del mismo modo e a como utiliza los denidos en su misma clase (setNumSatelites). Cuando se dene un esquema de herencia, la subclase hereda todos los miembros (mtodos e y atributos) de la superclase que no hayan sido denidos como privados. Debe observarse que en el ejemplo anterior la clase Planeta no hereda el atributo radio. Sin embargo, esto no quiere decir que los planetas no tengan un radio asociado, sino que no es posible acceder directamente a ese atributo. En realidad los objetos de tipo Planeta pueden establecer su radio a travs del mtodo setRadio. Debe remarcarse que es una prctica muy habitual e e a denir como privados los atributos de una clase e incluir mtodos pblicos que den acceso a e u dichos atributos, tal y como se explic en el apartado 3.4. o Veamos un nuevo ejemplo sobre el uso de la herencia. Supongamos que queremos implementar un juego en el que una nave debe disparar a ciertas amenazas que se le aproximan. De forma muy simplista, podr amos denir el comportamiento de nuestra nave mediante el siguiente cdigo: o
4.1. Herencia class Nave { private int posX, posY; // Posicin de la nave en la pantalla o private int municion; Nave() { posX = 400; posY = 50; municion = 100; } void moverDerecha( int dist ) { posX += dist; } void moverIzquierda( int dist ) { posX -= dist; } void disparar() { if( municion > 0 ) { municion--; // Cdigo necesario para realizar disparo o } } void dibujar() { // Obviaremos la parte grfica y nos limitaremos a mostrar a // un texto por la consola [Link]("Nave en " + posX + "," + posY); [Link]("Municin restante: " + municion); o } }
83
Supongamos ahora que tras superar cierto nivel en el juego, podemos disponer de otra nave con mayores prestaciones (por ejemplo, un escudo protector). En este caso podemos tomar como base la nave anterior e implementar una segunda clase que contenga unicamente la nueva funcionalidad.
class NaveConEscudo extends Nave { private boolean escudo; NaveConEscudo() { escudo = false; } void activarEscudo() { escudo = true; } void desactivarEscudo() { escudo = false; } }
Ahora ser posible hacer uso de esta nueva nave en nuestro juego. a
class Juego { public static void main(String[] args) { NaveConEscudo miNave = new NaveConEscudo(); [Link](20); [Link](); [Link](); [Link]();
84 }
El aspecto interesante del ejemplo anterior es observar que la subclase NaveConEscudo incluye todos los elementos de la clase Nave tal y como si hubiesen sido denidos en la propia clase NaveConEscudo. Esta caracter stica permite denir con un m nimo esfuerzo nuevas clases basadas en otras ya implementadas, lo que redunda en la reutilizacin de o cdigo. Adems, si en el uso de la herencia nos basamos en clases cuyo cdigo ya ha sido o a o vericado y el cual se supone exento de errores, entonces ser ms fcil crear programas a a a robustos, ya que unicamente deberemos preocuparnos por validar la nueva funcionalidad aadida en la subclase. n Por supuesto ser posible crear objetos de tipo Nave en lugar de NaveConEscudo, pero en a este caso no se podr ejecutar el mtodo [Link](). En el ejemplo mostrado, a e los objetos de la clase NaveConEscudo disparan y se dibujan exactamente igual a como lo hacen los objetos de la clase Nave, ya que estos mtodos han sido heredados. e Podr amos pensar que a nuestra nueva nave no se le permita disparar cuando tiene activado el escudo, o que se dibuje de forma distinta a como lo hac la nave bsica. Ello a a implica que la clase NaveConEscudo no slo introduce nuevas prestaciones respecto de la o clase Nave, sino que adems debe modicar parte del comportamiento ya establecido. En el a apartado 4.2 veremos qu mecanismos nos permiten abordar este problema. e Hay que mencionar que en Java no existe la herencia mltiple debido a los problemas u que ello genera. Si se permitiera un esquema de herencia como el mostrado en la gura 4.2 implicar que la clase C heredar tanto los mtodos denidos en A como los denidos en B. a a e Podr ocurrir que tanto A como B contuvieran un mtodo con el mismo nombre y parmetros a e a pero distinta implementacin (distinto cdigo), lo que provocar un conicto acerca de cul o o a a de las dos implementaciones heredar.
4.1.2.
Una subclase puede referirse a la superclase inmediata mediante el uso de la palabra reservada super. Esta palabra reservada puede utilizarse de dos formas: Para llamar al constructor de la superclase.
super( lista_de_argumentos_del_constructor );
4.1. Herencia
85
Para acceder a un miembro de la superclase que ha sido ocultado. Por ejemplo, si declaramos en la subclase un miembro con el mismo nombre que tiene en la superclase, el miembro de la superclase quedar ocultado. En este caso ser posible invocarlo a a mediante el uso de super.
[Link] = . . . ; [Link]( lista_de_argumentos );
Estos dos modos de usar la palabra super se vern con ms detalle en los apartados 4.1.3 a a y 4.2.1 respectivamente.
4.1.3.
Constructores y herencia
A diferencia de lo que ocurre con los mtodos y atributos no privados, los constructores no e se heredan. Adems de esta caracter a stica, deben tenerse en cuenta algunos aspectos sobre el comportamiento de los constructores dentro del contexto de la herencia, ya que dicho comportamiento es sensiblemente distinto al del resto de mtodos. e Orden de ejecucin de los constructores o Cuando existe una relacin de herencia entre diversas clases y se crea un objeto de una o subclase S, se ejecuta no slo el constructor de S sino tambin el de todas las superclases de o e S. Para ello se ejecuta en primer lugar el constructor de la clase que ocupa el nivel ms alto a en la jerarqu de herencia y se contina de forma ordenada con el resto de las subclases1 . a u El siguiente ejemplo ilustra este comportamiento:
class A { A() { [Link]("En A"); } } class B extends A { B() { [Link]("En B"); } } class C extends B { C() { [Link]("En C"); } } class Constructores_y_Herencia { public static void main(String[] args) { C obj = new C(); } }
86
Qu constructor se ejecuta en la superclase? Uso de super() e Como ya se ha visto en cap tulos anteriores, es posible que una misma clase tenga ms de a un constructor (sobrecarga del constructor), tal y como se muestra en el siguiente ejemplo:
class A { A() { [Link]("En A"); } A(int i) { [Link]("En A(i)"); } } class B extends A { B() { [Link]("En B"); } B(int j) { [Link]("En B(j)"); } }
La cuestin que se plantea es: qu constructores se invocarn cuando se ejecuta la o e a sentencia B obj = new B(5); ? Puesto que hemos creado un objeto de tipo B al que le pasamos un entero como parmetro, parece claro que en la clase B se ejecutar el constructor a a B(int j). Sin embargo, puede haber confusin acerca de qu constructor se ejecutar en o e a A. La regla en este sentido es clara: mientras no se diga expl citamente lo contrario, en la superclase se ejecutar siempre el constructor sin parmetros. Por tanto, ante la sentencia a a B obj = new B(5), se mostrar a:
En A En B(j)
Hay, sin embargo, un modo de cambiar este comportamiento por defecto para permitir ejecutar en la superclase un constructor diferente. Para ello debemos hacer uso de la sentencia super(). Esta sentencia invoca uno de los constructores de la superclase, el cual se escoger en funcin de los parmetros que contenga super(). a o a En el siguiente ejemplo se especica de forma expl cita el constructor que deseamos ejecutar en A:
class A { A() { [Link]("En A"); } A(int i) { [Link]("En A(i)"); } } class B extends A { B() { [Link]("En B"); } B(int j) { super(j); // Ejecutar en la superclase un constructor // que acepte un entero [Link]("En B(j)"); } }
87
Ha de tenerse en cuenta que en el caso de usar la sentencia super(), sta deber ser e a obligatoriamente la primera sentencia del constructor. Esto es as porque se debe respetar el orden de ejecucin de los constructores comentado anteriormente. o En resumen, cuando se crea una instancia de una clase, para determinar el constructor que debe ejecutarse en cada una de las superclases, en primer lugar se exploran los constructores en orden jerrquico ascendente (desde la subclase hacia las superclase). Con a este proceso se decide el constructor que debe ejecutarse en cada una de las clases que componen la jerarqu Si en algn constructor no aparece expl a. u citamente una llamada a super(lista_de_argumentos) se entiende que de forma impl cita se est invocando a a super() (constructor sin parmetros de la superclase). Finalmente, una vez se conoce el a constructor que debe ejecutarse en cada una de las clases que componen la jerarqu stos a, e se ejecutan en orden jerrquico descendente (desde la superclase hacia las subclases). a Prdida del constructor por defecto e Podemos considerar que todas las clases en Java tienen de forma impl cita un constructor por defecto sin parmetros y sin cdigo. Ello permite crear objetos de dicha clase sin a o necesidad de incluir expl citamente un constructor. Por ejemplo, dada la clase
class A { int i; }
ya que, aunque no lo escribamos, la clase A lleva impl cito un constructor por defecto:
class A { int i; A(){} // Constructor por defecto }
Sin embargo, es importante saber que dicho constructor por defecto se pierde si escribimos cualquier otro constructor. Por ejemplo, dada la clase
class A { int i; A( int valor) { i=valor; } }
La sentencia A obj = new A() generar un error de compilacin, ya que en este caso no a o existe ningn constructor en A sin parmetros. Hemos perdido el constructor por defecto. Lo u a correcto ser por ejemplo, A obj = new A(5). a, Esta situacin debe tenerse en cuenta igualmente cuando exista un esquema de herencia. o Imaginemos la siguiente jerarqu de clases: a
88 class A { int i; A( int valor) { i=valor; } } class B extends A { int j; B( int valor) { j=valor; } }
En este caso la sentencia B obj = new B(5) generar igualmente un error ya que, puesto a que no hemos especicado un comportamiento distinto mediante super(), en A deber a ejecutarse el constructor sin parmetros, sin embargo, tal constructor no existe puesto que a se ha perdido el constructor por defecto. La solucin pasar por sobrecargar el constructor o a de A aadiendo un constructor sin parmetros, n a
class A { int i; A() { i=0; } A( int valor) { i=valor; } } class B extends A { int j; B( int valor) { j=valor; } }
o bien indicar expl citamente en el constructor de B que se desea ejecutar en A un constructor que recibe un entero como parmetro, tal y como se muestra a continuacin: a o
class A { int i; A( int valor) { i=valor; } } class B extends A { int j; B( int valor) { super(0); j=valor; } }
4.1. Herencia
89
4.1.4.
Modicadores de acceso
Los modicadores de acceso public y private descritos en el apartado 3.4.1 del cap tulo anterior se utilizan para controlar el acceso a los miembros de una clase. Existe un tercer modicador de acceso, protected que tiene sentido utilizar cuando entra en juego la herencia. Cuando se declara un atributo o mtodo protected, dicho elemento no ser visible desde e a aquellas clases que no deriven de la clase donde fue denido y que, adems, se encuentren a en un paquete diferente. En la tabla 4.1 se resume la visibilidad de los atributos y mtodos en funcin de sus e o modicadores de acceso.
private
Misma clase Subclase del mismo paquete No subclase del mismo paquete Subclase de diferente paquete No subclase de diferente paquete
S No No No No
protected
public
S S S S No
S S S S S
4.1.5.
La clase Object
La clase Object es una clase especial denida por Java. Object es la clase base de todas las dems clases, de modo que cualquier clase en Java deriva directa o indirectamente de a sta, sin necesidad de indicarlo expl e citamente mediante la palabra extends. Esto signica que en Java existe un unico rbol jerrquico de clases en el que estn incluidas todas, donde a a a la clase Object gura en la ra de dicho rbol. z a A continuacin se muestra un ejemplo en el que se observa que las clases que no llevan la o clusula extends heredan impl a citamente de Object. La gura 4.3 muestra el esquema de herencia de este ejemplo.
class . . } class . . } A { . // Al no poner extends, deriva implcitamente de Object // Deriva de A y, por tanto, de Object
B extends A { .
En la clase Object existen varios mtodos denidos, entre los cuales guran: e boolean equals(Object o): compara dos objetos para ver si son equivalentes y devuelve un valor booleano. String toString(): devuelve una cadena de tipo String que conteniene una descripcin o del objeto. Este mtodo se llama impl e citamente cuando se trata de imprimir un objeto con println() o cuando se opera con el objeto en una suma (concatenacin) de o cadenas. void finalize(): este mtodo es invocado automticamente por el garbage collector cuane a do se determina que ya no existen ms referencias al objeto. a Para que estos mtodos sean de utilidad, las subclases de Object debern sobreescribirlos, e a tal y como se explica en el apartado 4.2.7.
90
Figura 4.3: En Java existe un unico rbol jerrquico, en el que la clase Object ocupa el nivel a a ms alto. a
4.2.
Polimorsmo
Una de las caracter sticas de la herencia es que una referencia de una clase base puede utilizarse para referenciar objetos de cualquir otra clase heredada (este concepto se explica en el apartado 4.2.3). Esta caracter stica, tan simple y a la vez tan potente, permite implementar lo que se conoce como polimorsmo. El trmino polimorsmo hace referencia a la capacidad que tienen algunos mtodos de e e presentar mltiples formas, en el sentido de que una llamada a un mismo mtodo puede u e ofrecer comportamientos distintos en funcin del contexto en el que se ejecute. El polimorso mo, como se ver ms adelante, dota a los lenguajes orientados a objetos de una potencia de a a la que carecen otros lenguajes, ya que permite realizar ciertas abstracciones sobre los tipos de datos con los que se trabaja. Es imposible tratar y entender el polimorsmo sin antes abordar una serie de conceptos relacionados. Estos conceptos son independientes del tema del polimorsmo, pero necesarios para su comprensin. o
4.2.1.
Sobreescritura de mtodos e
La herencia permite crear subclases que contienen todos los atributos y mtodos no e privados de la clase superior. Habitualmente en la subclase se denirn, adems, atributos a a y mtodos propios que no exist en la superclase. En ocasiones, adems interesa que la e an a subclase modique alguno de los mtodos heredados para que tengan un comportamiento e distinto. Esto se realiza mediante la sobreescritura de mtodos. Simplemente consiste en e implementar en la subclase un mtodo que ya exist en la superclase. e a
class Esfera { double radio; void mostrarValores() { [Link]("R=" + radio); }
4.2. Polimorsmo int numSatelites; void mostrarValores() { [Link]("R=" + radio + "Num. Sat.=" + numSatelites); }
91
En este ejemplo la clase Planeta hereda el mtodo mostrarValores de Esfera, pero e luego lo sobreescribe para poder mostrar tanto el radio como el nmero de satlites. En esta u e situacin el mtodo mostrarValores ejecutar un cdigo distinto dependiendo del objeto o e a o sobre el que se invoque.
public class EjemploSobreescritura { public static void main(String[] args) { Esfera e = new Esfera(); [Link]=10; Planeta p = new Planeta(); [Link]=6378; [Link]=1; [Link](); // Muestra los valores de la esfera [Link](); // Muestra los valores del planeta } }
En el ejemplo dado en el apartado 4.1.1 se den la clase NaveConEscudo a partir a de la clase Nave. Nuestra nueva nave permit denir un escudo tal y como se muestra a a continuacin: o
class Nave { int posX, posY; int municion; Nave() { . . . } void moverDerecha( int dist ) { . . . } void moverIzquierda( int dist ) { . . . } void disparar() { if( municion > 0) municion--; } void dibujar() { . . . } } class NaveConEscudo extends Nave { boolean escudo; NaveConEscudo() { escudo = false; } void activarEscudo() { escudo = true; } void desactivarEscudo() { escudo = false; } }
En este ejemplo podr ser interesante que NaveConEscudo modicase el comportamiento a de alguno de los mtodos heredados de Nave. Por ejemplo, que no pueda disparar si tiene e el escudo activado. Para ello, tal y como se ha visto, bastar con sobreescribir el mtodo a e correspondiente en la subclase:
92
Cap tulo 4. Herencia y Polimorsmo class NaveConEscudo extends Nave { boolean escudo; NaveConEscudo() { escudo = false; } void activarEscudo() { escudo = true; } void desactivarEscudo() { escudo = false; } // Sobreescribimos el mtodo disparar e void disparar() { if( escudo == false && municion > 0 ) municion--; } }
De este modo cada nave tiene su propia versin del mtodo disparar. De forma similar o e podr sobreescribirse el mtodo dibujar para que cada tipo de nave tuviese una reprea e sentacin grca distinta. o a Reemplazo y renamiento La sobreescritura de un mtodo podemos enfocarla desde dos puntos de vista: reescribir e el mtodo completamente ignorando el cdigo que hubiese en la superclase, o ampliar el e o cdigo existente en la superclase con nuevas instrucciones. En el primer caso hablaremos o de reemplazo y en el segundo de renamiento. Los ejemplos vistos anteriormente corresponden a sobreescritura mediante reemplazo. Para implementar la sobreescritura mediante renamiento se debe invocar desde el mtodo sobreescrito de la subclase el mtodo corree e spondiente de la superclase mediante el uso de super. Por ejemplo podr amos pensar que una NaveConEscudo se dibuja como una Nave con algunos elementos extras. En este caso ser ms interesante el renamiento que el reemplazo, ya que aprovechar a a amos el cdigo del o mtodo dibuja escrito para la clase Nave. e
class Nave { . . . void dibujar() { // Cdigo para dibujar una nave bsica o a } } class NaveConEscudo extends Nave { . . . void dibujar() { [Link](); // Primero dibujamos una nave bsica. a // Cdigo para dibujar los elementos extra o . . .
Uso del modicador nal para evitar la sobreescritura Ya se vio que el modicador final puede aplicarse a la declaracin de una variable para o asegurarnos de que el valor de la misma no cambie tras su inicializacin. Cuando entra en o juego la herencia, puede tener sentido adems aplicar el modicador final a la declaracin a o de mtodos. En este caso, un mtodo declarado como final no puede ser sobreescrito en las e e subclases que hereden el mtodo. e
4.2. Polimorsmo class A { final void metodo1() { . . . } } class B extends A { // Esta clase no puede sobreescribir metodo1() }
93
4.2.2.
En este punto conviene realizar una distincin importante entre lo que es la declaracin o o de una referencia y la instanciacin de un objeto al cual apuntar dicha referencia. En todos o a los ejemplos vistos hasta ahora se declaraban referencias de un determinado tipo (una clase) y despus se instanciaba un objeto de esa misma clase, el cual quedaba apuntado por la e referencia. Por ejemplo:
Esfera e = new Esfera();
En este caso vemos que el tipo (clase) de la referencia coincide con el tipo de objeto que se crea, sin embargo, esto no tiene por qu ser siempre as De hecho es posible declarar una e . referencia de una clase determinada e instanciar un objeto, no de dicha clase, sino de una subclase de la misma. Por ejemplo, dada la siguiente relacin de clases o
class Esfera { double radio; double superficie() { return 4*[Link]*radio*radio; }
Conviene diferenciar el tipo esttico de una variable del tipo dinmico. Denominamos a a tipo esttico al tipo con el que se declara una variable referencia, y tipo dinmico al tipo del a a objeto al que apunta dicha referencia. En el primer ejemplo mostrado, tanto el tipo esttico a como el tipo dinmico de la variable e es Esfera. Por el contrario, en el segundo ejemplo se a ha declarado una variable e de tipo Esfera (tipo esttico), pero, el objeto creado es de tipo a Planeta (tipo dinmico). a
4.2.3.
Tal y como se ha visto en el apartado anterior, es posible declarar una referencia de un tipo esttico determinado e instanciar un objeto, no de dicha clase, sino de una subclase de la a misma. Esto es, es posible que el tipo dinmico sea una subclase del tipo esttico. Este modo a a de referenciar objetos en los que el tipo referencia corresponde a una superclase del objeto referenciado es lo que denominamos conversin hacia arriba. El trmino hacia arriba viene o e
94
motivado por la forma en que se representa grcamente la herencia, donde las superclases a aparecen arriba de las subclases. En la conversin hacia arriba se referencia un objeto dado o mediante una referencia perteneciente a un tipo o clase que jerrquicamente est arriba a a de la clase correspondiente al objeto creado. Cuando se utiliza la conversin hacia arriba es importante tener en cuenta una limitacin: o o si creamos un objeto de un tipo determinado y empleamos una referencia de una superclase para acceder al mismo, a travs de dicha referencia unicamente podremos ver la parte del e objeto que se deni en la superclase. Dicho de otro modo, el tipo esttico limita la interfaz, o a esto es, la visibilidad que se tiene de los atributos y mtodos de un objeto determinado. e Tomemos como ejemplo las siguientes clases:
class A { public void m1() { . . . } } class B extends A { public void m2() { . . . } }
Supongamos ahora que en algn punto de nuestro programa se crean los siguientes objeu tos:
B obj1 = new B(); A obj2 = new B(); B obj3 = new A(); // Tipo esttico y dinmico de obj1: B a a // Tipo esttico A y tipo dinmico B a a // Tipo esttico B y tipo dinmico A. ERROR! a a
Figura 4.4: Dadas una superclase A y una subclase B, se crean objetos de distinta manera. (a) Tipo esttico=B, tipo dinmico=B: se tiene acceso a todo el objeto. (b) Tipo esttico=A, tipo a a a dinmico=B (conversin hacia arriba): se tiene acceso unicamente a la parte de A. (c) Tipo a o esttico=B, tipo dinmico=A: Error. Si se permitiera, se deber tener acceso a elementos a a a que realmente no han sido creados. En la gura 4.4 se muestra cmo, a partir de la referencia obj1 se tiene acceso tanto al o mtodo m1() como a m2(). Sin embargo, con la referencia obj2, a pesar de que referencia a e
4.2. Polimorsmo
95
un objeto de tipo B, unicamente se tiene acceso a los mtodos denidos en A, ya que el tipo e esttico impone esta limitacin. a o Retomando el ejemplo de la Esfera del apartado anterior, sobreescribamos ahora alguno de los mtodos denidos en la superclase. Por ejemplo, supongamos que se desea medir la e supercie de un planeta con mayor precisin, teniendo en cuenta el achatamiento existente o en los polos. Para ello podr amos redenir la clase Planeta como sigue:
class Esfera { double radio; double superficie() { return 4*[Link]*radio*radio; }
class Planeta extends Esfera { int numSatelites; double coeficienteAchatamiento; Planeta(double r, double c) { radio = r; coeficienteAchatamiento = c; } // Sobreescribimos el mtodo superficie e double superficie() { // Clculo preciso de la superficie teniendo en cuenta a // el coeficiente de achatamiento }
El tipo esttico Esfera contiene el mtodo superficie, por lo tanto dicho mtodo es a e e visible a travs de la referencia e, sin embargo es importante entender que se ejecutar el e a mtodo del objeto que hemos creado (en este caso de tipo Planeta). Puesto que el mtodo e e superficie ha sido sobreescrito en la clase Planeta se ejecutar esta nueva versin del a o mtodo. e A modo de resumen diremos que el tipo esttico dictamina QUE operaciones pueden a realizarse, pero es el tipo dinmico el que determina COMO se realizan. Esta caracter a stica tiene una enorme transcendencia en la programacin orientada a objetos ya que, como se o ver en apartados posteriores, permite realizar ciertas abstracciones sobre los tipos de datos a (clases) con los que se trabaja. Existen algunas restricciones lgicas en la utilizacin de tipos estticos y dinmicos diso o a a tintos. Una variable referencia con un tipo esttico determinado puede tener, como se ha a visto, un tipo dinmico que sea derivado del tipo esttico, pero no a la inversa. Esto es, la a a siguiente declaracin no ser correcta: o a
Planeta p = new Esfera();
La restriccin impuesta es lgica por varios motivos. Si se declara una referencia con el o o tipo esttico Esfera, sta puede ser utilizada para referenciar un Planeta. Es como decir a e
96
que un planeta tambin es una esfera. Sin embargo, no tiene tanto sentido declarar una e referencia de tipo Planeta e intentar referirse con ella a un objeto de tipo Esfera, ya que ser como decir que cualquier esfera es un planeta. a Desde el punto de vista semntico del lenguaje existe una razn todav ms contundente. a o a a Si declaramos una referencia de tipo esttico Planeta estamos indicando que con dicha a referencia podemos acceder a cualquier miembro de la clase Planeta (el tipo esttico indica a qu se puede hacer), esto es, deber e amos poder hacer
Planeta p; . . . [Link]=1;
Sin embargo, si dicha referencia la utilizamos para instanciar un objeto de tipo Esfera
Planeta p; p = new Esfera();
resultar incorrecta. De algn modo estar a u amos dando al tipo esttico un alcance o visia bilidad mayor de lo que realmente existe en el tipo dinmico, tal y como se muestra en la a gura 4.4(c), lo que generar inconsistencias como la que acabamos de exponer. a Por qu es interesante la conversin hacia arriba? e o La conversin hacia arriba no es un capricho del lenguaje, sino que tiene una razn de o o ser. De hecho es una de las caracter sticas fundamentales de la POO, ya que es la base de cuestiones tan importantes como el polimorsmo o la abstraccin. Aunque estos conceptos se o estudiarn en detalle en los siguientes apartados, podemos avanzar que en ocasiones resulta a muy interesante poder llevar a cabo ciertas acciones sin necesidad de saber exactamente con qu tipo de objeto estamos trabajando. Con la conversin hacia arriba ignoramos el tipo real e o del objeto y lo tratamos de un modo ms genrico. Ello permite realizar ciertas abstracciones a e sobre los objetos con los que trabajamos, yendo de lo concreto a lo general. Tomemos el siguiente ejemplo: imaginemos que tenemos una serie de Dispositivos (ltros, amplicadores, conversores A/D, codicadores, etc.) los cuales pueden conectarse en serie para procesar una seal elctrica. Supongamos que la cadena de proceso permite cualquier n e secuencia de este tipo de dispositivos. Todos los dispositivos tienen en comn que poseen una u salida a la cual puede conectarse cualquier otro dispositivo. Por otro lado, cada dispositivo tendr ciertas caracter a sticas particulares que lo diferenciarn del resto. Podr a amos pensar en una clase base o superclase denominada Dispositivo de la que heredasen las clases Filtro, Amplificador, Conversor o Decodificador. Puesto que todos ellos tienen una salida a la que conectar otro dispositivo, podr amos poner un atributo que represente esta conexin en la clase base: o
class Dispositivo { Dispositivo salida; void conectar( Dispositivo d ) { salida = d; } // Resto de atributos y mtodos e
4.2. Polimorsmo . . .
97
Lo interesante de este ejemplo es que el atributo salida es del tipo genrico Dispositivo, e sin embargo, gracias a la conversin hacia arriba, esta referencia puede utilizarse para refero enciar objetos de cualquier tipo de dispositivo (ltro, amplicador, etc.). De modo similar, el mtodo conectar admite cualquier tipo de dispositivo. Podr parecer extrao que un e a n mtodo que acepta como parmetro objetos de un tipo determinado pueda llegar a aceptar e a objetos de otro tipo, sin embargo, tomando el ejemplo anterior, hemos de pensar que al n y al cabo, un ltro tambin es un tipo de dispositivo. e
4.2.4.
Con todo lo expuesto en los apartados anteriores, ahora estamos en condiciones de entender el mecanismo que permite implementar el polimorsmo en tiempo de ejecucin: el enlace o dinmico o seleccin dinmica de mtodo. Tomemos como ejemplo la siguiente jerarqu de a o a e a clases en la que se denen distintas guras geomtricas: e
class Figura { double area() { return 0; // No sabemos qu rea tiene una figura genrica e a e } } class Rectangulo extends Figura { double alto, ancho; Rectangulo( double al, double an) { alto = al; ancho = an; } double area() { return alto*ancho; } } class Circulo extends Figura { double radio; Circulo(double r) { radio = r; } double area() { return [Link]*radio*radio; } }
// Mostramos las reas de las figuras creadas a for(int i=0; i<10; i++) { double a = v[i].area(); // Seleccin dinmica de mtodo o a e [Link]("Area="+a); }
En este programa se crea un vector con 10 referencias de tipo Figura, esto es, el tipo esttico de cada referencia v[i] es Figura (la sentencia v = new Figura[10] crea un vector a con 10 referencias, pero stas todav no referencian ningn objeto). A continuacin se crean e a u o objetos de tipo Rectangulo y Circulo los cuales quedan referenciados por las referencias contenidas en v. Por tanto, el tipo dinmico de cada referencia v[i] es o bien Rectangulo a o bien Circulo (ntese el uso de la conversin hacia arriba estudiada en el apartado 4.2.3). o o Observemos que realmente no tenemos certeza sobre el tipo dinmico concreto ya que dicho a tipo se ha elegido de forma aleatoria, lo unico que sabemos es que, de un modo ms genrico, a e todos ellos son Figuras. Finalmente se calcula el rea de cada gura mediante la sentencia a v[i].area(). En esta situacin se tiene que el tipo esttico de v[i] es Figura, y puesto o a que el mtodo area est denido en dicho tipo, la sentencia v[i].area() es correcta (el e a tipo esttico indica QUE podemos hacer). Sin embargo, ya se ha explicado que realmente a se ejecutar el mtodo area denido en el tipo dinmico (el tipo dinmico indica COMO se a e a a hace). Se ejecutar por tanto el mtodo area del objeto que se haya creado en cada caso. a e Llegados a este punto no es dif observar que es imposible conocer en tiempo de compicil lacin cul de los mtodos area se va a ejecutar en cada caso, ya que hasta que no se ejecute o a e el programa no se van a conocer los tipos dinmicos de cada gura. En consecuencia debe a existir un mecanismo que en tiempo de ejecucin, y no en tiempo de compilacin, sea capaz o o de establecer qu mtodo ejecutar en funcin del tipo dinmico del objeto. Dicho mecanismo e e o a existe en la mquina virtual de Java y se conoce como enlace dinmico o seleccin dinmica a a o a de mtodo y es el que permite llevar a cabo el polimorsmo en tiempo de ejecucin. e o El polimorsmo es una caracter stica fundamental de los lenguajes orientados a objetos, que permite realizar ciertas abstracciones sobre los tipos de datos. Lo sorprendente del ejemplo anterior es que el programa principal no necesita saber qu tipo de objetos hay e almacenados en el vector v para poder realizar ciertas operaciones con ellos (calcular el rea a en este caso), es suciente con saber que, de forma genrica, son guras. Es ms, ser posible e a a crear nuevos tipos de guras, por ejemplo tringulos, y aadirlas en el vector v; el bucle que a n calcula el rea de cada una de ellas seguir funcionando sin necesidad de ningn cambio, a a u a pesar de que cuando se escribi ni siquiera exist la clase Triangulo. Esto es as porque o a realmente a la hora de escribir el bucle que calcula las reas no estamos concretando el mtoa e do area que deseamos ejecutar, sino que lo estamos posponiendo hasta el ultimo momento (la ejecucin del programa) lo que permite en cualquier momento crear nuevas guras, cada o una con su propio mtodo area. e Supongamos ahora que necesitamos un mtodo que calcule si dos guras cualesquiera e tienen o no la misma rea. Para ello podr a amos aadir a la clase Figura el mtodo mismaArea. n e
class Figura { double area() { return 0; // No sabemos qu rea tiene una figura genrica e a e
4.2. Polimorsmo } boolean mismaArea(Figura otra) { double a1 = area(); double a2 = [Link](); if( a1 == a2 ) return true; else return false; // O simplemente: return ( a1==a2 ); }
99
} class Rectangulo extends Figura { // Igual que en el ejemplo anterior } class Circulo extends Figura { // Igual que en el ejemplo anterior }
Ahora la clase Figura implementa dos mtodos, uno de los cuales (area) se sobreescribe e en las subclases y otro (mismaArea) que se hereda tal cual. Observemos ahora el siguiente fragmento de cdigo: o
Figura f1 = new Rectangulo(10,10); Figura f2 = new Circulo(5); if( [Link](f2) ) [Link]("Figuras con la misma rea"); a
El mtodo mismaArea denido en la clase Figura invoca a su vez al mtodo area, pero e e qu mtodo area se ejecuta en este caso? Tratemos de averiguar qu mtodo area se ejecuta e e e e en la llamada double a1 = area(). Un razonamiento errneo podr llevar a pensar que, o a puesto que estamos ejecutando el mtodo mismaArea que se encuentra en la clase Figura e (ya que no ha sido sobreescrito en Rectangulo ni en Circulo) entonces cuando este mismo mtodo invoca a su vez a area se estar ejecutando igualmente el mtodo area denido en e a e Figura. El error de este razonamiento radica en pensar que se est ejecutando el mtodo mismaArea a e de la clase Figura. Realmente dicho mtodo se est ejecutando sobre un objeto de tipo e a Rectangulo ya que el tipo dinmico de f1 corresponde a esta clase y, si bien es cierto a que Rectangulo no sobreescribe el mtodo mismaArea, s que lo hereda. En otras pale abras, Rectangulo tiene su propio mtodo mismaArea, cuyo cdigo es idntico al mtodo e o e e mismaArea denido en Figura. Una vez aclarado que se ejecuta el mtodo mismaArea de e Rectangulo, parece lgico pensar que la llamada al mtodo area se ejecutar tambin sobre o e a e la clase Rectangulo. Ello podr verse ms claro si en lugar de escribir a a
boolean mismaArea(Figura otra) { double a1 = area(); . . . }
100
Sabemos que this siempre hace referencia al objeto que invoc al mtodo. Puesto que en o e nuestro ejemplo el mtodo mismaArea fue invocado por f1, mentalmente podemos substie tuir [Link]() por [Link](), donde es obvio que f1 referencia a un objeto de tipo Rectangulo. Por otro lado, en la llamada
double a2 = [Link]();
la referencia otra toma su valor de f2, la cual a su vez referencia a un objeto de tipo Circulo. En este caso, por tanto, se ejecutar el mtodo area de la clase Circulo. a e Nuevamente, lo sorprendente del mtodo mismaArea es que puede comparar las reas de e a cualquier tipo de gura, aunque todav no hayan sido denidas. Si en un futuro denimos la a clase Dodecaedro el mtodo mismaArea podr trabajar con este tipo de objetos sin necesidad e a de cambiar una sola l nea de su cdigo. Esto es as porque el mtodo area es un mtodo o e e polimrco (cambia de forma, o de cdigo, en funcin del contexto en el que se ejecuta). El o o o unico requerimiento ser que Dodecaedro herede de Figura y que sobreescriba su propia a implementacin del mtodo area. o e
4.2.5.
Clases abstractas
Si observamos con detenimiento el ejemplo anterior podr amos pensar que no tiene mucha lgica incluir el mtodo area en la clase Figura, ya que dif o e cilmente puede calcularse el rea a de una gura genrica. En el ejemplo se ha adoptado el criterio de devolver 0. La razn de e o haber incluido el mtodo area en la clase Figura es unicamente para poder implementar e el enlace dinmico. De no haber incluido este mtodo, la sentencia v[i].area() hubiera a e generado un error de compilacin puesto que el tipo esttico (Figura en este caso) limita o a la visibilidad de los mtodos y atributos a los que se tiene acceso. Vemos, por tanto, que e es necesario incluir el mtodo area en la clase Figura, aunque realmente nunca se va a e ejecutar. Ntese que no ser vlido dejar el mtodo sin cdigo o a a e o
double area() {}
ya que, al menos, debe devolver un valor de tipo double, tal y como indica el tipo del mtodo. No ser posible poder denir mtodos sin cdigo para este tipo de situaciones? La e a e o respuesta es s y estos mtodos especiales son los denominados mtodos abstractos. , e e Un mtodo abstracto es un mtodo del que unicamente existe la cabecera y que carece de e e cdigo. El objetivo de incluir este tipo de mtodos en una clase es permitir el polimorsmo en o e tiempo de ejecucin. La idea, tal y como se ver ms adelante, es obligar a que las subclases o a a que extiendan a la clase donde est denido lo sobreescriban. a En Java se dene un mtodo abstracto simplemente anteponiendo la palabra reservada e abstract delante del mtodo, y nalizando el mismo con un punto y coma tras el parntesis e e que cierra la lista de parmetros. a
abstract tipo nombre_metodo(lista_argumentos);
En el ejemplo anterior hubiramos podido denir el mtodo area abstracto del siguiente e e modo:
abstract double area();
El hecho de denir un mtodo abstracto convierte en abstracta a la clase en la cual se e dene, lo que obliga a poner tambin la palabra abstract delante del nombre de la clase. e Nuestra nueva clase Figura quedar por tanto, a,
4.2. Polimorsmo abstract class Figura { abstract double area(); boolean mismaArea(Figura otra) { double a1 = area(); double a2 = [Link](); return ( a1==a2 ); } }
101
y el resto de subclases (Rectangulo, Circulo) no sufrir modicacin alguna. an o Ante esta nueva implementacin de la clase Figura cabr preguntarse qu efectos tendr o a e a el siguiente fragmento de cdigo: o
Figura f = new Figura(); [Link]( "Area=" + [Link]() );
Simplemente esto generar un error de compilacin. Ha de saberse que Java no permite a o instanciar objetos de clases abstractas. Esta restriccin es lgica, ya que el objetivo de las o o clases abstractas es que sirvan de base para denir nuevas subclases y no el de utilizarlas de forma aislada. De algn modo, una clase abstracta es una clase inacabada, de la que se espera u que futuras subclases terminen por implementar los mtodos abstractos que tienen denidos. e No parece lgico, por tanto, crear objetos de algo que todav no est completamente denido. o a a Bsicamente la clase Figura representa la idea abstracta de una gura, y no hay manera de a calcular el rea de algo que todav no sabemos lo que es. Unicamente es posible calcular el a a a rea de guras concretas como rectngulos o c a rculos. Debido a que puede llegar a crear confusin, en este punto vale la pena aclarar la diferencia o entre la sentencia
Figura f = new Figura();
y
Figura [] v = new Figura[100];
La primera de ellas generar un error de compilacin ya que, como acaba de explicarse, a o no es posible instanciar objetos de una clase abstracta. En el segundo caso, sin embargo, es importante entender que no se estn creando 100 objetos de tipo Figura sino 100 referencias a de dicho tipo, lo cual es perfectamente vlido. Estas referencias, por otro lado, debern a a emplearse necesariamente para instanciar objetos de alguna subclase no abstracta de Figura. Por ejemplo,
v[i] = new Rectangulo(10,10);
Es importante tener en cuenta ciertas implicaciones que tiene la utilizacin de clases o abstractas: Una clase que posea al menos un mtodo abstracto debe ser declarada como abstracta. e Los mtodos abstractos no tienen cuerpo y deben ser implementados por las subclases e de la clase abstracta. Si una subclase que extiende una clase abstracta no implementa alguno de los mtoe dos abstractos declarados en la superclase, entonces debe ser declarada tambin como e abstracta, ya que hereda el mtodo abstracto tal y como se hab denido en la supere a clase.
102
Una clase abstracta no puede ser instanciada. Una clase abstracta puede tener mtodos no abstractos. Es ms, es posible crear clases e a abstractas en las que ninguno de sus mtodos sea abstracto. Esto ultimo puede resultar e interesante cuando se desea evitar que una clase sea instanciada. En relacin a los atributos, una clase abstracta no incorpora ninguna modicacin con o o respecto a las clases no abstractas. Se pueden declarar variables referencia cuyo tipo sea una clase abstracta (por ejemplo, Figura f;). Estas referencias podrn ser utilizadas para referenciar objetos de a alguna subclase no abstracta, haciendo uso de la conversin hacia arriba vista en el o apartado 4.2.3. Por ejemplo, se puede crear un vector con N referencias del tipo abstracto Figura y posteriormente utilizar dicho vector para referenciar objetos de tipo Rectangulo, Circulo, etc. Constructores en clases abstractas Si bien es cierto que no se pueden instanciar objetos de una clase abstracta, ello no quiere decir que carezca de sentido incluir constructores en este tipo de clases. De hecho es habitual que las clases abstractas tengan constructor. El motivo de ello es que cuando se crea un objeto de una subclase, tambin se ejecutan los constructores de todas sus superclases e (aunque stas sean abstractas) tal y como se ha visto en el apartado 4.1.3. e Por ejemplo, podr amos denir la clase Figura y sus subclases como sigue:
abstract class Figura { int x, y; // Posicin de la figura o Figura() { x=0; y=0; } Figura(int posX, int posY) { x = posX; y = posY; } // Resto de mtodos e . . .
} class Circulo extends Figura { int radio; Circulo(int r) { // Como no se especifica lo contrario mediante radio = r; // super, se invocar al constructor sin a } // parmetros en la superclase a Circulo(int r, int posX, int posY) { super(posX, posY); // Se invoca al constructor con parmetros a radio = r; // en la superclase } // Resto de mtodos e
4.2. Polimorsmo . . .
103
La instruccin o
Circulo c = new Circulo(5);
dar lugar a que se invoque el constructor sin parmetros en la superclase, lo que inia a cializar los atributos con valores x=0, y=0 y radio=5, mientras que la instruccin a o
Circulo c = new Circulo(5,10,15);
dar lugar a que se invoque el constructor con parmetros en la superclase, lo que inia a cializar los atributos con valores x=10, y=15 y radio=5. a
4.2.6.
Tal y como se ha visto en el apartado 4.2.3, en la conversin hacia arriba se gana generalo idad pero se pierde informacin acerca del tipo concreto con el que se trabaja y, consecuenteo mente, se reduce la interfaz. Si se quiere recuperar toda la informacin del tipo con el que se o trabaja (esto es, recuperar el acceso a todos los miembros del objeto) ser necesario moverse a hacia abajo en la jerarqu mediante un cambio de tipo. Para ello se debe cambiar el tipo a de la referencia (tipo esttico) para que coincida con el tipo real del objeto (tipo dinmico) a a o al menos con algn otro tipo que permita el acceso al miembro del objeto deseado. Esta u conversin a un tipo ms espec o a co es lo que se conoce como conversin hacia abajo y se o realiza mediante una operacin de casting. o
Figura f = new Circulo(); // Conversin hacia arriba o Circulo c = (Circulo)f; // Conversin hacia abajo mediante o // cambio de tipo (casting) // Ahora mediante la referencia c podemos acceder a TODOS los miembros // de la clase Circulo. Con la referencia f SLO podemos acceder a los O // miembros declarados en la clase Figura
Tambin se puede hacer un cambio de tipo temporal sin necesidad de almacenar el nuevo e tipo en otra variable referencia.
Figura f = new Circulo(); // Conversin hacia arriba o ((Circulo)f).radio=1; // Acceso a miembros de la subclase // A continuacin f sigue siendo de tipo Figura o
En este caso mediante la sentencia ((Circulo)f) se cambia el tipo de f unicamente para esta instruccin, lo que permite acceder a cualquier miembro denido en Circulo (por o ejemplo al atributo radio). En sentencias posteriores f seguir siendo de tipo Figura. a Las conversiones de tipo, sin embargo, deben realizarse con precaucin: la conversin o o hacia arriba siempre es segura (por ejemplo un c rculo, con toda certeza, es una gura, por lo que este cambio de tipo no generar problemas), pero la conversin hacia abajo puede a o no ser segura (una gura puede ser un c rculo, pero tambin un rectngulo o cualquier otra e a gura). Dicho de otro modo, en la conversin hacia arriba se reduce la visibilidad que se o tiene del objeto (se reduce la interfaz) mientras que en la conversin hacia abajo se ampl o a. Debemos estar seguros de que esta ampliacin de la parte visible se corresponde realmente o con el objeto que hemos creado, de lo contrario se producir un error en tiempo de ejecucin. a o Imaginemos que tenemos una jerarqu de clases como la que se muestra en la gura 4.5. a En el siguiente ejemplo se muestran distintas conversiones dentro de esta jerarqu alguna a, de ellas incorrecta.
104
En el ejemplo anterior, para acceder a la interfaz completa del objeto Circulo (por ejemplo para acceder al mtodo setRadio) es necesaria la conversin hacia abajo. Ahora e o bien, hemos de tener la certeza de que el objeto que se encuentra referenciado es realmente del nuevo tipo al que queremos convertirlo, de lo contrario se producir un error en tiempo a de ejecucin, tal y como ocurre en la ultima instruccin. o o En este ejemplo es obvio que v[0] referencia un Rectangulo y v[1] un Circulo, por lo que es fcil darse cuenta del error cometido en la ultima instruccin, sin embargo, esto no a o siempre es as Hagamos la siguiente modicacin sobre el cdigo anterior: . o o
public class ConversionHaciaAbajo { public static void main(String[] args) { Figura[] v = new Figura[2];
4.2. Polimorsmo double aleatorio = [Link](); if( aleatorio > 0.5 ) { v[0] = new Rectangulo(); v[1] = new Circulo(); } else { v[0] = new Circulo(); v[1] = new Rectangulo(); } . . . } }
105
Si ahora pretendemos cambiar el radio de cualquier c rculo que tengamos en el vector v nos encontramos con que no ser seguro hacer una conversin hacia abajo, ya que realmente a o desconocemos el tipo de objeto que hay almacenado en cada componente del vector. Ser a interesante disponer de algn mecanismo que nos permitiera conocer el tipo de un objeto u (tipo dinmico) para poder hacer la conversin hacia abajo con total seguridad. En Java, a o este mecanismo nos lo proporciona el operador instanceof. El operador instanceof toma como primer operando una variable referencia y como segundo un tipo, segn la siguiente sintaxis: u
referencia instanceof Tipo
El resultado de la expresin anterior es true en caso de que el objeto asociado a la refero encia (tipo dinmico) coincida con el tipo del segundo operador, y false en caso contrario. a Por ejemplo, para cambiar el radio de todos los c rculos que se encuentran almacenados en un vector de guras podr emplearse el siguiente cdigo: a o
for( int i=0; i<[Link]; i++ ) if( v[i] instanceof Circulo ) ((Circulo)v[i]).setRadio(10); // Comprobacin del tipo dinmico o a // Conversin hacia abajo segura o
Como recomendacin nal cabr decir que el uso del operador instanceof deber o a a restringirse a los casos en que sea estrictamente necesario. En la medida de lo posible es preferible emplear mtodos polimrcos que permitan realizar las operaciones requeridas sin e o necesidad de conocer con exactitud el tipo concreto del objeto con el que se trabaja.
4.2.7.
Es habitual que las clases sobreescriban algunos de los mtodos denidos en Object. e La sobreescritura del mtodo finalize es util cuando el objeto utiliza ciertos recursos e como cheros y conexiones de red que conviene sean cerrados cuando termina su utilizacin. o Tambin es habitual sobreescribir los mtodos equals y toString mencionados en el apartae e do 4.1.5, tal y como se muestra en el siguiente ejemplo:
class Complejo { double real, imag; Complejo(double r, double i) { real = r; imag = i; }
106
Cap tulo 4. Herencia y Polimorsmo Complejo() { // Sobrecargamos el constructor this(0,0); // Se invoca al constructor con dos parmetros a } // Sobrecarga de equals public boolean equals( Complejo c ) { return (real == [Link] && imag == [Link]); } // Sobreescritura de equals public boolean equals( Object o ) { if( o instanceof Complejo ) return (real==((Complejo)o).real && imag==((Complejo)o).imag); else return false; } // Sobreescritura de toString public String toString() { return real + "+" + imag + "i"; }
class P { public static void main(String [] args) { // Creamos dos nmeros complejos iguales u Complejo c1 = new Complejo(4, 3); Complejo c2 = new Complejo(4, 3); if( c1 == c2 ) [Link]("Objetos else [Link]("Objetos if( [Link](c2) ) [Link]("Objetos else [Link]("Objetos con la misma referencia"); con distintas referencias"); con la misma informacin"); o con distinta informacin"); o
Hay que observar que el mtodo equals del ejemplo anterior se ha sobrecargado en un e caso y sobreescrito en otro. El mtodo con cabecera e
public boolean equals( Complejo c )
corresponde realmente a una sobrecarga y no a una sobreescritura del mtodo con el mismo e nombre denido en Object. Esto es as porque el mtodo equals de Object recibe como e parmetro una referencia de tipo Object y no de tipo Complejo. a Por otro lado, el mtodo e
107
corresponde realmente a una sobreescritura, ya que recibe como parmetro una referencia a de tipo Object. Tal y como se ha visto en apartados anteriores, es perfectamente vlido a referenciar un objeto de tipo Complejo mediante una referencia de tipo esttico Object, sin a embargo, ser necesario cambiar el tipo esttico mediante una conversin hacia abajo para a a o poder acceder a los atributos propios de la clase Complejo. En cuanto a la sobreescritura de toString, debe entenderse que cuando se utiliza cualquier objeto como si fuera un String, internamente se invoca el mtodo toString de dicho objeto. e En este sentido, la instruccin o
[Link]("c1 = "+ c1) [Link]("c1 = " +[Link]()); donde [Link]()) se sustituye, lgicamente, por la cadena que devuelva el mtodo o e toString.
ser equivalente a a
4.3.
Interfaces
Se entiende por interfaz de una clase el conjunto de operaciones (mtodos) que se pueden e realizar con los objetos de dicha clase. Aunque dicha interfaz puede deducirse fcilmente a observando los mtodos pblicos implementados en una clase determinada, el lenguaje Java e u dispone de un mecanismo para denir de forma expl cita las interfaces de las clases. Este mecanismo, ms all de detallar simplemente la interfaz de las clases, aporta mayor nivel de a a abstraccin al lenguaje, tal y como se ir viendo en los siguientes apartados. o a
4.3.1.
En Java no existe la herencia mltiple. De esta manera, se evitan los problemas que u genera la utilizacin de la herencia mltiple que fueron comentados en el apartado 4.1.1. Sin o u embargo, tampoco se tienen las ventajas de la herencia mltiple. La solucin en Java ha sido u o la incorporacin de un mecanismo, las interfaces, que proveen de algunas de las ventajas de o la herencia mltiple sin sus inconvenientes. u En la gura 4.6 se presenta un diseo (no permitido en Java) en el que la clase C hereda n tanto de A como de B. Si, como ocurre en el ejemplo, las dos superclases A y B contienen el mtodo m1() y dicho mtodo est implementado de forma distinta en cada una de ellas, e e a existir un conicto respecto a cul de las dos implementaciones de m1() deber heredar a a a C. Este conicto, por otro lado, no se dar si m1() se hubiese denido abstracto tanto en a A como en B, ya que en este caso el mtodo no estar implementado y por tanto no cabr e a a esperar conicto alguno. Podr pensarse, por tanto, que si A y B fuesen clases abstractas no a deber haber inconveniente en permitir la herencia mltiple. Sin embargo, tal y como se ha a u visto en apartados anteriores, las clases abstractas pueden contener mtodos no abstractos, e con lo que la exigencia propuesta de que las superclases sean abstractas no garantizar la a ausencia de conictos en un esquema de herencia mltiple. u Para que un diseo como el propuesto fuera fcilmente implementable deber existir n a a algn mecanismo que exigiera tanto a A como a B que todos sus mtodos fuesen abstractos. u e Adems, por motivos similares, dicho mecanismo no deber permitir la declaracin de atriba a o utos de instancia en A o en B, ya que podr darse el caso de que dos atributos con el mismo a nombre tuviesen valores distintos. Pues bien, este mecanismo existe en Java y se lleva a la prctica mediante lo que se conoce como interfaz. a
108
Figura 4.6: Conictos en un esquema de herencia mltiple. u El uso de interfaces, ms all de tratar de resolver unicamente el problema de la herencia a a mltiple, guarda una estrecha relacin con el concepto de polimorsmo estudiado en el u o apartado 4.2, tal y como se ver ms adelante. a a
4.3.2.
La interfaz de una clase viene denida de forma impl cita en funcin de los miembros o pblicos2 que contenga dicha clase. Adems de esta denicin impl u a o cita, Java dispone de un mecanismo para denir expl citamente la interfaz (o parte de la interfaz) de una clase, lo que permite separarla completamente de su implementacin. Ello se hace mediante la declaracin o o de lo que en Java se denomina una interfaz. Sintcticamente, las interfaces son como las a clases pero sin variables de instancia y con mtodos declarados sin cuerpo. e La denicin de una interfaz tiene la forma siguiente: o
acceso tipo tipo ... tipo tipo } interfaz nombre_interfaz { var1; var2; metodo1( ... ) ; metodo2( ... ) ;
El acceso de una interfaz debe ser pblico, bien con la palabra reservada public o sin u modicador de acceso (friendly). Despus se especica que la siguiente denicin pertenece e o a una interfaz utilizando la palabra reservada interface, seguida del nombre de la interfaz y del bloque de cdigo correspondiente a la denicin de la interfaz. Las variables declaradas o o en la interfaz son impl citamente estticas (static) y nales (final), lo que signica que a han de ser inicializadas con un valor constante y su valor no puede cambiarse posteriormente. Los mtodos no tienen cuerpo puesto que son bsicamente mtodos abstractos que han de e a e ser implementados por las clases que implementan la interfaz. Como se ir viendo a lo largo de esta seccin, las interfeces tienen ciertas similitudes a o con las clases abstractas. De hecho, una interfaz podr verse como una clase abstracta sin a atributos de instancia y en la que todos sus mtodos son abstractos. e
2 En este contexto el concepto pblico no se restringe unicamente a aquellos miembros declarados con la u palabra reservada public sino a aquellos miembros que sean visibles desde otros objetos. El que sean o no visibles depender del modicador de acceso (public, protected, private o friendly), de la relacin de herencia a o entre las distintas clases y del paquete donde se encuentren.
4.3. Interfaces
109
Como ejemplo se podr denir la interfaz de cierta coleccin de objetos del siguiente a o modo:
interface Coleccion { void aadirElemento( Object o ); n int getNumElementos(); void mostrar(); }
Se puede observar que la interfaz no hace suposiciones acerca de los detalles de implementacin. Bsicamente una interfaz dene qu operaciones se pueden realizar, pero no o a e cmo se realizan. o Una vez denida una interfaz, cualquier nmero de clases puede implementarla. Impleu mentar una interfaz implica implementar cada uno de los mtodos denidos en la misma. e Para indicar que una clase implementa una interfaz determinada, se utiliza la palabra reservada implements con la siguiente sintaxis:
acceso class nombreClase implements NombreInterfaz1[, NombreInterfaz2] { ... }
Bsicamente, todo lo que necesita una clase para implementar una interfaz es sobreescribir a el conjunto completo de mtodos declarados en dicha interfaz. En caso de que la clase no e sobreescriba todos los mtodos, dicha clase deber declararse abstracta, ya que contiene e a mtodos sin implementar. Los mtodos denidos en una interfaz deben ser declarados con e e public en la clase que los implementa. Por ejemplo, podr amos realizar la siguiente implementacin de la interfaz Coleccion o utilizando un vector para almacenar los distintos elementos del conjunto:
class Conjunto implements Coleccion { private Object[] v; private int numElementos; Conjunto( int maxElementos ) { v = new Object[maxElementos]; numElementos = 0; } // Implementar los mtodos de la interfaz Coleccion e public void aadirElemento( Object o ) { n if( numElementos < [Link] ) { v[numElementos] = o; numElementos++; } } public int getNumElementos() { return numElementos; } public void mostrar() { for(int i=0; i<numElementos; i++) [Link](o); // Tendremos que haber sobreescrito el // mtodo toString de nuestros objetos e }
110
Podr realizarse, por otro lado, una implementacin alternativa basada en una lista a o enlazada:
class ListaEnlazada implements Coleccion { private Object cabeza; private int numElementos; Conjunto() { cabeza = null; numElementos = 0; } // Implementar los mtodos de la interfaz Coleccion e public void aadirElemento( Object o ) { n // Operaciones para aadir el objeto o a la lista enlazada n . . . } public int getNumElementos() { return numElementos; } public void mostrar() { // Recorrer la lista enlazada y mostrar cada uno de los elementos . . . }
Con este esquema, independientemente de que utilicemos objetos de tipo Conjunto o de tipo ListaEnlazada, sabremos que en cualquier caso tendrn implementados los mtodos a e aadirElemento, getNumElementos y mostrar ya que todos ellos fueron denidos en la n interfaz Coleccion. Podr dar la sensacin de que denir la interfaz en un lugar separado e independiente de a o la clase que lo implementa no aporte ninguna ventaja, ya que se puede averiguar fcilmente a la interfaz de cualquier clase con tan solo jarnos en sus mtodos pblicos, sin necesidad de e u denirla expl citamente en un lugar independiente de la clase. Por otro lado, este esquema tiene muchas similitudes con el concepto de clase abstracta visto en el apartado 4.2.5, con lo que nuevamente la utilidad de las interfaces parece diluirse. De hecho, implementar una interfaz tiene implicaciones muy similares a las que tendr extender una clase abstracta. a Existe, sin embargo, una diferencia fundamental: las clases abstractas se reutilizan e implementan mediante el esquema de herencia. Ello quiere decir que slo se puede heredar de una o clase abstracta. Sin embargo, una clase puede implementar mltiples interfaces, e incluso u heredar a la vez de alguna otra clase. Las interfaces estn en una jerarqu distinta de la jerarqu de clases, por lo que es a a a posible que varias clases que no tengan la ms m a nima relacin de herencia implementen la o misma interfaz. En la gura 4.7 se muestran dos interfaces (I1 e I2) que son implementadas por clases que no en todos los casos tienen una relacin de herencia. Concretamente, la clase o A (y por extensin C y D) implementan la interfaz I1. Por otro lado, la clase E, que no o guarda relacin de herencia con las anteriores, tambin implementa I1. Adicionalmente, E o e implementa I2 (una clase puede implementar ms de una interfaz). a En el prximo apartado se explotar el mecanismo de las interfaces en el contexto del o a polimorsmo y se ver la potencia que realmente aportan. Antes de ello concluiremos este a apartado con algunas considereciones que deben tenerse en cuenta acerca de las interfaces:
4.3. Interfaces
111
Figura 4.7: Combinacin de un esquema de herencia e implementacin de interfaces. o o Una interfaz unicamente puede contener atributos estticos constantes y cabeceras de a mtodos. e No es posible instanciar una interfaz. Una misma interfaz puede ser implementada por ms de una clase. a Una misma clase puede implementar ms de una interfaz. a Cualquier mtodo denido en una interfaz se sobreentiende que es pblico, aunque no e u se ponga expl citamente la palabra public delante del mismo. Cualquier atributo denido en una interfaz se sobreentiende que es pblico, esttico y u a nal, aunque no se pongan expl citamente las palabras public static final delante del mismo. Cualquier clase que implemente una interfaz deber implementar todos los mtodos a e denidos en la misma. Si no lo hace se convierte en una clase abstracta (por no tener todos sus mtodos implementados) y deber declararse como tal mediante la palabra e a reservada abstract.
4.3.3.
Una interfaz es un tipo de dato y, por tanto, puede utilizarse para declarar referencias o como tipo de dato en los argumentos de un mtodo. e En el apartado 4.2.3 se vio cmo una referencia de una superclase puede referenciar a o objetos pertenecientes a alguna de sus subclases (conversin hacia arriba). De forma similar, o una referencia de una interfaz puede emplearse para referenciar objetos pertenecientes a cualquier clase que implemente dicha interfaz. Ello permite implementar el polimorsmo en tiempo de ejecucin de un modo similar a como se implementa haciendo uso de clases o abstractas. Por ejemplo, dada la interfaz Coleccion del ejemplo anterior y sus dos implementaciones Conjunto y ListaEnlazada, es posible declarar referencias de tipo Coleccion y sobre ellas instanciar objetos de tipo Conjunto o ListaEnlazada.
Coleccion c; if( cierta_condicion ) c = new Conjunto(); else
112 c = new ListaEnlazada(); // Aadir elementos n for( int i=0; i<10; i++ ) [Link]( new Integer(i) ); n
// Mostrar el contenido [Link]("La coleccin contiene los siguientes "); o [Link]( [Link]() + "elementos"); [Link]();
En este ejemplo, segn ciertas condiciones, se crea un objeto de tipo Conjunto o de tipo u ListaEnlazada. Lo interesante del cdigo anterior es que nuestro programa puede trabajar o perfectamente con la referencia c sin necesidad de conocer qu tipo de objeto se ha creado e realmente bajo dicha referencia. Ser en tiempo de ejecucin cuando, al invocar cualquiera a o de los mtodos de c, se decida qu mtodo debe ejecutarse (en nuestro caso un mtodo de e e e e la clase Conjunto o de la clase ListaEnlazada). Ya se vio en el apartado 4.2.4 que este mecanismo que permite posponer hasta el ultimo momento la decisin del mtodo que debe o e ejecutarse se conoce como seleccin dinmica de mtodo o enlace dinmico 3 y, a la postre, o a e a permite implementar el polimorsmo en tiempo de ejecucin. o A continuacin se muestra un ejemplo completo en el que se ha denido la interfaz o ConjuntoOrdenable y una clase Ordena con una serie de mtodos que permiten ordenar e cualquier conjunto ordenable. Lgicamente, para que un conjunto sea considerado ordenable o debe implementar la interfaz ConjuntoOrdenable. Nuevamente, lo interesante del ejemplo que se muestra a continuacin radica en que los mtodos de la clase Ordena no necesitan o e conocer el tipo real de los objetos que se estn ordenando. a
interface ConjuntoOrdenable { public int getNumElementos(); public boolean menor(int i, int j); public void intercambiar(int i, int j); } class Ordena { // Este mtodo puede ordenar cualquier conjunto e // que implemente la interfaz ConjuntoOrdenable static void seleccionDirecta( ConjuntoOrdenable c ) { int pos_min, N = [Link](); for( int i = 0; i <= N-2; i++ ) { pos_min = i; for( int j = i+1; j < N; j++ ) { if( [Link](j, pos_min) ) pos_min = j; } [Link](i, pos_min); } } static void burbuja( ConjuntoOrdenable c ) { // Otros mtodos de ordenacin e o // . . . } static void quickSort( ConjuntoOrdenable c ) {
3 Algunos
autores utilizan el trmino ligadura tard para referirse a este mismo concepto. e a
113
// Una implementacin concreta de un ConjuntoOrdenable o class ConjuntoPalabras implements ConjuntoOrdenable { private LinkedList lista; // Mtodos propios e ConjuntoPalabras() { lista = new LinkedList(); } public void inserta(String s) { [Link](s); } public String toString() { String s = ""; for( int i=0; i<[Link](); i++ ) s = s + [Link](i) + " "; return s; } // Mtodos exigidos por la interfaz ConjuntoOrdenable e public int getNumElementos() { return [Link](); } public boolean menor( int i, int j ) { String s1 = (String)[Link](i); String s2 = (String)[Link](j); if( [Link](s2) < 0 ) return true; else return false; } public void intercambiar(int i, int j) { Object pal1 = [Link](i); Object pal2 = [Link](j); [Link](j, pal1); [Link](i, pal2); }
class EjemploInterfazOrdena { public static void main( String[] args ) { ConjuntoPalabras cp = new ConjuntoPalabras(); [Link]("las"); [Link]("interfaces"); [Link]("son"); [Link]("muy"); [Link]("utiles"); [Link](cp); [Link](cp); [Link](cp);
114 }
El mtodo seleccionDirecta admite cualquier objeto de tipo ConjuntoOrdenable. e Los objetos de tipo ConjuntoPalabras tambin son de tipo ConjuntoOrdenable, ya e que implementa dicha interfaz. El mtodo seleccionDirecta espera recibir como parmetro algn objeto de tipo e a u ConjuntoOrdenable sin embargo, cuando se invoca se le est pasando un objeto a de tipo ConjuntoPalabras. Esto es posible gracias a la conversin hacia arriba y o a que los objetos de tipo ConjuntoPalabras, de algn modo, tambin son de tipo u e ConjuntoOrdenable. Podr amos ampliar el ejemplo anterior para ilustrar cmo una clase puede implementar o ms de una interfaz al mismo tiempo. a
interface ConjuntoDesordenable { public int getNumElementos(); public void intercambiar(int i, int j); } class Desordena { // Este mtodo puede desordenar cualquier conjunto que e // implemente la interfaz ConjuntoDesordenable static void desordenar( ConjuntoDesordenable c ) { Random rnd = new Random(); int n = [Link](); for(int i=0; i<n*2; i++) { // Selecciono dos elementos aleatorios y los // cambio de lugar int elem1 = [Link]([Link]()) % n; int elem2 = [Link]([Link]()) % n; [Link](elem1, elem2); } } } class ConjuntoPalabras implements ConjuntoOrdenable, ConjuntoDesordenable { . . . }
Ahora, los objetos de tipo ConjuntoPalabras no slo pueden ordenarse sino tambin desoro e denarse. Esto es as porque ConjuntoPalabras implementa tanto la interfaz ConjuntoOrdenable como ConjuntoDesordenable y, por tanto, cualquier mtodo que requiera como parmetro e a objetos de alguno de estos tipos, podr recibir sin problemas objetos de tipo ConjuntoPalabras. a Por otro lado, el cuerpo de la clase ConjuntoPalabras ser igual al del ejemplo anterior a ya que, aunque ahora implementa tambin la interfaz ConjuntoDesordenable, todos los e mtodos requeridos por esta interfaz ya exist en la versin anterior y, por tanto, ya e an o estn implementados. De no ser as hubiese sido necesario implementar los nuevos mtodos a , e requeridos por la interfaz ConjuntoDesordenable.
115
4.3.4.
Denicin de constantes o
Dado que, por denicin, todos los atributos que se denen en una interfaz son static o y final, las interfaces pueden utilizarse tambin para denir grupos de constantes. e Por ejemplo
interface DiasSemana { int LUNES=1, MARTES=2, MIERCOLES=3, JUEVES=4, VIERNES=5, SABADO=6, DOMINGO=7; }
Aunque no se haya indicado expl citamente, se considera que LUNES, MARTES, etc. son estticas y nales. Por otro lado, si bien no es un requerimiento del lenguaje, por convenio a las constantes se suelen escribir en maysculas. u Un posible uso de la interfaz anterior ser a:
for( int i = [Link]; i <= [Link]; i++ ) { . . . }
4.3.5.
Las interfaces pueden extender otras interfaces. Adems, a diferencia de lo que ocurre a con las clases, entre interfaces se permite la herencia mltiple. Esto es as porque, aun en el u caso en el que se heredasen dos mtodos con el mismo nombre y atributos, no podr entrar e an en conicto por carecer dichos mtodos de cuerpo. e
interface I1 { void metodo1(); void metodo2(); } interface I2 { void metodo3(); } interface I3 extends I1, I2 { // Herencia mltiple entre interfaces u void metodo4(); // metodo1, metodo2 y metodo3 se heredan de I1 e I2 } class C implements I3 { // La clase C deber implementar a // metodo1, metodo2, metodo3 y metodo4 }
4.4.
4.4.1.
Ejercicios resueltos
Enunciados
1. Indica lo que mostrar por pantalla el siguiente programa o, en caso de que sea incora recto, explica cul es el error: a
class A { } class B extends A { void metodo() {
116
} public class Clase { public static void main( String[] args ) { A obj = new B(); [Link](); } }
2. Indica lo que mostrar por pantalla el siguiente programa o, en caso de que sea incora recto, explica cul es el error: a
class A { void metodo() { [Link]("Mtodo de A"); e } } class B extends A { void metodo() { [Link]("Mtodo de B"); e } } public class Clase { public static void main( String[] args ) { A obj = new B(); [Link](); } }
3. En la siguiente jerarqu de herencia existen llamadas a mtodos en los que se debe a e tener claro el concepto de polimorsmo. Escribe cul ser el resultado de la ejecucin a a o del siguiente cdigo: o
abstract class Flujo { abstract public void escribe(char c); public void escribe (String s) { for (int i=0; i<[Link](); i++) { [Link]("Escribe de Flujo ...."); escribe([Link](i)); } } public void escribe (int i) { escribe(""+i); } } class Disco extends Flujo { public void escribe(char c) { [Link]("Escribe de disco " + c); } } class DiscoFlexible extends Disco { public void escribe (String s) { [Link] ("Escribe de Disco Flexible...");
117
public class TestFlujo { public static void main (String a[]) { DiscoFlexible dc = new DiscoFlexible(); Flujo f = dc; [Link]("ab"); } }
Indicar la salida por pantalla que generar cada una de las siguientes sentencias o, en a caso de que alguna de ellas no sea correcta, explicar el error.
[Link].A B C C obj obj obj obj = = = = new new new new B(1,2,3); [Link](obj); B(1); [Link](obj); C(1,2,3,4); [Link](obj); C(1,2,3); [Link](obj);
118
Cap tulo 4. Herencia y Polimorsmo public static void main(String[] args){ A[] v = new A[10]; for ( int i = 0; i < 10; i++ ) { v[i] = new B(); v[i].metodo(); } }
6. Reescribe el siguiente cdigo para que realice la misma operacin sin utilizar la instruco o cin instanceof y sin utilizar casting. En la solucin aportada se debe usar la tcnica o o e de enlace dinmico. a
interface Procesable {} class A implements Procesable{ void procesarA() { [Link]("Procesando A") }; } class B implements Procesable { void procesarB() { [Link]("Procesando B") }; } class Cuestion { public static void main(String[] args){ Procesable p; if( cierta_condicion ) p = new A(); else p = new B(); if( p instanceof A ) ((A)p).procesarA(); else ((B)p).procesarB(); } }
4.4. Ejercicios resueltos class B extends A { void m2() { [Link]("Soy el mtodo 2 de B"); e }
119
4.4.2.
Soluciones
1. La referencia obj tiene tipo esttico A y tipo dinmico B. El tipo esttico indic QUE se a a a a puede hacer y el dinmico COMO se hace. A pesar de que metodo() est denido en a a el tipo dinmico, como no se encuentra en el tipo esttico se generar un error de a a a compilacin. o 2. El programa es correcto. Mostrar por pantalla a
Mtodo de B e
3.
de de de de de
4.
at1=1 at2=2 at3=3 at1=0 at2=0 at3=1 at1=1 at2=2 at3=3 at4=4 Error. En la clase B no existe un constructor vaco, y en el constructor de C no se especifica explcitamente qu constructor debe ejecutarse en B e
5.
La instruccin v[i].metodo() es incorrecta, ya que aunque dicho mtodo existe o e en el tipo dinmico de v[i] (clase B), no existe en el tipo esttico (clase A). Podr a a a solucionarse aadiendo abstract void metodo(); en la clase A. n Al haber escrito el constructor A(int i) en la clase A, se pierde el constructor sin parmetros. Puesto que al crear objetos de la clase B se debe ejecutar no a slo el constructor de B sino tambin el de A, ser necesario especicar en el o e a constructor de B, mediante el uso de super, que en A deseamos ejecutar el constructor A(int i). Una segunda alternativa consistir en aadir un constructor a n sin parmetros en A. a
6.
interface Procesable { void procesar(); } class A implements Procesable{ void procesar() { [Link]("Procesando A") }; } class B implements Procesable { void procesar() { [Link]("Procesando B") }; } class Cuestion { public static void main(String[] args){ Procesable p; if( cierta_condicion ) p = new A(); else p = new B(); [Link]();
120 }
7.
La instruccin this.i = i es incorrecta, ya que this.i es una variable de tipo o final static (por haberse denido dentro de una interfaz) y por tanto no es posible modicar su valor. El mtodo mostrar() deber ser public por estar denido en una interfaz. e a
8. El cdigo es incorrecto ya que el mtodo m1() denido en la clase A deber haberse o e a sobreescrito en la clase B, o bien deber haberse declarado la clase B como abstracta. a
4.5.
Ejercicios propuestos
class C1 { int i; C1() { i = 1;} void m1() { [Link]( i ); } void m2() { m1(); m1(); } } class C2 extends C1 { C2() { i = 2; } void m1() { [Link]( -i ); } } class C3 extends C2 { C3() { i++; } void m2() { m1(); } } class Herencia_y_Sobreescritura { public static void main( String[] args ) { C1 a = new C1(); a.m2(); C1 b = new C2(); b.m2(); C2 c = new C3(); c.m2(); } }
2. Se pretende implementar el conjunto de clases necesarias para simular conexiones sencillas entre redes de ordenadores. Nuestro sistema de conexiones estar formado por a redes, encaminadores y hosts. Para realizar la conexin de los distintos elementos que o forman el sistema debe tenerse en cuenta que: Todos los elementos (redes, encaminadores y hosts) tienes una direccin asociao da formada por dos nmeros enteros. Las redes y los encaminadores tienen una u direccin del tipo X.0, mientras que los hosts tienen una direccin de tipo X.Y. o o
121
Cada host se conecta a una red determinada. Las direcciones de todos los host de una misma red tendrn el primer campo en comn, que coincidir a su vez con a u a el primer campo de la direccin de la red a la que estn conectados (por ejemplo, o a los hosts con direcciones 128.1, 128.2, etc. debern pertenecer a la red 128.0). a Las redes deben de mantener una lista de los hosts que estn presentes en ella. Para a ello se aconseja que la clase red tenga un atributo privado de tipo LinkedList. Adems, las redes pueden estar conectadas a un encaminador. a Cada encaminador tiene N puertos, a los que pueden conectarse tanto redes como otros encaminadores. Cuando se crea un encaminador se debe indicar el nmero u de puertos que tiene. La implementacin a realizar debe de ceirse al uso que de ella se hace en el siguiente o n programa principal:
class Redes { public static void main(String[] args){ Red r1,r2,r3; Encaminador e1,e2; Host h11,h12,h21,h22,h31,h32; // Creamos las r1=new Red(new r2=new Red(new r3=new Red(new redes Direccion(1,0)); Direccion(2,0)); Direccion(3,0));
// Creamos los hosts h11=new Host(new Direccion(1,1)); h12=new Host(new Direccion(1,2)); h21=new Host(new Direccion(2,1)); h22=new Host(new Direccion(2,2)); h31=new Host(new Direccion(3,1)); h32=new Host(new Direccion(3,2)); // Conectamos los hosts a las redes [Link](h11); [Link](r1); [Link](h12); [Link](r1); [Link](h21); [Link](r2); [Link](h22); [Link](r2); [Link](h31); [Link](r3); [Link](h32); [Link](r3); // Creamos los encaminadores e1=new Encaminador(new Direccion(4,0),3); e2=new Encaminador(new Direccion(5,0),3); // Conectamos los [Link](r1); [Link](r2); [Link](e2); [Link](r3); encaminadores y las redes [Link](e1); [Link](e1); [Link](e1); [Link](e2);
122
3. Ampliar el programa del ejercicio anterior para que permita el env de mensajes (en o nuestro caso objetos de tipo String) entre dos hosts. Para mandar un mensaje del host X1.Y1 al host X2.Y2 se sigue el siguiente procedimiento: El host X1.Y1 env el mensaje a su red (X1.0). a La red X1.0 comprueba la direccin destino y o Si la direccin destino es de la misma red, env el mensaje directamente al o a host destino. Si no, env el mensaje al encaminador, para que este lo redirija. a Si un encaminador recibe un mensaje, comprueba si alguna de las redes que tiene conectadas a l es la que tiene el host destino (red con direccin X2.0). e o Si encuentra la red destino, env a sta el mensaje. a e Si no, reenv el mensaje a todos los encaminadores que tenga conectados. a El env de mensajes deber funcionar correctamente con el siguiente cdigo (suponieno a o do que se ha creado una red como la del ejercicio anterior).
[Link]("MENSAJE_1",new Direccion(3,2)); [Link]("MENSAJE_2",new Direccion(3,1)); [Link]("MENSAJE_3",new Direccion(1,2));
Cap tulo 5
Manejo de excepciones
5.1. Introduccin a las excepciones o
Una excepcin es una situacin anormal ocurrida durante la ejecucin de un programa. o o o En un programa Java, pueden producirse excepciones por distintos motivos. Por ejemplo, en el caso de que un programa ejecute una divisin entre cero, se producir una excepcin o a o indicando que no es posible ejecutar tal operacin. En un programa que intenta escribir o ciertos datos en un chero, se produce una excepcin en caso de que ocurra algn error en o u el sistema de cheros que contiene el chero. Tambin se produce una excepcin cuando un e o programa intenta acceder a un elemento de un array utilizando un ndice incorrecto (por ejemplo, un valor negativo). Existen muchas otras situaciones en las que se puede producir una excepcin. En algunos casos, las excepciones indican errores en el programa. En otros o casos, las excepciones indican algn problema grave ocurrido en la mquina virtual, el sistema u a operativo subyacente o los dispositivos hardware utilizados. La plataforma de desarrollo Java proporciona algunos mecanismos que permiten a un programa Java detectar las excepciones y recuperarse de los posibles problemas ocurridos. Los programas Java tambin pueden generar excepciones, por ejemplo para sealar alguna e n situacin anmala detectada por el propio programa. o o Entre estos mecanismos se incluyen distintas clases que permiten representar distintos tipos de excepciones que pueden producirse durante la ejecucin de un programa Java. Todas o esas clases se organizan de forma jerrquica a partir de una clase base llamada Throwable. a Esta clase tiene dos subclases, Exception y Error. La clase Exception permite representar aquellas excepciones que un programa Java convencional podr querer detectar y a tratar. La clase Error permite representar distintos errores graves, que no se suelen tratar en los programas Java convencionales. En general, estas excepciones se deben a problemas importantes que impiden que el programa se siga ejecutando con normalidad. A su vez, la clase Exception tiene muchas subclases. Algunas de ellas pueden indicar problemas concretos. Por ejemplo, la excepcin ClassNotFoundException se produce cuano do la mquina virtual no encuentra un chero .class con la denicin de una clase necesaria a o para la ejecucin del programa actual. o Tambin hay subclases de Exception que permiten agrupar distintos conjuntos de exe cepciones. Por ejemplo, la clase IOException permite representar distintos tipos de excepciones que pueden producirse al realizar operaciones de entrada/salida de datos. La clase FileNotFoundException se produce cuando un programa Java intenta abrir un chero que no existe. 123
124
La clase Error tambin tiene diversas subclases, aunque en general, los programas Java e no suelen estar preparados para detectar y manejar dichos errores. En la gura 5.1 se representa una pequea parte de la jerarqu de clases de la librer n a a estndar de Java que representan excepciones. a Throwable $$$ $$$ W $ z Error Exception % z c
...
...
...
...
Figura 5.1: Jerarqu de excepciones en Java (fragmento) a En cualquier caso, cuando se produce una situacin anmala durante la ejecucin de o o o un programa Java, sea cual sea el tipo de situacin, la mquina virtual crea una instancia o a de Throwable o alguna de sus subclases. Este objeto contiene informacin acerca de la o situacin anmala y puede ser capturado por el programa para consultar dicha informacin o o o y para realizar las acciones que se consideren adecuadas.
5.2.
Captura de excepciones
Considrese el siguiente ejemplo, donde se intenta abrir el chero [Link] para leer la e informacin que contiene: o
public class Exc0 { public static void main (String [] args) { int d1, d2, d3; FileInputStream f = new FileInputStream("[Link]"); ... // Lectura de los datos } ... // Uso de los datos
La instruccin utilizada para abrir el chero [Link] puede producir o lanzar una exo cepcin, por ejemplo, en caso de que el chero en cuestin no exista. Adems, las operaciones o o a utilizadas para leer los datos contenidos en el chero (no indicadas en el ejemplo anterior) posiblemente tambin lancen excepciones, si se produce algn error leyendo el chero. En e u caso de que esto ocurra, la ejecucin del programa se interrumpe y en la salida de error del o programa se escribe un resultado como el siguiente:
[Link]: [Link] at [Link]([Link]).
En este caso, se dice que el programa ha lanzado una excepcin. La salida anterior o se conoce como la traza de la excepcin y contiene informacin sobre el error producido o o (consultar el apartado 5.3.2).
125
El programa anterior presenta un importante problema. En caso de que se produzca una excepcin, el programa es interrumpido y terminado, sin tener posibilidad alguna de tratar o el error y recuperarse. El lenguaje Java proporciona un mecanismo que permite a los programas Java detectar la ocurrencia de excepciones y realizar algunas operaciones que le permitan recuperarse de los errores ocurridos. Este mecanismo es la sentencia try-catch.
5.2.1.
La sentencia try-catch
La sentencia try-catch permite proteger un cierto fragmento de cdigo frente a la ocuro rencia de determinados tipos de excepciones y en caso de que stas ocurran, permite ejecutar e un cierto cdigo para recuperarse de los posibles errores. o De forma general, se utiliza de la siguiente forma:
try { ... // Cdigo que puede generar excepciones o } catch (Tipos de excepciones a capturar) { ... // Cdigo para tratar el error o }
} catch (Exception e) { [Link]("No es posible abrir el fichero"); return; } } ... // Uso de los datos
En el ejemplo, una vez le dos los datos, se proceder a usarlos, segn las necesidades del a u programa. En caso de que alguna de las operaciones protegidas lance una excepcin, sta ser capo e a turada por el bloque catch. En este bloque se incluye el cdigo manejador de la excepcin. o o En el ejemplo anterior, el manejo de la excepcin consiste unicamente en informar al usuario o mediante un mensaje escrito en la salida estndar y ejecutar la sentencia return, que termia na inmediatamente la ejecucin del mtodo actual. Como este mtodo es el propio mtodo o e e e main, el efecto de la sentencia return es el de terminar la ejecucin del programa. o
try. Si las operaciones protegidas se ejecutan correctamente y no se produce ninguna excepcin, entonces la ejecucin contina a partir de la siguiente instruccin al bloque try-catch. o o u o
Las operaciones que pueden lanzar alguna excepcin estn protegidas mediante el bloque o a
126
En la declaracin del bloque catch, se utiliza la expresin Exception e, con dos objeo o tivos. En primer lugar, esta expresin indica el tipo de excepciones que van a ser capturadas o en caso de que se produzcan. En el ejemplo anterior, se captura cualquier excepcin de tipo o Exception o de cualquiera de sus subclases. Adems, la expresin Exception e es tambin a o e la declaracin de una variable e que representa a la propia excepcin y permite obtener o o informacin relativa a la misma (tal como se presenta en el apartado 5.3). El mbito de esta o a variable es solo el bloque catch, por lo que no puede ser utilizada fuera de dicho bloque. Una vez ejecutado el bloque catch, la ejecucin del programa contina en la instruccin o u o siguiente al bloque try-catch, a menos que la ejecucin sea alterada de alguna manera, por o ejemplo, mediante una sentencia return, como en el ejemplo anterior. En el apartado 5.4.1 se introduce otra forma de alterar el ujo del programa dentro de un bloque catch. En el ejemplo anterior, otro posible tratamiento de la excepcin podr consistir en asignar unos o a datos por defecto (y no ejecutar ninguna sentencia return), de manera que la ejecucin del o programa pueda continuar. Las excepciones y el compilador La gran mayor de tipos de excepciones deben ser capturadas expl a citamente mediante un bloque try-catch1 . El compilador analiza el cdigo fuente y comprueba si cada instruccin que puede lano o zar alguna excepcin est protegida mediante un bloque try-catch 2 . De lo contrario, el o a compilador interrumpir la compilacin y avisar del error, como en el siguiente ejemplo: a o a
[Link][Link] unreported exception [Link]; must be caught or declared to be thrown FileInputStream f= new FileInputStream("[Link]");
En el ejemplo anterior, el compilador indica que en la l nea 5 del chero [Link] se realiza una accin que puede lanzar una excepcin de tipo FileNotFoundException y o o que sta l e nea debe ser protegida mediante un bloque try-catch (o una sentencia throws adecuada en el mtodo que la contiene). e Por otra parte, el compilador tambin comprueba que los bloques try-catch se usen e adecuadamente. Si se utiliza un bloque try-catch para proteger una determinada seccin o de cdigo frente a un determinado tipo (o tipos) de excepciones pero dicho cdigo no es capaz o o de generar dichas excepciones entonces el compilador interrumpe la compilacin y avisa del o error: Por ejemplo, dado el siguiente fragmento de cdigo: o
try { int i = 0; } catch (FileNotFoundException fnfe) { [Link]("..."); }
127
En el ejemplo anterior, se indica que el bloque try-catch est capturando una excepcin a o (de tipo FileNotFoundException que nunca podr ser lanzada por el cdigo incluido en el a o bloque try. Por ultimo, conviene saber que existe un cierto subconjunto de tipos de excepciones que no necesitan ser expl citamente capturadas. Estos tipos de excepciones se conocen como excepciones no capturadas (se presentan en el apartado 5.2.7. En estos casos, el compilador no obliga a capturarlas mediante bloques try-catch (o clusulas throws). a Asegurando el estado Conviene tener en cuenta que, en caso de que se produzca una excepcin al ejecutar un o cierto conjunto de instrucciones contenidas en un bloque try, en general no se sabe con seguridad qu instrucciones se han ejecutado correctamente, cules han fallado y cules ni e a a siquiera se han ejecutado. En general, en el bloque catch se debe asumir que todas las instrucciones del bloque try han fallado. En el ejemplo anterior, se podr sustituir el bloque catch propuesto, por ste otro: a e
} catch (Exception e) { // Asignacin de valores por defecto o d1 = 0; d2 = -1; d3 = 1000; }
As la ejecucin del programa podr continuar, utilizando unos valores por defecto (en , o a lugar de terminar la ejecucin, como en el ejemplo inicial). En este caso, es necesario dar un o valor por defecto a los tres datos le dos en el bloque try, porque no se sabe con seguridad qu datos han sido le e dos correctamente y cules no. a
5.2.2.
En el ejemplo propuesto, se utiliza un bloque catch que permite capturar excepciones de tipo Exception:
} catch (Exception e) { ... }
De esta manera, se captura cualquier excepcin de tipo Exception o de cualquiera de o sus subtipos. En general, esta es una buena forma de proteger un cierto fragmento de cdigo o fuente frente a un conjunto bastante amplio de excepciones. De forma alternativa, es posible renar un poco el tipo de las excepciones capturadas. En el ejemplo propuesto, las instrucciones incluidas en el bloque try solamente pueden lanzar un subconjunto concreto de excepciones, como FileNotFoundException y otras subclases de IOException. En este caso ser posible cambiar la declaracin anterior por sta otra: a o e
} catch (IOException e) { ... }
De esta manera, el bloque catch solo captura aquellas excepciones que sean de tipo IOException o de alguna de sus subclases (por ejemplo, FileNotFoundException). Este mecanismo de captura selectiva de excepciones resulta muy util en combinacin con o el mecanismo de captura mltiple de excepciones (descrito en el apartado 5.2.3), que permite u capturar distintos tipos de excepciones y manejar de forma distinta cada uno de ellos.
128
5.2.3.
En ocasiones, interesa proteger un determinado bloque de cdigo frente a varias excepo ciones y manejar cada una de ellas de forma distinta. En estos casos, es posible indicar varios bloques catch alternativos, cada uno de los cuales se utilizar para capturar un tipo distinto a de excepciones. En el siguiente ejemplo, las excepciones relacionadas con la entrada/salida se manejan de una forma y el resto de excepciones se manejan de otra forma:
try { FileInputStream f = new FileInputStream("[Link]"); // Lectura de los datos ... } catch (IOException ioe) { // Asignacin de valores por defecto o d1 = 0; d2 = -1; d3 = 1000; } catch (Exception e) { [Link]("Se ha producido una excepcin"); o return; }
As en caso de que se produzca una excepcin de entrada/salida, el programa contin, o uar (con unos valores por defecto). En caso de que se produzca cualquier otra excepcin, a o la ejecucin del mtodo actual ser interrumpida, mediante la sentencia return. o e a En el ejemplo anterior, cuando se produce una excepcin, su tipo es comparado con los o tipos indicados en los distintos bloques catch, en el orden en que aparecen en el cdigo o fuente, hasta que se encuentra un tipo compatible. Por ejemplo, si en el caso anterior ocurre una excepcin de tipo FileNotFoundException, en primer lugar dicho tipo ser comparao a do con IOException. Como FileNotFoundException es un subtipo de IOException, entonces se ejecutar el primer bloque catch. En caso de que se produzca una excepcin de a o tipo IndexOutOfBoundsException (u otro tipo distinto a IOException o a alguno de sus subtipos), entonces se comparar dicho tipo con el tipo Exception declarado en el segundo a bloque catch. Como IndexOutOfBoundsException es un subtipo de Exception, la excepcin ser manejada en el segundo bloque catch. o a Por ello, el orden en que se indican los distintos bloques catch es muy importante, pues determina qu bloque catch se ejecutar en cada caso. Adems, hay que tener en cuenta e a a que no todas las combinaciones posibles son correctas. Si, en el ejemplo anterior, el orden de los bloques catch fuera el contrario (primero un catch capturando Exception y luego el otro capturando IOException), obtendr amos un error de compilacin, debido a que el o tipo Exception es ms general que el tipo IOException y ste a su vez es ms general que a e a FileNotFoundException. Por eso, conviene elegir cuidadosamente el orden de los bloques catch. Los bloques catch que capturan excepciones de tipos concretos (por ejemplo, FileNotFoundException) deben ser los primeros. A continuacin se indican los bloques para excepciones algo ms o a generales (por ejemplo, IOException). Por ultimo se indican los bloques con excepciones muy generales (por ejemplo, Exception). En cualquier caso, nunca se ejecuta ms de un bloque catch. En el ejemplo anterior, si a se produce una excepcin de tipo FileNotFoundException, sta ser manejada unicamente o e a en el bloque catch que captura IOException. Una vez terminado dicho bloque catch, no
129
se ejecutar ningn otro bloque, sino que la ejecucin del programa continuar a partir de a u o a la sentencia siguiente al bloque try-catch. Por ultimo, conviene tener en cuenta que los nombres de las variables utilizados en las clusulas catch no deben colisionar entre s ni con ninguna otra variable o atributo. En el a ejemplo anterior, se han utilizado las variables ioe, para el bloque que captura IOException y e, para el bloque que captura Exception (asumiendo que no hay ningn atributo ni variable u llamados ioe o e). Es una buena prctica utilizar algn convenio para nombrar las variables a u utilizadas en las clusulas catch. Un convenio conocido es utilizar las iniciales de cada tipo a de excepcin (ioe para IOException, fnfe para FileNotFoundException, etc.). Puede o ocurrir que en un mismo mbito (por ejemplo, en un mismo mtodo, haya varios bloques a e try-catch, que capturen el mismo tipo de excepcin. En este caso, se recomienda numerar o las variables (por ejemplo, ioe, ioe2, etc., para distinguir distintas excepciones de tipo IOException).
5.2.4.
try-catch anidados
En algunos casos, el cdigo manejador de una excepcin puede a su vez lanzar una o o excepcin. En el siguiente ejemplo, se intenta abrir un determinado chero y leer algunos o datos. Como estas operaciones pueden lanzar una excepcin, se protegen mediante un bloque o try-catch. El manejo de las posibles excepciones consiste en abrir un segundo chero y guardar unos ciertos datos. Como estas operaciones tambin pueden lanzar excepciones, es e necesario protegerlas mediante su correspondiente bloque try-catch.
try { f = new FileInputStream("[Link]"); a = [Link](); b = [Link](); ... } catch (Exception e) { try { FileOutputStream f2 = new FileOutputStream("[Link]") [Link](...); } catch (Exception e2) { ... } }
Aunque el lenguaje permite el uso de varios niveles de anidamiento de bloques try-catch, en general es recomendable evitar su uso, pues dicultan la lectura y el mantenimiento del cdigo. En su lugar, es preferible una solucin alternativa mediante sentencias de control o o convencionales. El ejemplo anterior podr reescribirse de la siguiente forma: a
boolean datosLeidos; try { f = new FileInputStream("[Link]"); a = [Link](); b = [Link](); ... datosLeidos = true; } catch (Exception e) { datosLeidos = false; }
130
Cap tulo 5. Manejo de excepciones if (datosLeidos == false) { try { FileOutputStream f2 = new FileOutputStream("[Link]") [Link](...); } catch (Exception e2) { ... } }
En el apartado 5.4 se relacionan los bloques try-catch anidados con otras caracter sticas del tratamiento de excepciones en Java.
5.2.5.
La sentencia nally
En ocasiones, a la hora de escribir un bloque try-catch, es conveniente incluir algn u fragmento de cdigo que se ejecute tanto si el bloque try se ejecuta correctamente por o completo como en el caso de que se produzca una excepcin. Para ello, se puede aadir una o n clusula finally a un bloque try-catch, tal como se ilustra en el siguiente ejemplo: a
FileInputStream f = null; int a, b; try { f = new FileInputStream(fichero); a = [Link](); b = [Link](); ... } catch (IOException e) { ... } finally { if (f != null) [Link](); }
En el ejemplo anterior, se utiliza un bloque finally para cerrar el ujo de datos, en caso de que haya sido abierto. En el apartado 5.4 se relaciona la clusula finally con otras caracter a sticas del tratamiento de excepciones en Java y los bloques try-catch.
5.2.6.
A modo de resumen de los conceptos relacionados con el uso de excepciones en Java presentados hasta el momento, conviene presentar la forma general de las sentencias try-catch:
try { // Fragmento de cdigo que puede generar una excepcin o o } catch (tipo_de_excepcin variable) { o // Fragmento de cdigo manejador de la excepcin o o } catch (tipo_de_excepcin variable) { o // Fragmento de cdigo manejador de la excepcin o o } ... { } finally { // Cdigo que se ejecuta siempre o }
131
Como se ha indicado anteriormente es posible utilizar varios bloques catch, aunque es posible utilizar varios, para manejar de forma diferente distintos tipos de excepciones. Adems, es posible incluir un bloque finally opcional. En caso de utilizarlo, este bloque a se ejecutar en cualquier caso, tanto si las instrucciones del bloque try se ejecutan correca tamente como si se produce alguna excepcin. o
5.2.7.
Excepciones no capturadas
Existe un subconjunto de excepciones que pueden producirse durante la ejecucin de o operaciones muy comunes. Por ejemplo, caulquier instruccin del tipo o
[Link](param1, param2, ...);
puede lanzar una excepcin de tipo NullPointerException en caso de que la referencia o objeto no haya sido inicializada y por tanto apunte a null. Otro ejemplo comn es la excepcin IndexOutOfBoundsException, que se puede lanzar u o en una instruccin como la siguiente o
int v = enteros[i]; [Link]).
en caso de que el valor de i no sea vlido (cuando es menor que 0 o mayor o igual que a
Existen otros casos muy comunes como las excepciones de tipo ArithmeticException, que se lanza, por ejemplo, al hacer una divisin entre cero, o las de tipo ClassCastException, o que se lanzan cuando se intenta hacer una operacin de casting incorrecta, como la siguiente: o
Linea linea = new Linea(punto1, punto2); Cuadrado cuadrado = (Cuadrado)linea;
Si se quisiera proteger el cdigo que puede lanzar estas excepciones, prcticamente habr o a a que proteger cualquier l nea de cdigo dentro de algn bloque try-catch. El resultado ser o u a un programa poco legible e inmantenible. Para evitar este problema, stas y otras excepciones no necesitan ser capturadas. En su e lugar, se conf en que el programador tomar las medidas necesarias para evitar que pasen. a a Por ejemplo, para evitar las excepciones de tipo NullPointerException, el programador debe asegurarse de inicializar correctamente sus objetos. Adems, puede hacer comprobaa ciones del tipo if (objeto == null) ..., para evitar que ocurran dichas excepciones. Para evitar excepciones de tipo IndexOutOfBoundsException, el programador debe asegurarse de que los ndices utilizados en los accesos a arrays son correctos. En el caso de las excepciones de tipo ArithmeticException, se deben hacer las comparaciones sobre los operandos de las expresiones matemticas a evaluar y en el caso de las excepciones de tipo a ClassCastException, se puede hacer uso del operador instanceof para comprobar previamente que el cast a realizar es correcto. En otros casos, el programador deber hacer las a comprobaciones correspondientes. Todos los tipos de excepciones que no necesitan ser capturadas son subclases (directas o indirectas) de RuntimeException, que a su vez es una subclase de Exception, como se indica en la gura 5.2. A este tipo de excepciones se les suele llamar excepciones no capturadas 3 , porque no suelen capturarse expl citamente. Conviene notar que, en caso de que sea necesario por algn motivo, estas excepciones u pueden ser capturadas como cualquier otra. En el siguiente ejemplo se manejan de forma distinta las excepciones debidas a la evaluacin matemtica y el resto de excepciones: o a
3 En
132
Throwable c z
int resultado; try { dato1 = ... dato2 = ... resultado = ... } catch (ArithmeticException ae) { [Link]("Los operandos no son correctos"); resultado = 0; } catch (Exception e) { return; }
Conviene notar que este uso de los bloques try-catch es, en cierta manera, una forma de controlar el ujo del programa. Por ejemplo, el ejemplo anterior es similar al siguiente:
int resultado; dato1 = ... dato2 = ... if (correctos(dato1, dato2) == true) { resultado = ... } else { ... }
Aunque es posible utilizar los bloques try-catch para realizar el control de ujo de un programa, en general no es recomendable . En su lugar, es preferible utilizar sentencias de control de ujo convencionales (por ejemplo, condiciones if).
5.3.
La clase Throwable
Tal como se ha indicado en el apartado 5.1, la clase Throwable es la clase base que representa a todas las excepciones que pueden ocurrir en un programa Java. En esta clase existen varios mtodos utiles, que son heredados por las distintas subclases e (directas e indirectas) de Throwable. En los siguientes subapartados se presentan dos de los mtodos ms utilizados. En la documentacin de la clase Throwable es posible encontrar e a o mtodos adicionales que pueden resultar utiles en las fases de depuracin de un programa e o Java.
133
5.3.1.
El mtodo getMessage e
Uno de los mtodos ms utiles denido en la clase Throwable y heredado por todas e a sus subclases es el mtodo getMessage. Este mtodo permite obtener informacin de una e e o excepcin. o El siguiente ejemplo ilustra su uso:
try { C c = new C(); c.m1(); } catch (FileNotFoundException fnfe) { [Link]([Link]()); }
En el bloque catch se capturan las excepciones de tipo FileNotFoundException, que se producen cuando se intenta abrir un chero que no existe. En caso de que se produzca una excepcin de dicho tipo, en la salida estndar del programa se escribir algo como: o a a
[Link] (No such file or directory)
(suponiendo que el chero que se intenta abrir se llame [Link]). Como se puede observar, el mtodo getMessage permite obtener informacin util sobre e o la excepcin. El usuario puede utilizar esta informacin para intentar solucionar el error. Por o o ejemplo, en este caso, se muestra el nombre del chero que se intenta abrir. Esta indicacin o podr servir para que el usuario recuerde que es necesario que el chero de datos utilizado a por el programa debe tener un nombre concreto, deber estar ubicado en algn directorio u concreto, etc.
5.3.2.
El mtodo printStackTrace e
Otro de los mtodos ms utiles es el mtodo printStackTrace. Este mtodo se utiliza e a e e normalmente dentro de un bloque catch, como parte del cdigo manejador de una excepcin, o o para mostrar la secuencia de operaciones que han provocado la excepcin: o
try { FileInputStream f; f = new FileInputStream("[Link]"); ... } catch (FileNotFoundException fnfe) { [Link](); }
134
En la primera l nea de la traza se indica el tipo de excepcin que se ha producido (en o el ejemplo, [Link]) y un mensaje con ms informacin. Ntese a o o que este mensaje es exactamente el mismo devuelto por el mtodo getMessage, segn se e u indic en el apartado anterior. o Adems, el mtodo printStackTrace escribe en la salida estndar del programa una a e a traza de la excepcin. La traza est formada por varias l o a neas que permiten conocer cul a ha sido el ujo del programa, justo hasta el momento en que se produjo la excepcin. Si se o observan las l neas, es posible saber que la excepcin se produjo en el mtodo open de la clase o e FileInputStream. Dicho mtodo fue invocado por el constructor de FileInputStream4 , e en la l nea 106 del chero FileInputStream.java5 , que a su vez fue invocado por otro constructor distinto de FileInputStream, en la l nea 66 del mismo chero. Este otro constructor fue invocado por el mtodo m2 de la clase C (en la l e nea 11 del chero [Link]). Dicho mtodo fue invocado por el mtodo m1 de la clase C (en la l e e nea 6 del mismo chero) y ste a su vez fue invocado por el mtodo main de la clase EjemploTraza (en la l e e nea 19 del mismo chero). Esta informacin es importante para depurar el error producido y corregirlo. La forma o habitual de depurar un programa a partir de una traza consiste en inspeccionar todas las l neas de la traza y averiguar cul es la primera que pertenece a alguna clase del programa a que se est escribiendo. En este caso, esa primera l a nea ser la cuarta l a nea de la traza anterior (la l nea 11 de [Link]). Una vez identicado dicho punto, se debe revisar con detenimiento la l nea del chero fuente indicado, en busca del error.
5.4.
En los apartados anteriores se ha presentado la forma en que un programa Java puede capturar y manejar excepciones, de manera que las excepciones son manejadas lo antes posible a partir del momento en que se producen. Sin embargo, en ocasiones conviene retrasar el manejo de una excepcin por diversos o motivos. Considrese el siguiente mtodo: e e
private void leerDatos (String fichero) { FileInputStream f = new FileInputStream(fichero); [Link](...); [Link](); }
Como en casos anteriores, es necesario proteger mediante un bloque try-catch las instrucciones anteriores, frente a las excepciones de tipo IOException que puedan producirse. El mtodo modicado ser el siguiente: e a
private void leerDatos (String fichero) { try { FileInputStream f = new FileInputStream(fichero); [Link](...); [Link](); } catch (IOException ioe) { ...
5 Este 4 Se
sabe que el invocador fue un constructor, por la cadena init que aparece en la l nea correspondiente. chero pertenece a las librer estndar de Java. as a
135
Una alternativa ser propagar las excepciones, lo que permitir retrasar su manejo. En a a lugar de utilizar un bloque try-catch que contenga las instrucciones que pueden lanzar excepciones, esta alternativa consiste en utilizar una clusula throws, en la declaracin del a o mtodo que contiene dichas instrucciones, de la siguiente manera: e
private void leerDatos (String fichero) throws IOException { FileInputStream f = new FileInputStream(fichero); [Link](...); [Link](); }
Mediante la clusula throws, se est indicando que el cdigo del mtodo leerDatos a a o e puede lanzar excepciones de tipo IOException. Esto obliga a que cualquier invocacin del o mtodo leerDatos est protegida mediante un try-catch donde se capturen las excepciones e e de tipo IOException (o alguna clase padre, como Exception):
private void leerDatosCompras () { try { leerDatos("[Link]"); leerDatos("[Link]"); } catch (IOException ioe) { [Link]("No es posible leer los datos de compras"); return; } }
De esta manera, es posible hacer un manejo de excepciones globalizado. En el ejemplo anterior, no interesa hacer un tratamiento particular en funcin del chero cuya lectura haya o fallado, sino un tratamiento global. En cualquier caso, siempre es necesario usar alguna de las dos alternativas. Si una instruccin puede lanzar excepciones de un determinado tipo, entonces stas deben ser capturadas o e mediante un bloque try-catch que contenga a dicha instruccin o, alternativamente, esta o instruccin debe pertenecer a un mtodo en cuya declaracin se incluya una clusula throws o e o a con la excepcin correspondiente. o
5.4.1.
En ocasiones interesa utilizar las dos alternativas para capturar excepciones presentadas hasta el momento: la captura inmediata de excepciones y la propagacin y captura tard o a de las excepciones. Existen dos casos donde conviene combinar ambas tcnicas. El primer caso se da cuando e se pretende manejar dos (o ms) veces una misma excepcin, desde distintos lugares del a o programa. T picamente uno de los lugares es el mtodo donde se lanza la excepcin y el otro e o es un segundo mtodo, que invoca al primero. e Existe un segundo caso, que se da cuando se preere capturar, en el momento en que se producen, solamente algunas excepciones. El resto de excepciones que se pudieran lanzar sern manejadas por el mtodo invocante. a e En los siguientes apartados se exploran ambos casos.
136
Caso 1: capturando varias veces la misma excepcin o En el siguiente ejemplo se captura una excepcin justo en el momento en que se produce, o luego se relanza y posteriormente se vuelve a capturar, en el mtodo invocante. e
public void calcularBalance () { float nuevoBalance; try { ... float ingresos = calcularIngresos(); float gastos = calcularGastos(); float variaciones = calcularVariaciones(); nuevoBalance = ingresos - gastos + variaciones; } catch (Exception e) { [Link]("No es posible calcular el balance"); [Link] = 0; return; } [Link] = nuevoBalance; } public float calcularIngresos () throws ArithmeticException { try { float ingresos = ... return ingresos; } catch (ArithmeticException ae) { [Link]("No es posible calcular los ingresos"); // Relanzar la excepcin que se acaba de capturar o throw ae; } }
En este ejemplo, la captura inmediata de las excepciones de tipo ArithmeticException permite hacer un primer manejo de las excepciones de este tipo. La sentencia throw que hay en el bloque catch del mtodo calcularIngresos permite relanzar la excepcin. Si se produce e o una ArithmeticException en el mtodo calcularIngresos, se ejecuta el bloque catch e correspondiente. Cuando la ejecucin del mtodo calcularIngresos alcanza la sentencia o e throw, la ejecucin del bloque catch se interrumpe y la ejecucin salta al bloque catch del o o mtodo calcularBalance, donde dicha excepcin (en forma de Exception) es manejada. e o En el mtodo calcularBalance, es posible hacer un segundo tratamiento de la excepe cin. Sin embargo, ntese que en el mtodo calcularBalance se est capturando cualquier o o e a excepcin (subclase de Exception), en lugar de capturar espec o camente las excepciones de tipo ArithmeticException. Esto permite capturar, con el mismo bloque catch, las excepciones de tipo ArithmeticException producidas por los mtodos calcularIngresos e y calcularGastos y adems, cualquier excepcin de otro tipo producida en algn otro a o u mtodo y por tanto manejar de la misma manera todas las excepciones. e Caso 2: Eligiendo entre captura inmediata y propagacin de excepciones o En este segundo caso se estudia la posibilidad de capturar solo algunas excepciones de forma inmediata y propagar el resto de excepciones al mtodo invocante. e Por ejemplo, el mtodo calcularIngresos podr ser el siguiente: e a
5.4. Captura vs. propagacin de excepciones: la sentencia throws o public float calcularIngresos () throws IOException { try { FileInputStream f = new FileInputStream(...); float ingresos = ... return ingresos; } catch (ArithmeticException ae) { ... } }
137
En este caso, el bloque catch solo captura las excepciones de tipo ArithmeticException. Las posibles excepciones de tipo IOException son propagadas al mtodo invocante (el mtoe e do calcularBalance, del ejemplo anterior) y capturadas por ste. e
5.4.2.
Si se produce una excepcin, se ejecutar el bloque catch. Antes de ejecutar la sentencia o a throw, se ejecutar el bloque finally. Terminado este bloque, se realizar la propagacin a a o de la excepcin, por lo que esta ser entregada al mtodo invocante. o a e Clusula throws Considrese el siguiente ejemplo: a e
public void m () throws Exception { try { FileInputStream f = new FileInputStream(...); ... } catch (IOException ioe) { ... } finally { ... } }
Si se produce una excepcin de un tipo distinto a IOException, por ejemplo una excepo cin de tipo NullPointerException, el bloque catch no se ejecutar, pero s se ejecutar el o a a bloque finally. Por ultimo, se propagar la excepcin al mtodo invocante. a o e
138
5.5.
En los apartados anteriores se ha presentado el soporte de excepciones ofrecido por la plataforma de desarrollo Java y se ha hecho un especial nfasis en la captura de las excepe ciones. Tambin se ha introducido la forma en que un programa puede propagar y relanzar e excepciones. Otra posibilidad ofrecida por el lenguaje Java es la de lanzar nuevas excepciones. Estas pueden ser de algn tipo existente (como IOException) o incluso pueden ser instancias u de nuevos tipos de excepciones. Estas nuevas posibilidades se presentan en los siguientes apartados.
5.5.1.
En este mtodo, se comprueba la validez de determinados parmetros y en su caso, se e a calcula un cierto balance. En caso de que algn parmetro no sea correcto, se devolver cou a a mo resultado un determinado valor especial (-1). Esta solucin presenta dos problemas. El o primer problema es la eleccin del valor especial. Este no puede ser un valor que pueda o ser considerado como un resultado correcto, pues entonces no habr forma de saber si el a valor devuelto es el valor especial que indica un error en los parmetros o en cambio es el a valor del balance. En el ejemplo anterior, el valor -1 no parece un valor adecuado, pues no permite distinguir aquellos casos en que realmente el balance tiene un valor igual a -1. El segundo problema es que el mtodo que invoca al mtodo calcularBalance debe conocer e e qu valor se ha decidido utilizar en caso de error. Si, en algn momento, se decide cambiar e u el valor especial -1 a otro valor (como -123456789), entonces habr que comprobar y posia blemente cambiar tambin todos aquellos fragmentos de cdigo donde se invoque al mtodo e o e calcularBalance. Estos problemas se podr solucionar sustituyendo el mtodo calcularBalance por an e esta otra versin: o
public void calcularBalance (float ingresos, float gastos, float variaciones) throws IllegalArgumentException { { boolean correcto = validar(ingresos, gastos, variaciones); if (correcto == false) throw new IllegalArgumentException("Los datos no son correctos"); else { balance = f(ingresos, gastos, variaciones); return balance; } }
139
En esta nueva versin, se lanza una excepcin en caso de que los parmetros no sean o o a correctos. Esta excepcin es de tipo IllegalArgumentException, un subtipo de Exception, o utilizado para sealar precisamente aquellas situaciones en que los parmetros o datos de n a entrada proporcionados no son correctos por algn motivo. u De esta forma, no es necesario utilizar ningn valor especial para sealar una situacin u n o anormal y por tanto el mtodo que invoca al mtodo calcularBalance no tiene por qu conoe e e cer ningn valor especial. u
5.5.2.
Como segunda mejora al ejemplo anterior, se podr utilizar un nuevo tipo de excepciones, a creado espec camente para sealar tal condicin errnea. n o o En primer lugar, es posible crear un nuevo tipo de excepciones, simplemente deniendo la siguiente clase:
public class ParametrosException extends Exception { private float gastos; private float ingresos; private float variaciones; public ParametrosException (float gastos, float ingresos, float variaciones) { [Link] = gastos; [Link] = ingresos; [Link] = variaciones; } public String getMessage () { return "Parmetros incorrectos: gastos=" + gastos + a ", ingresos=" + ingresos + ", variaciones=" + variaciones; } }
validacin ha producido la excepcin. o o Una vez denida la clase, es posible crear un nueva instancia, mediante el operador new y lanzar la excepcin creada, mediante una sentencia throw, como en el siguiente ejemplo: o
public void otroMetodo (float gastos, float ingresos, float variaciones) { boolean correctos = comprobar(gastos, ingresos, variaciones); if (correctos == false) throw new ParametrosException(gastos, ingresos, variaciones); else ... }
La clase ParametrosException es considerada una excepcin ya que extiende la clase o Exception6 . Por ello, podemos capturarla en bloques try-catch, lanzarla mediante sentencias throw, declararla en mtodos mediante clusulas throws, etc. e a En la clase ParametrosException se sobreescribe el mtodo getMessage (heredado e de Throwable), para que devuelva una cadena indicando los valores de los atributos cuya
6 En la prctica, en ocasiones conviene utilizar como clase base una excepcin de un tipo ms concreto. a o a En el ejemplo anterior, en lugar de utilizar la clase Exception como clase base se podr utilizar la clase a IllegalArgumentExcepion, que en general se utiliza para representar excepciones producidas por operaciones cuando reciben parmetros incorrectos. a
140
Esta excepcin se podr capturar igual que el resto de excepciones, como se ha indicado o a en apartados anteriores. Por ejemplo, es posible utilizar el siguiente cdigo: o
try { otroMetodo(1.0, 2.0, 3.0); } catch (Exception e) { [Link](); }
Ntese que en la primera l o nea de la traza se incluye la cadena devuelta por el mtodo e
5.6.
Cuestiones
1. Indica cul es la clase base de la jerarqu de excepciones de Java. Indica cules son sus a a a subclases directas y las diferencias entre ellas. Consulta la ayuda de la librer estndar a a para obtener ms informacin. a o 2. Indica el nombre de tres tipos de excepciones que no se hayan nombrado en este cap tulo. Para ello, consulta la ayuda de la librer estndar de Java. a a 3. Explica qu ventajas e inconvenientes presentan las excepciones no capturadas frente e el resto de excepciones. 4. Explica en qu casos es necesario hacer una captura mltiple de excepciones (mediante e u varios bloques catch). 5. Explica la utilidad de los bloques finally. Indica en qu casos son obligatorios. e
5.7.
Ejercicios resueltos
public void metodo (int arg1, int arg2) { try float auxiliar = 0; auxiliar = arg1 / arg2; catch Exception e { [Link]("auxiliar = " + auxiliar); throws e; } finally { [Link]("Terminando el try-catch..."); throw e; } } catch (ArithmeticException e) {
5.7. Ejercicios resueltos [Link]("Los parmetros no son correctos"); a } catch (IOException e) { [Link]("Excepcin de entrada/salida"); o } [Link]("auxiliar es " + auxiliar);
141
Solucin o a) El bloque try debe ir encerrado entre llaves ({ y }). b) En el primer bloque catch, la expresin Exception e debe escribirse entre parnteo e sis. c) La variable e se est utilizando en los tres bloques catch (solo puede ser utilizada a en uno de ellos). d ) La sentencia throws e debe ser throw e (la palabra reservada throws solo se utiliza en la declaracin de los mtodos). o e e) Si se quiere permitir relanzar excepciones de tipo Exception, mediante una sentencia throw e, es necesario aadir la clusula throws Exception en la declaracin n a o del mtodo. e f ) El orden de los bloques catch es incorrecto. En primer lugar debe indicarse el catch para las excepciones de tipos ms concretos (ArithmeticException a e IOException) y posteriormente deben indicarse los de tipos ms generales a (Exception). g) El bloque finally debe estar despus de todos los bloques catch. e h) En un bloque finally no es posible ejecutar una sentencia throw. i ) La variable auxiliar solo es visible en el bloque try (no es visible en los bloques catch ni fuera del bloque try). Debe declararse fuera del bloque try-catch, para que sea visible en todo el mtodo. e j ) Las sentencias del bloque try no pueden lanzar nunca una IOException, por lo que el bloque catch que captura IOException debe ser eliminado. 2. Dado el siguiente fragmento de cdigo: o
LinkedList lista = new LinkedList(); [Link]("uno"); [Link]("dos"); String p = (String)[Link](0);
averigua, mediante la ayuda de la librer estndar de Java, qu tipos de excepciones a a e pueden lanzarse. Aade el m n nimo cdigo imprescindible para capturar y manejar por o separado cada uno de dichos tipos. Solucin Ni el constructor utilizado ni el mtodo add lanzan ninguna excepcin. El o e o mtodo get puede lanzar una excepcin de tipo IndexOutOfBoundsException, pero e o sta es una excepcin no capturada, por lo que no es necesario capturarla ni propagarla. e o 3. Modica el siguiente cdigo para que propague las excepciones, en lugar de capturarlas: o
try { FileInputStream f = new FileInputStream("[Link]"); i = [Link](...); ... } catch (IOException e) { [Link]("Error de entrada/salida: " + [Link]()); return; } catch (Exception e) { [Link](); return; } if (i == 0) ...
Solucin La solucin ms sencilla consiste en eliminar la clusula try y los bloques o o a a catch y a adir la clusula throws, como se indica a continuacin: n a o
public void metodo () throws Exception { int i; FileInputStream f = new FileInputStream("[Link]"); i = [Link](...); if (i == 0) ...
Sin embargo, conviene tener que con esta solucin se elimina la posibilidad de manejar o de forma diferente excepciones de distinto tipo.
5.8.
Ejercicios propuestos
1. Dado el siguiente cdigo, indica qu problema existe (si es necesario, completa el ejempo e lo con el cdigo necesario para que pueda compilarse y ejecutarse, comp o lalo e interpreta los mensajes de error proporcionados por el compilador):
public void m1 () { try { m2(1, 0); } catch (ArithmeticException ae) { [Link]("Error aritmtico al ejecutar m2"); e [Link](); } } public void m2 (int a, int b) throws Exception { try {
5.8. Ejercicios propuestos float f = a/b; [Link]("f = " + f); } catch (ArithmeticException ae) { [Link](); throw ae; } catch (Exception e) { [Link]("Error de otro tipo"); [Link](); }
143
2. Dadas las siguientes clases Java, indicar qu se escribe en la salida estndar cuando se e a ejecuta el mtodo main: e
class C { private int a; public void m1 () { a = 0; m2(); [Link](a); } public void m2 () throws NullPointerException { try { m3(); } finally { [Link](a); a = 1; } [Link](a); } private void m3 () { if (a == 0) throw new NullPointerException(); } } public class TryFinally { public static void main (String [] args) { new C().m1(); } }
144
Cap tulo 6
146
En consecuencia, un applet Java posee las siguientes caracter sticas: Se utiliza intensivamente la biblioteca AWT (Abstract Window Toolkit), donde se denen los componentes de interfaz (o componentes de interaccin de usuario: botones, o mens, campos de edicin de texto. . . ), primitivas grcas, y eventos generados por el u o a usuario; as como la biblioteca Swing, que extiende a la anterior para crear componentes ligeros (lightweight), incorporando nuevas clases (los JComponents) y mejorando el aspecto visual. Cada componente de interaccin posee una representacin grca y un o o a conjunto de mtodos para su uso desde el programa. Adems, cada componente ree a conoce y reacciona ante determinados eventos (por ejemplo, un botn reacciona ante o un click de ratn) propagando el evento a aquellas partes del programa que previamente o han informado de su inters en gestionar los mismos. e La entrada no se basa en mtodos orientados a consola, como [Link](). e Como ya se ha mencionado, las aplicaciones grcas se estructuran segn el modelo a u denominado programacin dirigida por eventos. El programa ja una interfaz y especio ca las respuestas adecuadas (acciones) ante los posibles eventos (por ejemplo, generados por el ratn o teclado). Durante la ejecucin, el programa se limita a responder ante o o los eventos disparando la accin adecuada. o De la misma forma, la salida no se basa en mtodos orientados a consola como por e ejemplo [Link](), sino que se utiliza en su lugar el concepto de contexto grco y un conjunto de primitivas grcas para: gestionar texto (distintas fuentes, a a tamaos, etc.); crear l n neas, c rculos, rectngulos, etc., con o sin relleno; gestionar a colores de dibujo, relleno y fondo. . . Una aplicacin Java autnoma (lanzada directamente desde el sistema operativo) t o o picamente funciona o en modo consola o en modo grco. Un applet unicamente puede funcionar a en modo grco. Una aplicacin autnoma en modo grco es tan parecida a un applet que a o o a se pueden construir fcilmente programas que se ejecuten indistintamente desde el sistema a operativo (como aplicacin autnoma) o en el contexto de un navegador (como applet). o o
6.2.
6.2.1.
Applets
Concepto de applet
Un navegador web es un programa diseado para descargar un chero html (a partir n del almacenamiento local o a travs de la red), interpretar su contenido, y crear una imagen e del mismo. Un chero html contiene un conjunto de etiquetas (tags) que delimitan sus contenidos. El estndar html dene distintas etiquetas para propsitos diferentes: delimitar a o un enlace dinmico, indicar un tipo de letra espec a co, indicar la inclusin de un chero o de imagen o de sonido, etc. Adems, dene tambin una etiqueta espec a e ca para incluir referencias a programas Java (applets). Supongamos que se ejecuta un navegador web para acceder a un chero html que contiene la siguiente informacin (los puntos suspensivos indican informacin adicional que no afecta o o a la discusin), o
... <APPLET CODE="[Link]" HEIGHT="400" WIDTH="700"></APPLET> ...
Cuando el navegador descarga el chero html debe interpretar su contenido (a n de formatearlo y mostrarlo en pantalla). Al encontrar la etiqueta <APPLET...> descarga de la
6.2. Applets
147
mquina B el chero denominado [Link] que corresponde a la clase principal de un applet a Java. El navegador utiliza la mquina virtual Java disponible en la mquina, sobre la que a a crea una instancia de la clase X, a la que lanza una secuencia de mensajes determinados (ciclo de vida del applet). Al visualizar el documento en pantalla, el navegador debe reservar la zona de visualizacin o del applet. En el ejemplo, se trata de una zona de 700 p xels de anchura y 400 de altura. Dicha zona forma parte del documento que se visualiza (la ubicacin exacta dentro del mismo o depende de los restantes contenidos del chero). As pues, los atributos CODE, HEIGHT y WIDTH son los m nimos y necesarios para denir una etiqueta APPLET, ya que el navegador necesita conocer tanto la clase a ejecutar como el tamao de la zona de visualizacin. n o Mientras el navegador muestre el documento en pantalla, el usuario puede interactuar con el applet mediante eventos sobre el rea de interaccin, y el applet utiliza el rea de intera o a accin para mostrar cualquier informacin pertinente al usuario. El applet termina cuando o o el navegador descarta el documento actual. Un applet se implementa como una extensin de la clase [Link], si quereo mos trabajar con AWT, o, en el caso de emplear Swing, como extensin de la clase [Link], o que es una subclase de [Link]. A su vez, la clase [Link] extiende a [Link], que extiende a [Link] y ste a [Link]. e El resultado es que las clases Applet y JApplet heredan los mtodos de dibujo (primitivas e grcas, componentes de interfaz, etc.) y gestin de eventos denidos en esas clases. Cuando a o se indica que la clase principal de un programa extiende Applet o JApplet, dicho programa puede trabajar en modo grco y ser dirigido por eventos. Como la biblioteca Swing es ms a a avanzada que AWT, a partir de ahora slo se emplear la clase JApplet para implementar o a los applets. La respuesta por defecto de la clase JApplet ante los eventos de usuario y los mensajes procedentes del navegador es no hacer nada. En consecuencia, la tarea del programador es extender la clase JApplet (denir una nueva clase derivada de JApplet) para introducir el cdigo necesario ante los eventos o mensajes a los que se desee responder. o Ejemplo: Se desea desarrollar un applet que muestre un saludo en pantalla. Adems de a editar y compilar el programa [Link], se debe crear un documento html ([Link]) que incluya una etiqueta espec ca para lanzar la ejecucin del applet. o Cdigo del chero [Link]: o
import [Link]; import [Link]; public class Hola extends JApplet { public void paint (Graphics g) { [Link]("Hola", 20, 20); } }
148
Al igual que cualquier otro programa Java, el chero .java contiene sentencias de importacin (en este ejemplo import [Link], donde se denen las primitivas o grcas; e import [Link], clase base para crear los applets en Swing), y a una secuencia formada por una o ms clases, de las cuales, una es la principal (en este ejema plo slo hay una, y se denomina Hola). La diferencia estriba en que en un applet la clase o principal es una extensin de la clase JApplet. o Para ejecutar el applet, el navegador crea una instancia de la clase principal (en el ejemplo, de la clase Hola) y provoca la invocacin de distintos mtodos del applet en respuesta a o e distintas situaciones (por ejemplo, inicio o n de la ejecucin, el rea de interaccin ha o a o quedado oculta, etc). Todo applet debe ajustarse a un marco estricto que dene su ciclo de vida (mecanismos para iniciar, parar, reanudar y terminar un applet). Cuando el navegador ha cargado los bytecodes correspondientes a la clase principal (la extensin de JApplet), crea una instancia de la misma sobre la que invoca el mtodo init(). o e El mtodo init() es el punto de entrada al applet y normalmente se utiliza para inicializar e las variables necesarias y dibujar los componentes de interaccin. En este caso no se necesita o cdigo alguno en init(), ya que no existen variables en la clase, ni se utilizan componentes o de interaccin, por lo que no se sobreescribe la versin por defecto proporcionada por la clase o o JApplet. El navegador invoca el mtodo paint() del applet cada vez que necesita redibujar el e a rea de interaccin (por ejemplo, al visualizar el documento por vez primera, o cada vez que o la zona vuelve a ser visible despus de haber permanecido oculta por ejemplo, estaba tras e otra ventana, la ventana del navegador estaba minimizada, etc.). En el ejemplo, paint() dibuja en pantalla el saludo, para lo cual utiliza el mtodo drawString() denido en la clase e Graphics. El parmetro de paint() es de tipo Graphics, y corresponde al contexto grco a a asociado al rea de interaccin del applet, por lo que los mtodos invocados sobre dicho a o e parmetro afectan al rea de interaccin. El aspecto visual de este applet en un navegador a a o es el que se muestra en la gura 6.1.
6.2.2.
El navegador controla el comportamiento del applet invocando cinco mtodos estndar del e a mismo: paint(), init(), start(), stop() y destroy(). El mtodo paint() se encarga, e como ya hemos visto, de redibujar el applet. Los otros cuatro mtodos denen el ciclo de e
6.3. Grcos a
149
vida del applet. En la tabla 6.1 se denen las acciones que deben realizar los mtodos y e cundo son invocados. a Mensaje
init()
Evento(s) del navegador Carga documento html con etiqueta <APPLET>, y la clase principal correspondiente. Inmediatamente despus e de init(). Cada vez que se vuelva a visitar el applet con el navegador. Cada vez que se detecta la necesidad de pintar el rea de pantalla del apa plet. Cuando la pgina web que contiene a al applet se reemplaza por otra. Inmediatamente antes de invocar a destroy(). Termina el applet (cuando se descarta el documento o se abandona el navegador).
start()
Comportamiento que se debe denir en el applet Cdigo de inicializacin (inicialo o izacin de los atributos, aadir como n ponentes de interaccin, etc.). o Iniciar animaciones y otras tareas. Redibujar lo necesario en el rea de a interaccin. o Suspender animaciones y otras tareas (para no consumir recursos innecesariamente). Limpieza de los recursos del sistema.
paint()
stop()
destroy()
Tabla 6.1: Ciclo de vida de un applet Cuando se disea un applet, se deben sobreescribir unicamente aquellos mtodos que se n e desea realicen alguna tarea (por ejemplo, Hola no debe responder a los mensajes que afectan al ciclo de vida, por lo que no se sobreescribe ninguno de los mtodos restantes init(), e start(), stop() o destroy()).
6.3.
Grcos a
Java proporciona distintas primitivas para dibujar guras, mostrar texto, gestionar colores, etc.
6.3.1.
Sistema de coordenadas
Se utiliza el sistema de coordenadas estndar bidimensional comn a la mayor parte de a u las interfaces grcas. La esquina superior izquierda de la zona de visualizacin se considera a o la posicin (0,0). Los valores de la componente x crecen hacia la derecha, mientras que los o valores de la componente y crecen hacia abajo.
6.3.2.
Figuras
Toda accin de dibujo de guras (l o neas, rectngulos, valos, pol a o gonos y sus rellenos) se lleva a cabo a travs de la clase Graphics (o contexto grco), que dene los distintos e a mtodos para dibujar guras. Cada objeto de tipo Graphics posee su propio origen de e coordenadas, colores de dibujo y fondo, etc. El cdigo de dibujo debe aparecer en el mtodo paint(Graphics g) del applet, de forma o e que se pueda reconstruir el contenido dibujado en el applet cuando lo solicita el navegador.
150
El parmetro g hace referencia a un contexto grco preexistente (creado por el entorno y a a asociado a la zona de interaccin del applet) sobre el que se pueden aplicar las operaciones o de dibujo. Las diferentes operaciones de dibujo denidas en Graphics son las que se muestran en la tabla 6.2. Se asume que x, y, ancho, alto, x1, y1, x2, y2, angIni y angArc son enteros, y p un objeto de tipo Polygon creado a partir de dos vectores de enteros para las coordenadas x e y, y el n mero de puntos a dibujar. u Accin o Dibujar l nea Dibujar rectngulo a Dibujar rectngulo relleno a Borrar rectngulo a Dibujar valo o Dibujar valo relleno o Dibujar arco Dibujar arco relleno Dibujar pol gono Mtodo e
drawLine(x1, y1, x2, y2) drawRect(x, y, ancho, alto) fillRect(x, y, ancho, alto) clearRect(x, y, ancho, alto) drawOval(x, y, ancho, alto) fillOval(x, y, ancho, alto) drawArc(x, y, ancho, alto, angIni, angArc) fillArc(x, y, ancho, alto, angIni, angArc) drawPolygon(p)
Tabla 6.2: Operaciones de dibujo en Graphics Ejemplos de utilizacin de los mtodos de la tabla 6.2 son los siguientes (g es un objeto o e de tipo Graphics):
[Link](0, 0, 99, 99); [Link](20, 20, 20, 20); [Link](40, 40, 15, 25); [Link](30, 30, 5, 5); [Link](25, 0, 50, 25); [Link](25, 75, 50, 25); [Link](57, 37, 30, 20, 45, 90); [Link](57, 42, 30, 20, 45, 90); int[] x = {25, 40, 10}, y = {60, 70, 90}; Polygon p = new Polygon(x, y, 3); [Link](p);
6.3.3.
Color
Java dene la clase Color, que declara diferentes constantes estticas que corresponden a a otros tantos colores predenidos: [Link], [Link], [Link], [Link], [Link], etc.). Adems, la clase Color permite construir cualquier otro color especia cando en el constructor sus valores RGB. Una vez denidos los colores, es posible especicar el color de dibujo y el color de fondo (setForeground(color) y setBackground(color), respectivamente). El color de dibujo se utiliza en las primitivas grcas (las descritas en el apartado anterior y la correspondiente a al texto, que se introduce en el apartado siguiente). El color de fondo corresponde al fondo del componente. Tambin se denen primitivas para obtener los colores de dibujo y fondo e (getForeground() y getBackground(), respectivamente).
setForeground([Link]); [Link](0, 0, 99, 99); // se pinta en rojo
6.3. Grcos a [Link](25, 0, 50, 25); // se pinta en rojo setForeground(new Color(0, 0, 255)); // a partir de aqu se pintar en el color recin definido (azul) a e
151
6.3.4.
Texto
La primitiva bsica de Graphics para mostrar texto en la zona de interaccin del applet a o es drawString(texto, x, y) (similar a los otros mtodos de dibujo de guras). Pero, e adems, se pueden controlar aspectos tales como el tipo de letra que se utiliza para mostrar a el mensaje, o el tamao o atributos del mismo. n La clase Font describe tipos de letra. Se puede construir un tipo de letra a partir de la denominacin (por ejemplo, Serif, Helvetica, Courier), atributo (la clase Font dene o las constantes ITALIC, BOLD, PLAIN) y tamao (por ejemplo, 12, 14, 18). Todo componente n Java (por ejemplo, un applet) dene el mtodo setFont(tipoLetra), mediante el que se e puede asociar un tipo determinado de letra a dicho componente. La clase Graphics tambin e dene este mtodo para el dibujado de cadenas de caracteres. e
import [Link].*; import [Link].*; public class PruebaTexto extends JApplet { public void paint (Graphics g) { [Link]("Mensaje de prueba", 10, 10); Font f = new Font("Courier", [Link], 14); [Link](f); [Link]("Mensaje de prueba", 10, 50); } }
6.3.5.
Imgenes y sonido a
La clase Applet tiene mtodos que se encargan de cargar cheros de imagen (formatos gif, e jpg, png) y sonido (formatos mid, au) a partir de una url. En ambos casos, si el chero se encuentra en la misma ubicacin que el documento html, se puede utilizar el mtodo o e getDocumentBase(), que proporciona acceso a la url desde la que se ha descargado el documento. El resultado de getImage(url, fichero) es de tipo Image. Cada vez que se desea mostrar la imagen (por ejemplo, en paint()) se invoca uno de los mtodos drawImage() e de Graphics. Por su parte, el resultado de getAudioClip(url, fichero) es de tipo AudioClip, que ofrece los mtodos loop(), play() y stop(). e
import [Link].*; import [Link].*; import [Link].*; public class Multimedia extends JApplet { private Image imagen; private AudioClip audio; public void init () {
152
Cap tulo 6. Interfaz grca de usuario y applets a imagen = getImage(getDocumentBase(), "[Link]"); audio = getAudioClip(getDocumentBase(), "[Link]");
public void start () { [Link](); } public void stop () { [Link](); } public void paint (Graphics g) { [Link](imagen, 0, 0, this); } }
6.4.
La elaboracin de interfaces de usuario se va a ver tremendamente simplicada gracias a o la programacin orientada a objetos, ya que esta proporciona los elementos bsicos necesarios o a para construir interfaces y slo es necesario reutilizar dicho cdigo (ya sea mediante instano o ciacin o mediante herencia). De esta forma, la programacin orientada a objetos permite o o crear fcilmente interfaces grcas complejas. a a Swing proporciona distintos componentes de interfaz (botones, campos editables, etc.) que permiten la interaccin entre usuario y applet. Dichos componentes poseen una repreo sentacin grca, un conjunto de atributos y un conjunto de mtodos. Cada componente o a e puede responder a distintos eventos de usuario (por ejemplo, hacer click en un botn). Para o organizar dichos componentes se emplean los contenedores. Todo componente de interfaz debe incluirse en un contenedor o en el propio applet, que lo aadir a su contentPane n a (todo contenedor dispone del mtodo add() para aadir componentes). Finalmente, pueden e n emplearse gestores de ubicacin para controlar la posicin de los componentes dentro del o o contenedor.
6.4.1.
Componentes principales
Etiqueta (JLabel) Es una cadena de caracteres que se visualiza en pantalla. Puede justicarse a la derecha, izquierda (por defecto), o centrada.
JLabel etiqueta1 = new JLabel("Etiqueta justificada a la izquierda"); JLabel etiqueta2 = new JLabel("Etiqueta justificada a la derecha", [Link]);
Ya hemos visto anteriormente que la clase Graphics permite dibujar cadenas de texto. Las diferencias entre una etiqueta creada mediante drawString() y otra creada mediante un JLabel son las siguientes: la dibujada debe especicar las coordenadas dentro de la zona de visualizacin, mientras o que la localizacin de un JLabel se maneja automticamente con el gestor de ubicacin o a o asociado al contenedor al que se aade. n
153
la generada con drawString() debe redibujarse expl citamente en el mtodo paint(), e mientras que un JLabel se redibuja automticamente. a un JLabel dispone, adems, de mtodos diversos (por ejemplo, para asociar un icono a e a la etiqueta). Botn (JButton) Es un recuadro con etiqueta que responde a pulsaciones de ratn o de o o la tecla Intro si tiene el foco.
JButton boton = new JButton("Un botn"); o
Campo de edicin de texto (JTextField) Permite visualizar una cadena editable de o caracteres en una unica l nea. Durante la construccin se puede indicar la anchura del campo o y una cadena inicial. Si el contenido del campo es mayor que el rea visible se puede desplazar a el texto a derecha e izquierda.
// Un campo de texto vaco JTextField ct1 = new JTextField(); // Un campo de texto vaco de 20 columnas JTextField ct2 = new JTextField(20); // Un campo de texto con contenido especfico JTextField ct3 = new JTextField("Un campo de texto"); // Con contenido y nmero de columnas u JTextField ct4 = new JTextField("Un campo de texto", 20);
Como mtodos ms usados, se pueden destacar los que permiten obtener y modicar el e a texto contenido en el campo:
String leer = [Link](); [Link]("Contenido modificado");
Area de edicin de texto (JTextArea) Es un campo compuesto por varias l o neas (permite visualizar una cadena de caracteres unica que incluye caracteres de salto de l nea). Durante la construccin se jan las dimensiones iniciales (altura y anchura) del campo. Si o el texto que contiene ocupa ms que dicho tamao inicial, el rea aumenta su tamao. Si a n a n lo que queremos es que mantenga el tamao inicial y se cree una barra de desplazamiento n (scroll) de ser necesario, el componente deber mostrarse dentro de un JScrollPane. a
// Un rea de texto vaca a JTextArea at1 = new JTextArea(); // Vaca pero con tamao especificado n JTextArea at2 = new JTextArea(3, 10); // Con contenido especfico JTextArea at3 = new JTextArea("cadena de caracteres que\n" + "ocupa varias lneas"); // Con contenido y tamao especficos n JTextArea at4 = new JTextArea("cadena de caracteres que\n" + "ocupa varias lneas", 3, 10);
Igual que en el caso del JTextField, JTextArea dispone de los mtodos getText() y e setText(), con los que se obtiene y modica el contenido del rea de texto. a
154
Lienzo (JPanel) Aunque se trata de un contenedor genrico (como veremos en la sece cin 6.5.1), un JPanel tambin puede emplearse como base para crear lienzos en los que o e dibujar guras y texto. Para ello, se debe crear una clase que herede de JPanel y que sobreescriba el mtodo paintComponent() para que contenga el cdigo personalizado de e o pintado (ver seccin 6.9). De esta forma, y a diferencia del resto de componentes mencionao dos, el cdigo del JPanel se reutiliza en nuestras interfaces para crear nuevos lienzos de o dibujo mediante la herencia y sobreescritura (en vez de mediante la instanciacin directa, o como en ejemplos anteriores).
class ZonaDibujo extends JPanel { public ZonaDibujo () { // modificamos el tamao del panel n setPreferredSize(new Dimension(100, 100)); } public void paintComponent (Graphics g) { // heredamos el pintado por defecto del panel [Link](g); // realizamos nuestro propio pintado [Link](0, 0, 99, 99); // dibuja un borde alrededor del panel }
Eleccin (JComboBox) Son mens desplegables (pop-up) cuya etiqueta es la entrada aco u tualmente seleccionada en el men. Cuando se selecciona una entrada genera un evento que u indica la entrada seleccionada. Se pueden hacer editables, de forma que el usuario puede introducir un nuevo valor.
String[] colores = {"Rojo", "Verde", "Azul"}; JComboBox eleccion = new JComboBox(colores);
Dispone de los mtodos getSelectedIndex() y getSelectedItem() que permiten e obtener, respectivamente, el ndice actualmente seleccionado y el elemento correspondiente. Conmutador (JCheckBox y JRadioButton) Es una etiqueta con una pequea zona sensin ble a pulsaciones de ratn y que posee dos estados: cierto (marcado) y falso. Cada pulsacin o o conmuta el estado (por defecto est a falso). a
JCheckBox conmutador1 = new JCheckBox("Un conmutador"); JRadioButton conmutador2 = new JRadioButton("Otro conmutador");
Ambos componentes poseen el mtodo isSelected() que devuelve el estado del conmue tador: true si est marcado y false en otro caso. a Grupo de conmutadores (ButtonGroup) Permite controlar el comportamiento de un grupo de conmutadores, de forma que slo uno de ellos puede estar a cierto en cada instante o (al marcar uno se garantiza que los dems quedan a falso). a
ButtonGroup grupoConmutadores1 = new ButtonGroup(); JCheckBox conmutadorGrupo1 = new JCheckBox("Opcin 1"); o JCheckBox conmutadorGrupo2 = new JCheckBox("Opcin 2"); o // los conmutadores se deben aadir al ButtonGroup n
6.4. Componentes de interfaz de usuario [Link](conmutadorGrupo1); [Link](conmutadorGrupo2); ButtonGroup grupoConmutadores2 = new ButtonGroup(); JRadioButton conmutadorGrupo3 = new JRadioButton("Opcin 3"); o JRadioButton conmutadorGrupo4 = new JRadioButton("Opcin 4"); o [Link](conmutadorGrupo3); [Link](conmutadorGrupo4);
155
Lista (JList) Es una lista de opciones entre las cuales, dependiendo de un atributo, podemos seleccionar una (al seleccionar una se deselecciona la previamente seleccionada) o varias (se pueden mantener tantas entradas seleccionadas como se desee). El mtodo getSelectedValue() devuelve el e tem seleccionado y getSelectedIndex(), el ndice correspondiente a dicho tem (el primer tem ocupa la posicin 0). Cuando se pero mite seleccin mltiple se deben utilizar getSelectedValues() y getSelectedIndices() o u respectivamente (devuelven como respuesta un vector en lugar de un valor unico).
String[] ciudades = {"Murcia", "Castelln", "Valencia", o "Alicante", "Barcelona", "Tarragona"}; JList lista = new JList(ciudades); // slo se permite seleccionar un nico elemento o u [Link](ListSelectionModel.SINGLE_SELECTION);
Deslizador (JSlider) Es un control deslizante horizontal o vertical que puede tomar un valor entero entre dos l mites (m nimo y mximo) denidos en la construccin. a o
JSlider slider = new JSlider ([Link], 0, 10, 3);
Para obtener el valor actual del control, se dispone del mtodo getValue(). e Men (JMenuBar, JMenu, JMenuItem, JCheckBoxMenuItem, JRadioButtonMenuItem) El u lenguaje Java dispone de mecanismos para aadir mens a una aplicacin grca (JApplet, n u o a JDialog, JFrame. . . ). Una aplicacin posee un objeto JMenuBar (barra de men ), que cono u tiene objetos de tipo JMenu compuestos a su vez de objetos JMenuItem (pueden ser cadenas de caracteres, mens, conmutadores o separadores). u Para aadir mens a una aplicacin grca, se deben seguir los siguientes pasos: n u o a 1. Crear un objeto de tipo JMenuBar. 2. Crear los objetos de tipo JMenu necesarios. 3. Crear para cada JMenu las entradas necesarias (JMenuItem, JCheckBoxMenuItem, JRadioButtonMenuItem o separadores), y a adirlas al mismo. n 4. Crear nuevos JMenu para incorporarlos como submens dentro de otro JMenu, de ser u necesario. 5. Incorporar los objetos JMenu a la JMenuBar (en el orden en que deben aparecer de izquierda a derecha). 6. Incorporar la JMenuBar a la aplicacin grca (JApplet, por ejemplo) mediante el o a mtodo setJMenuBar. e
156
JMenuBar menuBar = new JMenuBar(); // paso 1 JMenu menu = new JMenu("Men"); u // paso 2 [Link](new JMenuItem("Accin")); // paso 3 o [Link](); [Link](new JCheckBoxMenuItem("Habilitar opcin 1")); o [Link](new JCheckBoxMenuItem("Habilitar opcin 2")); o JMenu submenu = new JMenu("Opciones..."); // paso 4 ButtonGroup grupo = new ButtonGroup(); JRadioButtonMenuItem radio1 = new JRadioButtonMenuItem("Opcin 1"); o JRadioButtonMenuItem radio2 = new JRadioButtonMenuItem("Opcin 2"); o [Link](radio1); [Link](radio2); [Link](radio1); [Link](radio2); [Link](submenu); // paso 4 [Link](menu); // paso 5 setJMenuBar(menuBar); // paso 6
El resultado visual del men del cdigo anterior es el mostrado en la gura 6.2. u o
6.4.2.
Un ejemplo completo
El siguiente cdigo declara todos los componentes estudiados en un mismo JApplet, cuya o representacin visual se muestra en la gura 6.3. o
import [Link].*; import [Link].*; class MiPanelDeDibujo extends JPanel { public MiPanelDeDibujo () { setPreferredSize(new Dimension(100, 100)); setBackground([Link]); } public void paintComponent (Graphics g) { [Link](g); [Link](0, 0, 99, 99); [Link](20, 20, 20, 20); [Link](40, 40, 15, 25); [Link](30, 30, 5, 5); [Link](25, 0, 50, 25); [Link](25, 75, 50, 25); [Link](57, 37, 30, 20, 45, 90);
6.4. Componentes de interfaz de usuario [Link](57, 42, 30, 20, 45, 90); int[] x = {25, 40, 10}, y = {60, 70, 90}; Polygon p = new Polygon(x, y, 3); [Link](p);
157
public class EjemploCompleto extends JApplet { public void init () { // cambiamos el gestor de ubicacin por defecto o setLayout(new FlowLayout()); // creamos los componentes y los aadimos al applet n JLabel etiqueta = new JLabel("Una etiqueta", [Link]); add(etiqueta); JButton boton = new JButton("Un botn"); o add(boton); JTextField campoTexto = new JTextField("Un campo de texto", 20); add(campoTexto); JTextArea areaTexto = new JTextArea("cadena de caracteres que\n" + "ocupa varias lneas", 3, 10); add(areaTexto); String[] colores = {"Rojo", "Verde", "Azul"}; JComboBox eleccion = new JComboBox(colores); add(eleccion); JCheckBox conmutador1 = new JCheckBox("Un conmutador"); add(conmutador1); JRadioButton conmutador2 = new JRadioButton("Otro conmutador"); add(conmutador2); ButtonGroup grupoConmutadores1 = new ButtonGroup(); JCheckBox conmutadorGrupo1 = new JCheckBox("Opcin 1"); o JCheckBox conmutadorGrupo2 = new JCheckBox("Opcin 2"); o [Link](conmutadorGrupo1); [Link](conmutadorGrupo2); add(conmutadorGrupo1); add(conmutadorGrupo2); ButtonGroup grupoConmutadores2 = new ButtonGroup(); JRadioButton conmutadorGrupo3 = new JRadioButton("Opcin 3"); o JRadioButton conmutadorGrupo4 = new JRadioButton("Opcin 4"); o [Link](conmutadorGrupo3); [Link](conmutadorGrupo4); add(conmutadorGrupo3); add(conmutadorGrupo4); String[] ciudades = {"Murcia", "Castelln", "Valencia", o
158
Cap tulo 6. Interfaz grca de usuario y applets a "Alicante", "Barcelona", "Tarragona"}; JList lista = new JList(ciudades); [Link](ListSelectionModel.SINGLE_SELECTION); add(lista); JSlider slider = new JSlider ([Link], 0, 10, 3); add(slider); // creamos el men y lo aadimos al applet u n JMenuBar menuBar = new JMenuBar(); JMenu menu = new JMenu("Men"); u [Link](new JMenuItem("Accin")); o [Link](menu); setJMenuBar(menuBar); // aadimos el lienzo de dibujo n add(new MiPanelDeDibujo());
} }
6.5.
Los componentes de interfaz no se encuentran aislados, sino agrupados dentro de contenedores. Los contenedores agrupan varios componentes y se encargan de distribuirlos (ubicarlos) de la forma ms adecuada (mantienen la funcionalidad de adicin, sustraccin, recua o o peracin, control y organizacin de los componentes). Los contenedores son a su vez compoo o nentes, lo que permite situar contenedores dentro de otros contenedores. Tambin disponen e del cdigo necesario para controlar eventos, cambiar la forma del cursor, modicar el tipo o de fuente, etc. Todos los contenedores son instancias de la clase Container o una de sus extensiones.
159
6.5.1.
Contenedores
En este libro nos centraremos en un unico tipo de contenedor: el panel. De esta forma, todos los componentes los aadiremos a un panel existente. Hay que recordar que todo n JApplet incorpora por defecto un contentPane como panel principal, de tal forma que cuando invocamos el mtodo add() sobre un JApplet para incorporar un componente, en e realidad se est aadiendo dicho componente al contentPane por defecto. De la misma a n forma, y como veremos ms adelante, cuando invocamos setLayout() sobre un JApplet, lo a que estamos cambiando es el gestor de ubicacin de su contentPane. Este comportamiento o permite tratar a un JApplet como si se tratara de un panel a estos efectos. A continuacin se describen los contenedores proporcionados por Swing. o Panel (JPanel) Es un contenedor genrico de componentes. Normalmente no tiene repe resentacin visual alguna (nicamente puede ser visualizado cambiando su color de fono u do). Suele utilizarse para distribuir los componentes de interfaz de una forma adecuada, asignndole un gestor de ubicacin determinado; o como lienzo, para realizar dibujos media o ante la sobreescritura de su mtodo paintComponent(). e Ventana (JWindow) Representa el concepto t pico de ventana (zona de pantalla desplazable y redimensionable, con contenido espec co, y que no puede incluirse en otro contenedor). Por defecto no posee t tulo ni borde ni barra de men, pero dispone de mtodos u e espec cos para alterar la posicin, el tamao, forma del cursor, etc. Normalmente no se o n crean objetos de tipo JWindow, sino de tipo JFrame o JDialog. Marco (JFrame) Un JFrame es lo que visualmente conocemos como ventana. Posee borde y t tulo y puede incluir una barra de men. Los objetos de tipo JFrame son capaces de u generar varios tipos de eventos (por ejemplo, WindowClosing cuando el usuario pincha con el ratn sobre el icono cerrar en la barra de t o tulo del JFrame). Dilogo (JDialog) Es una ventana con borde y t a tulo que permite recoger entradas del usuario. El dilogo se lanza desde otra ventana, y puede ser modal (todas las entradas del a usuario las recoge este dilogo, por lo que no se puede proseguir con la aplicacin hasta a o concluir el dilogo) o no modal (se puede mantener el dilogo abierto e interactuar con a a dilogo y aplicacin simultneamente), segn se indique en el constructor (tambin puede a o a u e conmutarse a modal una vez creado). Se puede mover (libremente, sin restringirlo a las dimensiones del padre) y redimensionar, pero no minimizar o maximizar.
6.5.2.
Gestores de ubicacin o
En lugar de especicar las coordenadas de pantalla en las que ubicar los distintos componentes de interfaz, se utilizan gestores de ubicacin que distribuyen (ubican) los componentes o de un contenedor segn una pol u tica determinada. As cada contenedor tiene asociado un , gestor de ubicacin que se utiliza para procesar las llamadas a [Link](). El o mtodo [Link](migestor) permite asociar un gestor de ubicacin a e o un contenedor. Existen distintos gestores de ubicacin, pensados para necesidades diferentes o (por ejemplo, para ubicar las teclas de una calculadora se utiliza uno que ubique los elementos segn una rejilla). u Todo componente posee un tamao m n nimo (por ejemplo, en un botn el necesario para o mostrar la etiqueta) y un tamao preferido (cuando se extiende un componente se puede n sobreescribir el mtodo getPreferredSize(), indicando de este modo el tamao ideal para e n
160
ese componente). Los contenedores tambin pueden indicar el espacio alrededor de su bore de interior que debe quedar libre (no se situar ningn componente) mediante el mtodo a u e getInsets(). El gestor de ubicacin parte de estas indicaciones para cada componente del o conjunto de componentes a gestionar y, segn la pol u tica establecida (por ejemplo, situar en una rejilla), intenta obtener el mejor resultado visual posible. Existe un conjunto de gestores de ubicacin ya denidos y, adems, se pueden crear otros o a propios. Los gestores de ubicacin ya denidos ms comunes se describen a continuacin. o a o Flujo (FlowLayout) Ubica los componentes de izquierda a derecha hasta agotar la l nea, y entonces pasa a la l nea siguiente. En cada l nea, los componentes estn centrados. Es el que a se utiliza por defecto en JPanel. El siguiente cdigo mostrar el aspecto visual capturado o a en la gura 6.41 .
import [Link].*; import [Link].*; public class PruebaFlow extends JApplet { public void init () { setLayout(new FlowLayout()); add(new JButton("Aceptar")); add(new JButton("Cancelar")); add(new JButton("Enviar")); add(new JButton("Borrar")); add(new JButton("Anterior")); add(new JButton("Siguiente")); } }
Bordes (BorderLayout) Es el empleado por defecto en JApplet. Posee cinco zonas: norte, sur, este, oeste y centro. Cuando se aade un componente se indica su ubicacin (por ejemplo, n o norte). No es necesario cubrir todas las zonas. Una caracter stica importante de este gestor de ubicacin es que redimensiona cada componente para que ocupe todo el espacio de la zona o en la que se inserta. De esta forma, slo se visualiza un componente por zona (el ultimo, o si se aade ms de uno en la misma zona). Para ubicar varios componentes en la misma n a zona, se debe aadir un contenedor como unico componente de la zona y colocar en l los n e componentes deseados. El cdigo siguiente ofrece el resultado visual de la gura 6.5. o
1 Otro
6.5. Contenedores y gestores de ubicacin o import [Link].*; import [Link].*; public class PruebaBorder extends JApplet { public void init () { // no sera necesario, ya que es el gestor por defecto setLayout(new BorderLayout()); add(new JLabel("Una cabecera"), [Link]); add(new JTextField("Abajo, un mensaje"), [Link]); add(new JButton("SALIR"), [Link]); add(new JButton("AYUDA"), [Link]); add(new JTextArea("Ejemplo de texto en\n" + "la parte central"), [Link]); } }
161
Rejilla (GridLayout) Distribuye los componentes en las y columnas, proporcionando un aspecto de rejilla, que se rellena por las. Cuando se proporcionan valores distintos de cero tanto para el nmero de las como para el de columnas (ya sea en el constructor o en los u mtodos setRows() y setColumns()), el nmero de columnas especicado se ignora. En e u su lugar, se emplean tantas columnas como sea necesario segn el nmero de las y de u u componentes a ubicar (por ejemplo, si se especican tres las y dos columnas pero hay nueve componentes a ubicar, estos se muestran en una cuadr cula de tres las y tres columnas). El nmero de columnas especicado slo se tiene en cuenta cuando el nmero de las se pone u o u a cero. Un cdigo como el siguiente muestra un aspecto visual como el de la gura 6.6. o
import [Link].*; import [Link].*; public class PruebaGrid extends JApplet { public void init () { setLayout(new GridLayout(3, 2)); // rejilla de 3 filas y 2 columnas for (int i = 1; i < 7; i++) add(new JButton("" + i)); } }
162
Ubicaciones complejas: contenedores anidados o compuestos Como los contenedores tambin son componentes, pueden anidarse de forma arbitraria para mejorar el control. e As podemos dividir el espacio visible en distintos paneles y aplicar un gestor de ubicacin , o distinto para cada uno. De esta forma, se puede dividir la interfaz en zonas distintas, ubicando los componentes de cada una de ellas segn diferentes pol u ticas, as como respondiendo de forma distinta a los eventos de cada zona. Por ejemplo, si modelamos un telfono mvil como e o un teclado y una pantalla, para el teclado interesar utilizar una rejilla pero la ubicacin de a o los s mbolos en pantalla requerir otra estrategia. a
import [Link].*; import [Link].*; public class MultiPanel extends JApplet { private String[] teclas = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "*", "0", "#"}; public void init () { // 2 secciones: pantalla y teclado setLayout(new GridLayout(2, 0)); // creamos la pantalla con bordes JPanel pantalla = new JPanel(); [Link](new BorderLayout()); [Link](new JLabel("movifone", [Link]), [Link]); [Link](new JLabel("Men", [Link]), u [Link]); // creamos el teclado con una rejilla JPanel teclado = new JPanel(); [Link](new GridLayout(5, 0)); [Link](new JButton("")); [Link](new JButton("OK")); [Link](new JButton("")); for (int i = 0; i < [Link]; i++) { JButton b = new JButton(teclas[i]); [Link](b); } // aadimos ambos paneles al applet n
163
} }
Figura 6.7: Gestores de ubicacin compuestos o Finalmente, Java proporciona otros gestores de ubicacin ms especializados, como o a
este libro.
6.6.
La arquitectura Fuente-Oyente (gura 6.8) dene un esquema en el que un elemento denominado fuente genera un evento en un determinado instante de tiempo. Dicho evento puede ser tratado o manejado por otro elemento, denominado oyente. Para que el evento viaje de la fuente al oyente, el oyente debe haberse registrado como tal previamente en la fuente. De esta forma, la responsabilidad de la fuente se limita a generar los eventos y enviarlos a los oyentes que tenga registrados en cada momento2 . Dicho env se realiza en forma de o invocacin de un mtodo concreto en cada oyente. Para asegurar que el oyente dispone de o e dicho mtodo, se requiere la implementacin de una cierta interfaz en el momento en el que e o el oyente se registra como tal. Siguiendo esta arquitectura, los componentes de interaccin de un applet se comportan o como fuentes de eventos, generando eventos en respuesta a diversas acciones del usuario. Por
2 En caso de que no haya ning n oyente registrado, no se llevar a cabo ninguna accin: el evento se u a o perder. a
164
ejemplo, un botn genera un evento cada vez que el usuario lo pulsa. En este caso, el evento o generado es la pulsacin y la fuente del evento es el botn (la fuente no es el ratn ni el o o o usuario, sino el componente de interaccin). Adicionalmente, diversos elementos se pueden o registrar como oyentes en las fuentes que sean de su inters, con el n de ser invocados e cada vez que se genera un evento, para realizar las acciones pertinentes. Este esquema de programacin se conoce con el nombre de programacin dirigida por eventos. o o Siguiendo un esquema parecido, y como ya vimos al principio del tema, el navegador puede generar eventos de repintado cuando detecta que es necesario actualizar (repintar) la zona de interaccin del applet (por ejemplo, cuando dicha zona vuelve a quedar visible despus o e de haber permanecido parcialmente cubierta por otra ventana, o se recarga el documento html, o se ha minimizado y vuelto a maximizar la ventana del navegador, etc.). La gestin o de dicha zona es responsabilidad exclusiva del applet, con lo que el applet se convierte en oyente de los eventos de repintado. La diferencia con la arquitectura general es que, para recibir eventos de repintado, el applet no necesita registrarse como oyente espec camente en ninguna fuente.
6.6.1.
Eventos
Existen muchos tipos de eventos distintos. Clases de eventos diferentes se representan mediante distintas clases Java, todas ellas subclase de [Link]. En este texto analizamos los eventos generados por el usuario (ratn y teclado, y accin soo o bre los distintos componentes de interaccin). Todos los eventos AWT derivan de la clase o [Link] (subclase de [Link]), y se incluyen en el paquete [Link]. Cada evento es generado por un objeto fuente (puede obtenerse mediante getSource()) y posee un tipo (puede obtenerse con getID()). El tipo se utiliza para distinguir los distintos tipos de eventos representados por la misma clase de evento (por ejemplo, la clase MouseEvent dene, entre otros, MOUSE_CLICKED y MOUSE_DRAGGED). Cada clase de evento posee el conjunto de mtodos adecuado para los eventos que representa (por ejemplo, sobre e los objetos de tipo MouseEvent se pueden invocar mtodos tales como getX(), getY() y e getClickCount()).
6.6.2.
Un generador o fuente de eventos mantiene una lista de oyentes interesados en recibir una noticacin cuando se produzca el evento. Adems, la fuente de eventos proporciona mtodos o a e que permiten a un oyente incorporarse a la lista de oyentes, o abandonarla. As si una fuente , de eventos genera eventos de tipo X, posee un mtodo denominado addXListener() (el e oyente lo invoca para incorporarse a la lista de oyentes) y otro denominado removeXListener() (para eliminarse de esa lista). Cuando el objeto fuente de eventos genera un evento (o cuando se produce un evento de usuario sobre ese objeto fuente), notica tal evento a todos los objetos oyentes. Para ello, invoca un mtodo espec e co sobre cada oyente, pasando como parmetro el objeto de tipo a evento. La tabla 6.3 muestra los principales eventos generados por los distintos componentes de interaccin. o Segn el tipo de evento, el mtodo invocado en el oyente ser distinto. Para que este u e a esquema funcione, todos los oyentes deben implementar el mtodo adecuado. Este punto se e garantiza obligando a que todos los oyentes que deseen ser noticados ante un evento concreto implementen una determinada interfaz (por ejemplo, los objetos que desean noticacin de o ActionEvent deben implementar la interfaz ActionListener. El paquete [Link]
165
Componente
JButton JCheckBox JCheckBoxMenuItem JComboBox JComponent
Eventos
ActionEvent ItemEvent ItemEvent ItemEvent ComponentEvent FocusEvent KeyEvent MouseEvent
Accin o Botn pulsado. o Item seleccionado/deseleccionado. Item seleccionado/deseleccionado. Item seleccionado/deseleccionado. Componente desplazado, redimensionado, ocultado o mostrado. El componente ha ganado/perdido el foco. Pulsacin/liberacin de tecla. o o Pulsacin/liberacin de botn, eno o o trada/salida de componente, desplazamiento/arrastre. Incorporacin/eliminacin de un o o componente al contenedor. Item seleccionado/deseleccionado. Item seleccionado. Item seleccionado. Item seleccionado. Desplazamiento del deslizador. El texto ha terminado de ser editado. Abrir, cerrar, minimizar, maximizar la ventana.
166
dene una interfaz de oyente para cada tipo de evento que dene (excepto para los eventos de ratn, sobre los que dene dos oyentes, MouseListener y MouseMotionListener). o Cada interfaz de oyente puede denir varios mtodos (por ejemplo, MouseEvent repree senta eventos tales como pulsar el boton del ratn, liberarlo, desplazar el ratn, etc., y cada o o uno de estos eventos supone invocar un mtodo distinto). Todos estos mtodos reciben como e e unico parmetro el objeto evento, por lo que ste debe contener toda la informacin relativa a e o al evento que el programa pueda necesitar. La tabla 6.4 recoge, para las principales clases de eventos, las interfaces que los oyentes deben implementar y los mtodos de cada interfaz. e
Clase de Evento
ActionEvent ChangeEvent ComponentEvent
Interfaz de Oyente
ActionListener ChangeListener ComponentListener
Mtodos e
actionPerformed stateChanged componentHidden componentMoved componentResized componentShown componentAdded componentRemoved focusGained focusLost itemStateChanged keyPressed keyReleased keyTyped valueChanged mouseClicked mouseEntered mouseExited mousePressed mouseReleased mouseDragged mouseMoved windowActivated windowClosed windowClosing windowDeactivated windowDeiconified windowIconified windowOpened
ListSelectionEvent MouseEvent
ListSelectionListener MouseListener
Una vez creada la clase que implementa la correspondiente interfaz, se debe crear una instancia (objeto oyente) y registrarla en la fuente de eventos (siempre ser algn componente a u de interaccin), mediante el correspondiente mtodo addXListener(). o e
167
6.6.3.
Escuchando eventos
Cmo escuchar eventos o Para que un elemento est en disposicin de escuchar eventos, debe implementar la ine o terfaz oyente correspondiente bien directamente o bien a travs de la herencia a partir de e una clase adaptadora. Mediante la implementacin de la interfaz oyente Cualquier clase que desee actuar o como oyente del evento XEvent3 debe implementar la interfaz XListener. Si dicha interfaz dene unicamente un mtodo, la tarea es simple. Si, por el contrario, dene varios mtodos, e e de los cuales slo interesa responder a uno o dos, seguimos en la obligacin de indicar el o o cdigo de todos los mtodos, aunque el cdigo sea no hacer nada. Supongamos, por ejemplo, o e o una clase que desea responder al evento MOUSE_CLICKED (pulsacin de ratn): o o
class OyenteClickRaton implements MouseListener { public void mouseClicked (MouseEvent e) { responderEvento(e); } // para los restantes eventos no se hace nada public void mouseEntered (MouseEvent e) { } public void mouseExited (MouseEvent e) { } public void mousePressed (MouseEvent e) { } public void mouseReleased(MouseEvent e) { } }
Mediante adaptadores Resulta bastante tedioso incorporar todo el conjunto de mtodos e que no hacen nada. Para simplicar la tarea, [Link] proporciona para cada clase de evento que posee ms de un mtodo una clase adaptador, que implementa la correspondiente a e interfaz simplemente con cdigo vac en los distintos mtodos (para la clase XEvent se o o e dispone de la interfaz XListener y el adaptador XAdapter). De esta forma, la clase oyente simplemente hereda del adaptador y sobreescribe los mtodos correspondientes a los eventos e que quiere tratar (para los restantes eventos ya se hereda del adaptador el comportamiento deseado de no hacer nada). El ejemplo anterior puede escribirse como:
class OyenteClickRaton extends MouseAdapter { public void mouseClicked (MouseEvent e) { responderEvento(e); } }
Dnde escuchar eventos o Existen diversos modos de implementar la escucha de eventos: a) que el propio applet acte como oyente; b) implementar el oyente en una clase independiente; c) implementar el u oyente en una clase anidada; y d) mediante clases annimas. o Implementacin en el propio JApplet El siguiente ejemplo ilustra la estructura de un o programa dirigido por eventos en el que el propio applet acta como oyente de eventos de u ratn, pero slo est interesado en los eventos de presionar el ratn y de arrastrarlo: o o a o
3 Donde
X debe sustituirse por alguno de los diversos tipos de eventos: Action, Mouse, Text. . .
public class Dibujo extends JApplet implements MouseListener, MouseMotionListener { private int x0, y0; // ltima coordenada u public void init () { // el propio applet (this) es el oyente addMouseListener(this); addMouseMotionListener(this); } // mtodo de la interfaz MouseListener e public void mousePressed (MouseEvent e) { x0 = [Link](); y0 = [Link](); } // mtodo de la interfaz MouseMotionListener e public void mouseDragged (MouseEvent e) { int x = [Link](), y = [Link](); // recta desde punto anterior getGraphics().drawLine(x0, y0, x, y); x0 = x; y0 = y; } // los public public public public otros mtodos de la interfaz MouseListener e void mouseReleased (MouseEvent e) { } void mouseClicked (MouseEvent e) { } void mouseEntered (MouseEvent e) { } void mouseExited (MouseEvent e) { }
El ejemplo anterior no puede explotar el uso de MouseListenerAdapter y MouseAdapter porque la clase principal ya extiende JApplet (Java no permite herencia mltiple). Adems, u a resulta que el mismo JApplet se est comportando como fuente de eventos (ya que es un a componente) y oyente (escucha eventos de ratn) a la vez, lo cual puede resultar confuso. Lo o ideal es disponer de clases separadas para actuar como fuente y oyente. Implementacin mediante clases independientes Otra forma de realizar la escucha o de eventos, ms aconsejable que la anterior, es utilizando clases independientes (externas a al applet) que extiendan el correspondiente adaptador o implementen la correspondiente interfaz. Hay que tener en cuenta que, al ser cdigo externo al applet, necesitaremos proporo cionarles una referencia al mismo para que puedan acceder a sus mtodos de ser necesario. e Dicha referencia al applet suele pasarse como parmetro al constructor de la clase oyente. a
6.6. Programacin dirigida por eventos o import [Link].*; import [Link].*; class OyenteRaton extends MouseAdapter { private Dibujo elApplet; public OyenteRaton (Dibujo elApplet) { [Link] = elApplet; } public void mousePressed (MouseEvent e) { elApplet.setX0([Link]()); elApplet.setY0([Link]()); } } class OyenteMovRaton extends MouseMotionAdapter { private Dibujo elApplet; public OyenteMovRaton (Dibujo elApplet) { [Link] = elApplet; } public void mouseDragged (MouseEvent e) { int x = [Link](), y = [Link](); // recta desde punto anterior [Link]().drawLine(elApplet.getX0(), elApplet.getY0(), x, y); elApplet.setX0(x); elApplet.setY0(y); } } public class Dibujo extends JApplet { private int x0, y0; // ltima coordenada u public void init () { addMouseListener(new OyenteRaton(this)); addMouseMotionListener(new OyenteMovRaton(this)); } public int getX0 () { return x0; } public int getY0 () { return y0; }
169
170
Implementacin mediante clases anidadas En el apartado anterior, se han tenido que o implementar mtodos adicionales (getX0(), getY0(), setX0(), setY0())4 para acceder de e forma controlada a los atributos desde las clases oyente externas. Esto puede resultar incmoo do si dichos mtodos no se necesitaban previamente. Adems, el constructor de las clases e a externas ha debido personalizarse para aceptar un parmetro de tipo igual a la clase que a extiende a JApplet (la referencia al applet, necesaria para invocar los mtodos del mismo). e Esto limita la reusabilidad del cdigo. o Java dispone del concepto de clase anidada (clase denida dentro de otra clase), que suele explotarse precisamente en el contexto de la gestin de eventos. o El siguiente cdigo equivale al anterior pero, en este caso, para actuar de oyente se crean o dos nuevas clases dentro de la clase Dibujo (una como extensin de MouseAdapter y otra o como extensin de MouseMotionAdapter). En este caso, al tratarse de cdigo interno al o o applet, todos los atributos y mtodos del mismo estn disponibles desde las clases anidadas e a sin necesidad de emplear referencias ni implementar nuevos mtodos de acceso controlado a e los atributos del applet.
import [Link].*; import [Link].*; public class Dibujo extends JApplet { class OyenteRaton extends MouseAdapter { public void mousePressed (MouseEvent e) { x0 = [Link](); y0 = [Link](); } } class OyenteMovRaton extends MouseMotionAdapter { public void mouseDragged (MouseEvent e) { int x = [Link](), y = [Link](); // recta desde punto anterior getGraphics().drawLine(x0, y0, x, y); x0 = x; y0 = y; } } private int x0, y0; // ltima coordenada u
4 Los mtodos cuya unica nalidad es proporcionar acceso de lectura y escritura a los atributos de una e clase reciben el nombre de getters y setters, respectivamente.
171
Implementacin mediante clases annimas De forma equivalente a la anterior, se o o pueden declarar las clases oyente como clases annimas en lugar de como clases internas. o Esto simplica el cdigo si dichas clases annimas tienen pocas l o o neas, como suele ser el caso de los oyentes. Una clase annima es una clase que se dene (se proporciona el cuerpo de la o clase) y se instancia (se crea un objeto de la clase) en la misma sentencia de cdigo. Toda o clase annima se dene como hija de otra clase, a la que sobreescribe los mtodos necesarios; o e o como implementacin de una interfaz, de la que proporciona el cdigo de todos sus mtodos. o o e La sintaxis consiste en el operador new seguido del nombre de la clase padre o de la interfaz implementada5 seguido de unos parntesis. Si la clase annima hereda de una superclase, e o entre los parntesis se proporcionarn los parmetros que se quieran pasar al constructor de e a a dicha superclase (en el caso de los adaptadores, los constructores no reciben ningn parmetro u a por lo que simplemente se pondrn dos parntesis). Inmediatamente despus, entre llaves, se a e e dene el cuerpo de la clase annima igual que se har para cualquier otra clase. Las clases o a annimas tienen varias peculiaridades adicionales, pero no es necesario conocerlas para su o empleo como clases oyente.
import [Link].*; import [Link].*; public class Dibujo extends JApplet { private int x0, y0; // ltima coordenada u public void init () { addMouseListener(new MouseAdapter() { public void mousePressed (MouseEvent e) { x0 = [Link](); y0 = [Link](); } }); addMouseMotionListener(new MouseMotionAdapter() { public void mouseDragged (MouseEvent e) { int x = [Link](), y = [Link](); // recta desde punto anterior getGraphics().drawLine(x0, y0, x, y); x0 = x; y0 = y; } });
} }
5 Al proporcionar slo el nombre de la clase padre o de la interfaz implementada, no se asigna un nombre o propio a estas clases, razn por la que reciben el nombre de annimas. o o
172
6.7.
Adems de los campos obligatorios (CODE, WIDTH, HEIGHT), se puede aadir el atributo a n CODEBASE para indicar la url donde est el cdigo (slo es necesario si no coincide con la a o o
<APPLET CODEBASE="una URL" CODE="[Link]" HEIGHT=100 WIDTH=50></APPLET>
En ocasiones se desea pasar parmetros al invocar el applet. La solucin es utilizar la a o etiqueta <PARAM> como sigue:
<APPLET CODE="[Link]" HEIGHT="100" WIDTH="50"> <PARAM NAME="valor" VALUE="230"> <PARAM NAME="nombre" VALUE="Juan Perez"> <PARAM NAME="dirCorreo" VALUE="jperez@[Link]"> </APPLET>
Cada parmetro requiere su propia etiqueta <PARAM>, donde se indica el nombre del a parmetro y su valor (en ambos casos se trata de cadenas de caracteres). Para acceder a los a parmetros desde el applet se invoca getParameter(nombreParametro), que devuelve una a cadena de caracteres que corresponde al valor del parmetro indicado. a
import [Link].*; public class Ejemplo extends JApplet { private String nombre, dirCorreo; private int valor; public void init () { nombre = getParameter("nombre"); dirCorreo = getParameter("dirCorreo"); valor = [Link](getParameter("valor")); }
Los aspectos a destacar de este esquema son: Se accede a los parmetros por nombre, no por posicin (la posicin relativa de los a o o parmetros en el chero html no es relevante). a Si se intenta acceder a un parmetro que no existe el resultado es null. Si existe, el a resultado es la cadena de caracteres que corresponde a su valor (en su caso el programa deber interpretarla posteriormente). a El siguiente programa ilustra estos aspectos. Fichero html:
<APPLET CODE="[Link]" WIDTH="500" HEIGHT="450"> <PARAM NAME="a1" VALUE="Leonardo Gimeno|leo@[Link]"> <PARAM NAME="a2" VALUE="Ramon Martos|ram@[Link]"> <PARAM NAME="a3" VALUE="Luis Perez|lp@[Link]"> </APPLET>
173
Fichero Java:
import [Link].*; import [Link].*; import [Link]; public class EjemploParam extends JApplet { private static final int max = 30; public void paint (Graphics g) { for (int i = 1; i <= max; i++) { String s = getParameter("a" + i); if (s != null) { StringTokenizer st = new StringTokenizer(s, "|"); [Link]("nombre = " + [Link]() + "; direccin = " + [Link](), 10, 20*i); o } } } }
6.8.
Desde el punto de vista de la seguridad, la ejecucin en una mquina cliente de un o a programa cargado desde un servidor remoto es un riesgo. Los applets de Java tratan de minimizar estos riesgos: Java es un lenguaje seguro, con comprobaciones intensivas en ejecucin, sin aritmtica o e de punteros, etc. Se establece un contexto de ejecucin r o gido (la sandbox ). La mquina virtual Java a a a los applets y evita interferencias que pudieran comprometer la integridad del sla sistema: Un applet no puede denir mtodos nativos, cargar bibliotecas ni arrancar proe gramas en la mquina cliente. a Un applet no puede leer/escribir cheros sobre la mquina local, ni acceder a a determinadas propiedades del sistema (por ejemplo, el nombre de usuario o la ruta de su directorio home). Un applet slo puede abrir conexiones de red con el servidor desde el que se ha o descargado dicho applet. Las ventanas abiertas por el applet poseen un aspecto distinto a las abiertas por un aplicacin. o En algunos casos estas restricciones pueden parecer excesivas. Cada navegador puede denir distintos niveles de restricciones, de forma que el usuario puede relajar las restricciones si lo desea. Sin embargo, resulta ms util asociar una rma digital al chero, de forma que a si se carga un applet rmado por un servidor que ha sido etiquetado como able, se relajan las restricciones de ejecucin. o
174
Pero no todo son restricciones, ya que los applets disfrutan de un conjunto de posibilidades vedadas a una aplicacin Java autnoma, como abrir conexiones de red con la mquina o o a remota, visualizar documentos html, o invocar mtodos pblicos de otros applets en la e u misma mquina. a
6.9.
Se ha dicho anteriormente que el navegador invoca el mtodo paint() cuando detecta e que la zona de interaccin del applet debe repintarse. Esto es una simplicacin, puesto que, o o en realidad, el navegador no invoca dicho mtodo directamente, sino que lanza una peticin e o de redibujado que lleva nalmente a tal invocacin. o De esta forma, el navegador avisa al applet siempre que sea necesario repintar la pantalla, siendo el applet responsable de ejecutar el cdigo adecuado. o El propio applet tambin puede iniciar una peticin de repintado mediante una llamada e o a repaint() (no se debe invocar a paint() directamente), cuando necesite redibujar la interfaz para adecuarla a cambios en el estado. Ya sea mediante un aviso del navegador, o por iniciativa del propio cdigo del applet, o nalmente se invoca el mtodo paint(Graphics g) del applet, cuyo parmetro es el contexto e a grco asociado al rea de visualizacin. a a o El mtodo paint() merece atencin especial debido a las diferentes implementaciones e o que han dado AWT y Swing. Sin embargo, no es nuestro objetivo conocer en profundidad las peculiaridades de este mtodo en ambas bibliotecas. Por ello, a continuacin se ofrecen e o unas directrices que aseguran el correcto funcionamiento de las interfaces grcas: a si se quieren dibujar guras, estas deber colocarse en componentes o paneles dedan icados a ello, es decir, que no incluyan ms componentes. Adicionalmente, el mtodo a e donde colocar el cdigo de pintado personalizado es distinto si dicho panel o compoo nente pertenece a una u otra biblioteca. As tenemos que: , en AWT, se debe sobreescribir el mtodo paint() incluyendo como primera l e nea una llamada a [Link](). en Swing, se deber sobreescribir el mtodo paintComponent() incluyendo como a e primera l nea una llamada a [Link](). en ningn caso deber sobreescribirse el mtodo paint() de un applet o de otro u a e contenedor principal (ventana, marco o dilogo ver seccin 6.5.1). a o Estas directrices, aunque quizs ms laboriosas que simplemente colocar el cdigo de a a o pintado en el mtodo paint() del applet (como se hace por simplicidad en algunos ejemplos e del tema), son ms seguras puesto que evitan ciertos comportamientos no deseados que a suceden a veces al redibujar las interfaces.
6.10.
Cuestiones
Cuestin 1 En qu se diferencian una aplicacin Java autnoma y un applet? o e o o Cuestin 2 Cules son los mtodos que constituyen el ciclo de vida de un applet? o a e Cuestin 3 Qu utilidad tienen los contenedores y los gestores de ubicacin? o e o Cuestin 4 Qu condiciones debe cumplir un elemento para poder escuchar un evento y o e realizar una accin en respuesta a dicho evento? o
175
Cuestin 5 Qu ocurre si, al dispararse un evento, no hay ningn oyente registrado en la o e u fuente? Cuestin 6 Qu ventajas e inconvenientes presenta la implementacin de oyentes en clases o e o externas independientes frente a la implementacin en el propio applet? o
6.11.
Ejercicios resueltos
Ejercicio 1 Crear un applet que convierta valores de temperatura expresados en grados Celsius a sus valores equivalentes en grados Farenheit. Implementar los oyentes necesarios mediante clases externas independientes. Todos los atributos del applet deben ser declarados como privados. o Solucin: La interfaz requerida para la escucha de eventos de botn, ActionListener, o slo tiene un mtodo. Debido a esto, no existe un adaptador para ella. De esta forma, la o e escucha de eventos se debe hacer necesariamente implementando la interfaz. Por otra parte, como los atributos del applet deben ser privados, se deben implementar mtodos getters y setters para permitir el acceso desde la clase oyente. e
import [Link].*; import [Link].*; import [Link].*; public class ConversorTemperatura extends JApplet { private JButton bConvertir; private JTextField tfCen, tfFar; public void init() { setLayout(new FlowLayout()); // Crear componentes tfCen = new JTextField(5); tfFar = new JTextField(5); bConvertir = new JButton("Convertir"); // Aadir componentes n add(new JLabel("C")); add(tfCen); add(new JLabel("F")); add(tfFar); add(bConvertir); // Crear oyente y registrar con fuente (boton Convertir) MiOyente oy = new MiOyente(this); [Link](oy);
class MiOyente implements ActionListener { private ConversorTemperatura elApplet; public MiOyente (ConversorTemperatura ct) { elApplet = ct; } public void actionPerformed (ActionEvent e) { try { float tempCel = [Link]([Link]().getText()); float tempFar = tempCel*9/5 + 32; [Link]().setText("" + tempFar); } catch(NumberFormatException ex) { [Link]().setText("Error"); } } }
Como se puede observar, slo ha sido necesario implementar mtodos getters para los o e atributos, ya que se trata de referencias a objetos y no de tipos simples. Adicionalmente, tfFar y tfCen son componentes de la interfaz del applet, ms que atributos en el sentido a tradicional (representantes del estado interno de la clase). Por ello, otra forma posible de resolver este ejercicio ser pasando las referencias a dichos componentes de interfaz como a parmetros del constructor de la clase oyente. Esta solucin no ser siempre adecuada ya a o a que el oyente podr necesitar acceder a muchos componentes de interfaz. a
import [Link].*; import [Link].*; import [Link].*; public class ConversorTemperatura extends JApplet { private JButton bConvertir; private JTextField tfCen, tfFar; public void init() { setLayout(new FlowLayout()); // Crear componentes tfCen = new JTextField(5); tfFar = new JTextField(5); bConvertir = new JButton("Convertir"); // Aadir componentes n add(new JLabel("C"));
6.11. Ejercicios resueltos add(tfCen); add(new JLabel("F")); add(tfFar); add(bConvertir); // Crear oyente y registrar con fuente (boton Convertir) MiOyente oy = new MiOyente(tfCen, tfFar); [Link](oy);
177
} }
class MiOyente implements ActionListener { private JTextField tfCen, tfFar; public MiOyente (JTextField c, JTextField f) { tfCen = c; tfFar = f; } public void actionPerformed (ActionEvent e) { try { float tempCel = [Link]([Link]()); float tempFar = tempCel*9/5 + 32; [Link]("" + tempFar); } catch(NumberFormatException ex) { [Link]("Error"); } } }
Ejercicio 2 Crear un applet que detecte cuando el ratn se sita sobre una zona especial o u resaltada en rojo e informe de ello al usuario. Implementar la escucha de los eventos de todas las formas estudiadas: en el applet, en clases externas, en clases internas y en clases annimas. o Solucin: En este caso, la interfaz correspondiente a los eventos de ratn, MouseListener, o o contiene ms de un mtodo, con lo que se dispone de un adaptador. De esta forma, la escucha a e de los eventos se puede hacer tanto mediante la implementacin de la interfaz como mediante o el adaptador. Escucha de eventos en el propio applet. Como el applet ya extiende de JApplet, la unica forma disponible de escuchar eventos es implementando la interfaz correspondiente.
import [Link].*; import [Link].*; import [Link].*; class PanelRojo extends JPanel { public PanelRojo () { super(); setBackground([Link]);
178 }
public class Sensible extends JApplet implements MouseListener { private TextField tf; private PanelRojo pr; public void init () { tf = new TextField(); pr = new PanelRojo(); [Link](this); add([Link], pr); add([Link], tf); } public void mouseEntered (MouseEvent e) { [Link]("El ratn est sobre la zona roja"); o a } public void mouseExited (MouseEvent e) { [Link]("El ratn est fuera de la zona roja"); o a } public void mouseClicked (MouseEvent e) {} public void mousePressed (MouseEvent e) {} public void mouseReleased (MouseEvent e) {} }
Escucha de eventos en una clase externa independiente mediante la implementacin o de la interfaz. Como en este caso el oyente slo necesita acceder al campo de texto, o podemos pasarle dicha referencia en su constructor, en lugar de pasarle la referencia al applet.
import [Link].*; import [Link].*; import [Link].*; class PanelRojo extends JPanel { public PanelRojo () { super(); setBackground([Link]); } } public class Sensible extends JApplet { private TextField tf; private PanelRojo pr; public void init () { tf = new TextField(); pr = new PanelRojo();
179
} }
class OyenteExterno implements MouseListener { private TextField tf; public OyenteExterno (TextField tf) { [Link] = tf; } public void mouseEntered (MouseEvent e) { [Link]("El ratn est sobre la zona roja"); o a } public void mouseExited (MouseEvent e) { [Link]("El ratn est fuera de la zona roja"); o a } public void mouseClicked (MouseEvent e) {} public void mousePressed (MouseEvent e) {} public void mouseReleased (MouseEvent e) {} }
Escucha de eventos en una clase externa independiente mediante el uso del adaptador.
import [Link].*; import [Link].*; import [Link].*; class PanelRojo extends JPanel { public PanelRojo () { super(); setBackground([Link]); } } public class Sensible extends JApplet { private TextField tf; private PanelRojo pr; public void init () { tf = new TextField(); pr = new PanelRojo(); [Link](new OyenteExterno(tf)); add([Link], pr); add([Link], tf); }
180 }
class OyenteExterno extends MouseAdapter { private TextField tf; public OyenteExterno (TextField tf) { [Link] = tf; } public void mouseEntered (MouseEvent e) { [Link]("El ratn est sobre la zona roja"); o a } public void mouseExited (MouseEvent e) { [Link]("El ratn est fuera de la zona roja"); o a } }
6.11. Ejercicios resueltos pr = new PanelRojo(); [Link](new OyenteInterno()); add([Link], pr); add([Link], tf);
181
} }
public class Sensible extends JApplet { private TextField tf; private PanelRojo pr; public void init () { tf = new TextField(); pr = new PanelRojo(); [Link]( new MouseListener() { public void mouseEntered (MouseEvent e) { [Link]("El ratn est sobre la zona roja"); o a } public void mouseExited (MouseEvent e) { [Link]("El ratn est fuera de la zona roja"); o a } public void mouseClicked (MouseEvent e) {} public void mousePressed (MouseEvent e) {} public void mouseReleased (MouseEvent e) {} }); add([Link], pr); add([Link], tf); } }
6.11. Ejercicios resueltos [Link]("El ratn est sobre la zona roja"); o a } public void mouseExited (MouseEvent e) { [Link]("El ratn est fuera de la zona roja"); o a } }); add([Link], pr); add([Link], tf);
183
} }
Ejercicio 3 Un ordenador se halla conectado a un sensor que puede generar dos tipos de evento (ACTION1 y ACTION2). Se desea controlar dicho sensor mediante el sistema de eventos de Java. Dado que no existe actualmente ningn evento que pueda responder a u esta necesidad, ser necesario incorporar al sistema de eventos de Java las herramientas a oportunas que permitan manejar los eventos que produce dicho sensor. Para ello, se debe realizar lo siguiente: Denir un nuevo evento SensorEvent, teniendo en cuenta que todos los eventos en Java son subtipos de EventObject. Denir la clase Sensor para que se pueda utilizar como un componente de interfaz gra ca de usuario (todos los componentes en Java heredan de la clase Component). Dicho componente es una fuente del evento denido en el punto anterior y, por tanto, debe permitir la incorporacin de un oyente (listener) para dicho evento (addSensorListener). o Denir las interfaces y adaptadores necesarios para poder crear oyentes del evento. Denir un applet y aadirle un componente de tipo Sensor, incorporndole a continn a uacin un oyente para responder unicamente a ACTION1. o Solucin: o
import [Link].*; import [Link].*; class SensorEvent extends [Link] { ... } class Sensor extends Component { public void addSensorListener (SensorListener l) { ... } } interface SensorListener { public void action1Performed (SensorEvent e); public void action2Performed (SensorEvent e); } class SensorAdapter implements SensorListener { public void action1Performed (SensorEvent e) { }
184
Cap tulo 6. Interfaz grca de usuario y applets a public void action2Performed (SensorEvent e) { }
public class PruebaSensor extends JApplet { public void init() { Sensor sensor = new Sensor(); add(sensor); [Link](new SensorAdapter() { public void action1Performed (SensorEvent e) { ... } }); } }
Ejercicio 4 Implementar las clases y mtodos necesarios para obtener el applet de la e gura 6.9. Proporcionar tambin el chero html necesario para cargar el applet en un e navegador.
Figura 6.9: Applet antes y despus de pulsar el botn e o Como puede verse en la gura, el applet consta de tres partes: un campo de texto donde se permite la introduccin de texto por parte del usuario, un rea donde aparece reejado o a dicho texto pero todo en maysculas o minsculas, y un botn que sirve para cambiar el u u o modo de transformacin. o El comportamiento del applet debe ser el siguiente. A medida que el usuario escribe en el campo de texto, el rea de texto se va actualizando automticamente. El texto que a a aparece en dicha rea es exactamente igual al del campo de texto excepto en que est todo en a a minsculas o todo en maysculas. Pulsar el botn implica cambiar de modo. As cuando el u u o , texto se transforma en minsculas, en el botn aparece Maysculas para cambiar a dicho u o u modo y viceversa. Pulsar el botn hace que se cambie de modo de transformacin y tambin o o e transforma todo lo escrito hasta el momento. Solucin: Se introducen en este ejercicio varios aspectos: o Los mtodos setEditable() y setLineWrap() de JTextArea que transforman el e a rea de texto para que sea o no editable, y para que haga o no ajuste de las l neas, respectivamente.
185
Uso del mtodo getSource() de un evento que permite obtener una referencia al e componente de interaccin que lanz el evento. En este caso, nos permite obtener una o o referencia al botn para leer su estado y cambiarlo (dicho estado est representado por o a el texto del botn, que corresponde con el modo de transformacin del texto). o o Un evento propio de Swing, DocumentEvent que permite escuchar eventos de transformacin del texto contenido en un JTextField o en un JTextArea en tiempo real, o es decir, a medida que se producen, letra a letra. Este evento tiene un funcionamiento similar a los vistos hasta el momento, aunque presenta ciertas diferencias. La ms a inmediata es que su fuente no es directamente el componente de interaccin, sino un o atributo del mismo: el Document. Esto es as porque en Swing se emplea el paradigma Modelo-Vista-Controlador, que separa los datos (Modelo) de la representacin visual o de los mismos (Vista) y del cdigo que responde a eventos y realiza los cambios oporo tunos en los anteriores elementos (Controlador). As pues, a travs del componente de e interaccin, obtenemos una referencia al modelo y nos registramos como oyentes de o los eventos que afecten a los datos. La interfaz de oyente correspondiente en este caso, DocumentListener, posee tres mtodos: changedUpdate(), que informa de cambios e en los atributos del texto; insertUpdate(), que informa de las inserciones en el texto; y removeUpdate(), que informa de los borrados en el texto. Para este ejercicio, slo o nos interesan los dos ultimos. Fichero html:
<HTML> <HEAD><TITLE>MinusMayus</TITLE></HEAD> <BODY> <APPLET CODE="[Link]" WIDTH=300 HEIGHT=200></APPLET> </BODY> </HTML>
Cdigo Java: o
import import import import import [Link].*; [Link].*; [Link].*; [Link].*; [Link].*;
public class MinusMayus extends JApplet { private private private private private private static final String sminus = "Minsculas"; u static final String smayus = "Maysculas"; u JButton b; JTextField t; JTextArea a; boolean pasarMinus = true;
public void init () { b = new JButton(smayus); t = new JTextField(); a = new JTextArea(""); [Link](false); [Link](true);
[Link](new EscuchaBoton()); [Link]().addDocumentListener(new DocumentListener() { public void changedUpdate (DocumentEvent e) { } public void insertUpdate (DocumentEvent e) { actualizar(); } public void removeUpdate (DocumentEvent e) { actualizar(); } });
private void actualizar () { if (pasarMinus) [Link]([Link]().toLowerCase()); else [Link]([Link]().toUpperCase()); } private void actualizar (boolean bminus) { pasarMinus = bminus; actualizar(); } class EscuchaBoton implements ActionListener { public void actionPerformed (ActionEvent e) { boolean bminus; JButton source = (JButton) [Link](); if ([Link]().equals(sminus)) { bminus = true; [Link](smayus); } else { bminus = false; [Link](sminus); } actualizar(bminus); } } }
Ejercicio 5 Disponemos de la clase ConjuntoPalabras que representa una estructura de datos tipo conjunto en la que se almacenan cadenas de texto (String) y el nmero de u veces que cada cadena de texto est repetida en el conjunto. La interfaz de dicha clase es la a siguiente: Constructor: ConjuntoPalabras(): crea un conjunto vac o.
187
Mtodos: e boolean insertar (String s) throws ConjuntoLleno: si la cadena s ya pertenece al conjunto, incrementa en uno su contador y devuelve true. Si la cadena no est en el conjunto, la inserta con el contador de repeticin igual a 1 y devuelve a o false. Si el conjunto est lleno, se lanza la excepcin ConjuntoLleno. a o int buscar (String s): devuelve 0 si la cadena pasada como argumento no est en el conjunto, o el nmero de repeticiones si dicha cadena s pertenece al a u conjunto. Se desea realizar un applet que proporcione una interfaz visual a un conjunto de este tipo. Inicialmente se debe crear un conjunto vac y posteriormente el usuario podr aadir o a n cadenas de texto en el mismo. La apariencia visual del applet es la de la gura 6.10.
Figura 6.10: Applet para construir conjuntos de palabras La cadena escrita en el campo de texto se inserta en el conjunto al presionar el botn o o bien pulsando Enter cuando el cursor est en el campo de texto. En cualquier caso, si la a cadena no estaba en el conjunto, se aade tambin a la lista de la derecha y aparece en el n e cuadro de texto de mensajes el mensaje XXX insertado en el conjunto, siendo XXX el string escrito en el campo de texto. Si la cadena ya estaba en el conjunto, no debe insertarse en la lista de la derecha y debe aparecer en el cuadro de texto de mensajes el mensaje XXX incrementado en el conjunto. Si al intentar insertar una cadena en el conjunto se genera la excepcin ConjuntoLleno debe aparecer en el cuadro de texto de mensajes el texto ERo ROR y en la barra de estado debe especicarse el mensaje: Conjunto lleno. Por otra parte, si en la lista se selecciona cualquier tem, en la caja de texto de mensajes aparecer el nmero de repeticiones del mismo, por ejemplo: Repeticiones = 4. a u Solucin: Aspectos a resaltar en este ejercicio: o Para mostrar un mensaje en la barra de estado del navegador, se emplea el mtodo e showStatus(String s). Siguiendo el paradigma Modelo-Vista-Controlador, JList emplea un modelo para gestionar los datos (en este caso, las entradas de la lista). Por ello, en lugar de aadir n las entradas directamente al JList, se han de aadir al modelo. Para simplicar la n tarea, Java proporciona un modelo bsico para listas, DefaultListModel. De esta a forma, basta con crear una instancia de este modelo y pasarla como parmetro en el a
188
constructor de JList. Posteriormente, cuando se deseen aadir entradas a la lista, se n emplear la referencia al modelo para hacer dichas inserciones. a Para disponer del cdigo completo, se proporciona tambin la implementacin de las o e o clases ConjuntoLleno y ConjuntoPalabras.
import import import import import [Link].*; [Link].*; [Link].*; [Link].*; [Link].*;
class ConjuntoLleno extends Exception {} class ConjuntoPalabras { private static final int max = 10; private HashMap palabras = new HashMap(); private int cont = 0; public boolean insertar (String s) throws ConjuntoLleno { Integer r = (Integer) [Link](s); if (r == null) { if (cont == max) throw new ConjuntoLleno(); cont++; [Link](s, new Integer(1)); return false; } else { [Link](s, new Integer([Link]()+1)); return true; } } public int buscar (String s) { Integer r = (Integer) [Link](s); if (r == null) return 0; return [Link](); } } public class CreadorConjunto extends JApplet { private private private private private private JTextField mensaje; JTextField texto; JButton insertar; ConjuntoPalabras c; DefaultListModel modelo; JList lista;
public void init () { mensaje = new JTextField(5); [Link](false); texto = new JTextField(5); insertar = new JButton("Insertar");
6.11. Ejercicios resueltos c = new ConjuntoPalabras(); modelo = new DefaultListModel(); lista = new JList(modelo); add(mensaje, [Link]); JPanel p1 = new JPanel(); [Link](new GridLayout(1, 2)); add(p1, [Link]); JPanel p2 = new JPanel(); [Link](new GridLayout(3, 1)); [Link](new JLabel("Texto a insertar:")); [Link](texto); [Link](insertar); [Link](p2); [Link](lista); Oyente oy = new Oyente(); [Link](oy); [Link](oy); [Link](ListSelectionModel.SINGLE_SELECTION); [Link](new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { int r = [Link]((String) [Link]()); [Link]("Repeticiones = " + r); } });
189
class Oyente implements ActionListener { public void actionPerformed (ActionEvent e) { try { String s = [Link](); if ([Link]() == 0) return; [Link](""); if () { [Link](s); [Link](s + " insertado en el conjunto"); } else { [Link](s + " incrementado en el conjunto"); } } catch (ConjuntoLleno ex) { [Link]("ERROR"); showStatus("Conjunto lleno"); } } } }
190
6.12.
Ejercicios propuestos
Ejercicio 1 Implementar el juego Adivina el nmero. Este juego consiste en adivinar un u nmero aleatorio en un mximo de 5 intentos. Tras cada intento, el applet responde si se u a ha acertado o, en caso contrario, si el nmero a adivinar es mayor o menor que el nmero u u propuesto por el jugador. Ejercicio 2 Implementar el juego Buscaminas. Ejercicio 3 Implementar el juego Hundir la Flota.