Desarrollo de software Se tratan diferentes aspectos de Java, desde las aplicaciones hasta los applets a través de numerosos ejemplos. Como herramientas de desarrollo se utilizan Microsoft Visual J++ 6 y JBuilder. La versión tratada del lenguaje es la estándar de Sun Microsystems. Se pretende adiestrar sobre: • • • • • • • Programación Orientada a Objetos. Construcción de aplicaciones Java. Creación de applets Java. Tratamiento de eventos y construcción de interfaces de usuario. Utilización de Visual J++ 6. Programación multihilo. Acceso a ficheros y conexiones a través de Internet. El único requisito es conocer algún lenguaje de programación PROGRAMACIÓN EN JAVA ÁNGEL ESTEBAN ADVERTENCIA LEGAL Todos los derechos de esta obra están reservados a Grupo EIDOS Consultoría y Documentación Informática, S.L. El editor prohíbe cualquier tipo de fijación, reproducción, transformación, distribución, ya sea mediante venta y/o alquiler y/o préstamo y/o cualquier otra forma de cesión de uso, y/o comunicación pública de la misma, total o parcialmente, por cualquier sistema o en cualquier soporte, ya sea por fotocopia, medio mecánico o electrónico, incluido el tratamiento informático de la misma, en cualquier lugar del universo. El almacenamiento o archivo de esta obra en un ordenador diferente al inicial está expresamente prohibido, así como cualquier otra forma de descarga (downloading), transmisión o puesta a disposición (aún en sistema streaming). La vulneración de cualesquiera de estos derechos podrá ser considerada como una actividad penal tipificada en los artículos 270 y siguientes del Código Penal. La protección de esta obra se extiende al universo, de acuerdo con las leyes y convenios internacionales. Esta obra está destinada exclusivamente para el uso particular del usuario, quedando expresamente prohibido su uso profesional en empresas, centros docentes o cualquier otro, incluyendo a sus empleados de cualquier tipo, colaboradores y/o alumnos. Si Vd. desea autorización para el uso profesional, puede obtenerla enviando un e-mail [email protected] o al fax (34)-91-5017824. Si piensa o tiene alguna duda sobre la legalidad de la autorización de la obra, o que la misma ha llegado hasta Vd. vulnerando lo anterior, le agradeceremos que nos lo comunique al e-mail [email protected] o al fax (34)-91-5017824). Esta comunicación será absolutamente confidencial. Colabore contra el fraude. Si usted piensa que esta obra le ha sido de utilidad, pero no se han abonado los derechos correspondientes, no podremos hacer más obras como ésta. © Ángel Esteban, 2000 © Grupo EIDOS Consultaría y Documentación Informática, S.L., 2000 ISBN 84-88457-18-9 Programación en Java Ángel Esteban Responsable editorial Paco Marín ([email protected]) Autoedición Magdalena Marín ([email protected]) Ángel Esteban ([email protected]) Grupo EIDOS C/ Téllez 30 Oficina 2 28007-Madrid (España) Tel: 91 5013234 Fax: 91 (34) 5017824 www.grupoeidos.com/www.eidos.es www.LaLibreriaDigital.com Coordinación de la edición Antonio Quirós ([email protected]) Índice ÍNDICE................................................................................................................................................... 5 INTRODUCCIÓN A LA POO ........................................................................................................... 11 ¿QUÉ ES LA POO?.............................................................................................................................. 11 OBJETOS ............................................................................................................................................. 12 MENSAJES .......................................................................................................................................... 13 CLASES............................................................................................................................................... 14 HERENCIA .......................................................................................................................................... 14 MÉTODOS ........................................................................................................................................... 15 POLIMORFISMO .................................................................................................................................. 16 SOBRECARGA ..................................................................................................................................... 16 LA LEY DE DEMETER ......................................................................................................................... 16 MODELO DE OBJETOS ......................................................................................................................... 17 RELACIONES ENTRE CLASES .............................................................................................................. 17 VENTAJAS E INCONVENIENTES DE LA POO ....................................................................................... 18 UN EJEMPLO SENCILLO ...................................................................................................................... 19 INTRODUCCIÓN AL LENGUAJE JAVA ...................................................................................... 21 INTRODUCCIÓN .................................................................................................................................. 21 BREVE HISTORIA DEL LENGUAJE ....................................................................................................... 22 DESCRIPCIÓN DEL LENGUAJE ............................................................................................................. 22 PROGRAMAS EN JAVA: APPLETS Y APLICACIONES ............................................................................ 25 SIMILITUDES Y DIFERENCIAS ENTRE JAVA Y C++ ............................................................................. 26 VERSIONES DEL LENGUAJE ................................................................................................................ 26 ENTORNOS DE DESARROLLO .............................................................................................................. 27 CARACTERÍSTICAS DE LA PLATAFORMA JAVA 2 ............................................................................... 29 SINTAXIS DEL LENGUAJE JAVA................................................................................................. 33 INTRODUCCIÓN .................................................................................................................................. 33 IDENTIFICADORES .............................................................................................................................. 33 PALABRAS CLAVE .............................................................................................................................. 35 LITERALES ......................................................................................................................................... 36 Enteros........................................................................................................................................... 36 Coma flotante................................................................................................................................. 37 Booleanos ...................................................................................................................................... 37 Caracteres...................................................................................................................................... 37 Cadenas ......................................................................................................................................... 38 El literal null.................................................................................................................................. 38 OPERADORES ..................................................................................................................................... 38 SEPARADORES.................................................................................................................................... 39 COMENTARIOS ................................................................................................................................... 39 TIPOS DE DATOS EN JAVA .................................................................................................................. 40 Enteros........................................................................................................................................... 40 Coma flotante................................................................................................................................. 41 Booleanos ...................................................................................................................................... 41 Carácter......................................................................................................................................... 41 Cadenas ......................................................................................................................................... 41 Arrays ............................................................................................................................................ 42 EMPAQUETAR TIPOS PRIMITIVOS ....................................................................................................... 43 CONVERSIÓN DE TIPOS DE DATOS ...................................................................................................... 44 BLOQUES Y ÁMBITOS ......................................................................................................................... 45 Bloques .......................................................................................................................................... 45 Ámbitos .......................................................................................................................................... 46 EXPRESIONES ..................................................................................................................................... 46 CLASIFICACIÓN DE OPERADORES ...................................................................................................... 48 Aritméticos..................................................................................................................................... 48 Operadores sobre enteros........................................................................................................... 48 Operadores sobre reales............................................................................................................. 50 Booleanos ...................................................................................................................................... 50 Relacionales................................................................................................................................... 51 Cadena........................................................................................................................................... 51 Asignación ..................................................................................................................................... 52 PRECEDENCIA DE LOS OPERADORES .................................................................................................. 52 CONTROL DE FLUJO............................................................................................................................ 53 If-else ............................................................................................................................................. 53 Switch............................................................................................................................................. 54 For ................................................................................................................................................. 55 While.............................................................................................................................................. 56 Do-while: ....................................................................................................................................... 57 Break, continue y etiquetas............................................................................................................ 57 Return ............................................................................................................................................ 59 Try, catch, finally y throws: ........................................................................................................... 59 POO EN JAVA: OBJETOS................................................................................................................ 61 INTRODUCCIÓN .................................................................................................................................. 61 OBJETOS ............................................................................................................................................. 61 CREACIÓN DE OBJETOS ...................................................................................................................... 62 Declaración ................................................................................................................................... 62 Instanciación.................................................................................................................................. 63 Inicialización ................................................................................................................................. 64 UTILIZACIÓN DE OBJETOS .................................................................................................................. 64 DESTRUCCIÓN DE OBJETOS ................................................................................................................ 66 POO EN JAVA: CLASES................................................................................................................... 69 CLASES............................................................................................................................................... 69 DECLARACIÓN DE LA CLASE .............................................................................................................. 70 Superclase de la clase.................................................................................................................... 70 Interfaces de la clase ..................................................................................................................... 70 Modificadores de la clase.............................................................................................................. 71 CUERPO DE LA CLASE. DECLARACIÓN DE ATRIBUTOS ...................................................................... 72 Modificadores................................................................................................................................ 72 Tipo................................................................................................................................................ 74 Nombre variable ............................................................................................................................ 75 CUERPO DE LA CLASE. IMPLEMENTACIÓN DE MÉTODOS ................................................................... 75 Declaración del método................................................................................................................. 75 Declaración de variables............................................................................................................... 80 Implementación del método........................................................................................................... 80 HERENCIA .......................................................................................................................................... 82 Sustituir la implementación de un método..................................................................................... 84 Ampliar la implementación de un método ..................................................................................... 85 Métodos que una clase heredada no puede redefinir .................................................................... 85 Métodos que una clase heredada tiene que redefinir .................................................................... 86 CLASES ABSTRACTAS......................................................................................................................... 86 POO EN JAVA: OTROS CONCEPTOS .......................................................................................... 89 INTRODUCCIÓN .................................................................................................................................. 89 INTERFACES ....................................................................................................................................... 89 Declaración del interfaz ................................................................................................................ 90 Cuerpo del interfaz ........................................................................................................................ 90 EXCEPCIONES..................................................................................................................................... 91 PAQUETES .......................................................................................................................................... 94 PRINCIPALES PAQUETES DEL LENGUAJE JAVA................................................................................... 98 LA CLASE OBJECT ............................................................................................................................ 100 POO EN JAVA: UN EJEMPLO. RESUMEN DE CONCEPTOS................................................ 103 INTRODUCCIÓN ................................................................................................................................ 103 DESCRIPCIÓN DEL PROBLEMA.......................................................................................................... 103 UTILIZANDO EL JDK 1.3.................................................................................................................. 104 CREANDO LAS CLASES ..................................................................................................................... 105 APLICACIONES JAVA ................................................................................................................... 111 INTRODUCCIÓN ................................................................................................................................ 111 INTRODUCCIÓN A VISUAL J++ 6...................................................................................................... 112 INTRODUCCIÓN A JBUILDER 3.5 ...................................................................................................... 117 EL MÉTODO MAIN().......................................................................................................................... 122 LA CLASE SYSTEM ........................................................................................................................... 125 LA CLASE RUNTIME ......................................................................................................................... 128 ALGUNAS CONSIDERACIONES SOBRE VISUAL J++ 6 ....................................................................... 130 CONSIDERACIONES SOBRE JBUILDER .............................................................................................. 135 INTERFACES DE USUARIO EN JAVA: COMPONENTES AWT ........................................... 145 INTRODUCCIÓN ................................................................................................................................ 145 EL AWT (ABSTRACT WINDOW TOOLKIT) ...................................................................................... 145 UTILIZANDO LOS COMPONENTES DEL AWT.................................................................................... 148 Frame........................................................................................................................................... 148 Cursor.......................................................................................................................................... 149 7 MenuBar ...................................................................................................................................... 150 Menu ............................................................................................................................................ 150 MenuItem ..................................................................................................................................... 150 CheckboxMenuItem ..................................................................................................................... 150 Dialog .......................................................................................................................................... 152 FileDialog.................................................................................................................................... 153 Container ..................................................................................................................................... 153 Button........................................................................................................................................... 154 Label ............................................................................................................................................ 156 List, Choice.................................................................................................................................. 157 TextField, TextArea ..................................................................................................................... 158 Checkbox, CheckboxGroup ......................................................................................................... 160 ScrollPane.................................................................................................................................... 162 INTERFACES DE USUARIO EN JAVA: GESTORES DE DISEÑO Y EVENTOS................. 165 INTRODUCCIÓN ................................................................................................................................ 165 GESTORES DE DISEÑO ...................................................................................................................... 165 FlowLayout .................................................................................................................................. 167 BorderLayout............................................................................................................................... 167 CardLayout .................................................................................................................................. 168 GridLayout................................................................................................................................... 170 GridBagLayout ............................................................................................................................ 171 TRATAMIENTO DE EVENTOS EN JAVA .............................................................................................. 173 INTERFACES DE USUARIO EN JAVA: COMPONENTES SWING / CONTENEDORES... 189 INTRODUCCIÓN ................................................................................................................................ 189 JFC Y SWING ................................................................................................................................... 190 COMPONENTES SWING FRENTE A COMPONENTES AWT.................................................................. 191 CONTENEDORES DE ALTO NIVEL...................................................................................................... 192 JFrame......................................................................................................................................... 197 JDialog, JOptionPane ................................................................................................................. 199 JApplet ......................................................................................................................................... 208 CONTENEDORES INTERMEDIOS ........................................................................................................ 208 JPanel .......................................................................................................................................... 209 JTabbedPane ............................................................................................................................... 212 JToolBar ...................................................................................................................................... 216 JLayeredPane .............................................................................................................................. 217 INTERFACES DE USUARIO EN JAVA: COMPONENTES ATÓMICOS DE SWING ......... 223 INTRODUCCIÓN ................................................................................................................................ 223 COMPONENTES ATÓMICOS ............................................................................................................... 223 COMPONENTES PARA OBTENER INFORMACIÓN ............................................................................... 225 JButton......................................................................................................................................... 225 JCheckbox.................................................................................................................................... 227 JRadioButton ............................................................................................................................... 229 JComboBox.................................................................................................................................. 231 JMenu .......................................................................................................................................... 233 JSlider.......................................................................................................................................... 238 COMPONENTES PARA MOSTRAR INFORMACIÓN ............................................................................... 242 JLabel .......................................................................................................................................... 242 JToolTip....................................................................................................................................... 245 JProgressBar ............................................................................................................................... 245 COMPONENTES QUE MUESTRAN INFORMACIÓN ESTRUCTURADA.................................................... 247 JColorChooser............................................................................................................................. 247 JFileChooser................................................................................................................................ 252 INTERFACES DE USUARIO EN JAVA: OTRAS CARACTERÍSTICAS DE SWING........... 257 INTRODUCCIÓN ................................................................................................................................ 257 EL GESTOR DE DISEÑO BOXLAYOUT ............................................................................................... 257 ESTABLECIENDO EL LOOK & FEEL .................................................................................................. 262 APPLETS DE JAVA: INTRODUCCIÓN A LOS APPLETS....................................................... 269 CONCEPTOS PREVIOS ....................................................................................................................... 269 Internet......................................................................................................................................... 269 URLs y direcciones IP ................................................................................................................. 270 Clientes y Servidores Web ........................................................................................................... 271 HTML........................................................................................................................................... 272 HTTP ........................................................................................................................................... 272 INTRODUCCIÓN A LOS APPLETS DE JAVA ......................................................................................... 272 EL CICLO DE VIDA DE LOS APPLETS ................................................................................................. 282 SEGURIDAD EN LOS APPLETS ........................................................................................................... 285 TRUSTED/UNTRUSTED APPLETS ...................................................................................................... 289 DIFERENCIAS ENTRE APPLETS Y APLICACIONES .............................................................................. 289 LA ETIQUETA APPLET ....................................................................................................................... 290 Codebase ..................................................................................................................................... 291 Code............................................................................................................................................. 291 Alt................................................................................................................................................. 291 Name............................................................................................................................................ 291 Width, Height............................................................................................................................... 291 Align............................................................................................................................................. 291 Vspace, Hspace............................................................................................................................ 292 <PARAM>................................................................................................................................... 292 Etiquetas HTML alternativas ...................................................................................................... 292 APPLETS DE JAVA: UTILIZANDO LOS APPLETS ................................................................. 293 INTERACCIÓN DE LOS APPLETS CON EL NAVEGADOR WEB .............................................................. 293 EVENTOS, GRÁFICOS Y FUENTES EN LOS APPLETS ........................................................................... 303 COMPRESIÓN DE APPLETS ................................................................................................................ 322 COMPONENTES SWING Y APPLETS ................................................................................................... 325 LA CLASE JAPPLET .......................................................................................................................... 327 ASPECTOS AVANZADOS DE JAVA: PROCESOS.................................................................... 331 INTRODUCCIÓN ................................................................................................................................ 331 PROCESOS Y MULTIPROCESO ........................................................................................................... 331 HOLA MUNDO CON HILOS ................................................................................................................ 332 PARALELISMO .................................................................................................................................. 333 UTILIZANDO PROCESOS. EL INTERFAZ RUNNABLE .......................................................................... 337 COORDINANDO LOS PROCESOS ........................................................................................................ 344 Sin derecho preferente................................................................................................................. 344 Con derecho preferente ............................................................................................................... 344 ASPECTOS AVANZADOS DE JAVA: CANALES Y SOCKETS............................................... 345 INTRODUCCIÓN A LOS CANALES ...................................................................................................... 345 CANALES ESTÁNDAR DE ENTRADA/SALIDA ..................................................................................... 346 CANALES DE JAVA.IO ....................................................................................................................... 348 CANALES DE TRANSMISIÓN (DATA SINK STREAMS) .......................................................................... 350 CANALES DE PROCESO (PROCESSING STREAMS)............................................................................... 352 Canales de Filtrado ..................................................................................................................... 353 Canales de buffer......................................................................................................................... 353 Canal de Concatenación.............................................................................................................. 354 Canales de conversión de tipos ................................................................................................... 354 9 Serialización de Objetos .............................................................................................................. 356 Canal Contador de Líneas........................................................................................................... 357 Canales de impresión .................................................................................................................. 358 Canales de vuelta atrás ............................................................................................................... 359 APLICACIONES CLIENTE/SERVIDOR EN JAVA ................................................................................... 359 Introducción a la POO ¿Qué es la POO? Las siglas POO se corresponden con Programación Orientada a Objetos, aunque muchas veces las podemos encontrar escritas en inglés OOP (Object Oriented Programming). En este primer capítulo de este curso vamos a tratar de explicar de forma sencilla los principales conceptos y términos que se utilizan dentro de este tipo de programación, es decir, dentro de la Programación Orientada a Objetos. No vamos a entrar en sesudas divagaciones filosóficas respecto a la POO, sino que vamos a definir lo más claramente posible cada uno de los elementos clave que aparecen en la POO para después poder aplicarlos al lenguaje que nos ocupa, es decir, al lenguaje Java. Este tema es muy necesario debido a que el lenguaje Java, como veremos en el siguiente capítulo, es un lenguaje que se basa en la Programación Orientada a Objetos, por lo tanto para conocer el lenguaje Java, es necesario conocer la Programación Orientada a Objetos. La Programación Orientada a Objetos trata de utilizar una visión real del mundo dentro de nuestros programas. La visión que se tiene del mundo dentro de la POO es que se encuentra formado por objetos. Para comprender bien la POO debemos olvidar un poco la Programación Estructurada, que si nos fijamos bien es algo artificial, la POO es una forma de abordar los problemas más natural. Aquí natural significa más en contacto con el mundo real que nos rodea, de esta forma si queremos resolver un problema determinado, debemos identificar cada una de las partes del problema con objetos presentes en el mundo real. Programación en Java © Grupo EIDOS En esta definición de POO ya estamos haciendo referencia al elemento clave de la misma: el objeto. El objeto va a ser la modelización de los objetos que nos encontramos en el mundo real, estos objetos los vamos a utilizar en nuestros programas para dar la solución al problema que nos ocupe en cada caso. Objetos Como ya hemos adelantado un objeto es la pieza básica de la POO, es una representación o modelización de un objeto real perteneciente a nuestro mundo, por ejemplo, podemos tener un objeto perro que represente a un perro dentro de nuestra realidad, o bien un objeto factura, cliente o pedido. Los objetos en la vida real tienen todos, dos características: estado y comportamiento. El estado de un objeto viene definido por una serie de parámetros que lo definen y que lo diferencian de objetos del mismo tipo. En el caso de tener un objeto perro, su estado estaría definido por su raza, color de pelo, tamaño, etc. Y el comportamiento viene definido por las acciones que pueden realizar los objetos, por ejemplo, en el caso del perro su comportamiento sería: saltar, correr, ladrar, etc. El comportamiento permite distinguir a objetos de distinto tipo, así por ejemplo el objeto perro tendrá un comportamiento distinto a un objeto gato. Si tomamos un ejemplo que tiene que ver más con el mundo de la informática se pueden ver más claros estos dos conceptos. Si tenemos un objeto pantalla que representa la pantalla de nuestro ordenador, el estado de la misma estaría definido por los siguientes parámetros: encendida o apagada, tamaño, resolución, número de colores, etc.; y su comportamiento podría ser: imprimir, encender, apagar, etc. Los parámetros o variables que definen el estado de un objeto se denominan atributos o variables miembro y las acciones que pueden realizar los objetos se denominan métodos o funciones miembro, y para indicar variables miembro y funciones miembro se utiliza el término general miembro. Si lo comparamos con la programación estructurada podríamos hacer la siguiente aproximación: los atributos o variables miembro serían variables y los métodos o funciones miembro procedimientos y funciones. A partir de ahora y a lo largo de todo el presente curso vamos a utilizar únicamente la nomenclatura de atributos y métodos. Los atributos de un objeto deben encontrarse ocultos al resto de los objetos, es decir, no se va a poder acceder directamente a los atributos de un objeto para modificar su estado o consultarlo. Para acceder a los atributos de un objeto se deben utilizar métodos. Es decir, los métodos exponen toda la funcionalidad del objeto, mientras que los detalles del estado interno del objeto permanecen ocultos. Incluso algunos métodos también pueden permanecer ocultos. El hecho de ocultar la implementación interna de un objeto, es decir, como está construido y de que se compone se denomina encapsulación. La encapsulación es uno de los beneficios y particularidades del paradigma de la Programación Orientada a Objetos. Normalmente un objeto ofrece una parte pública que será utilizada por otros objetos para interactuar entre sí, pero también permanece una parte oculta para encapsular los detalles de la implementación del objeto. Ya se ha dicho que un objeto está compuesto de atributos y métodos. Como la caja negra de un avión, el objeto recubre la información que almacena y solamente podemos obtener la información e indicarle que realiza acciones por medio de lo que comúnmente se denomina interfaz del objeto, que estará constituido por los métodos públicos. 12 © Grupo EIDOS 1. Introducción a la POO Los datos y la implementación queda oculta a los demás objetos que interaccionan en el programa, lo que favorece enormemente la protección de los datos y las estructuras internas contra las modificaciones externas al objeto. De este modo es mucho más sencillo localizar errores en los programas puesto que cada objeto está altamente especializado, y sólo se encarga de su tarea. Como se puede observar, esto consigue una mayor modularidad, que facilita además el diseño en equipo de programas y la reutilización de clases (componentes) creados por otros desarrolladores. Hemos indicado que la encapsulación es un beneficio que nos aporta la POO, es un beneficio porque permite abstraernos de la utilización de los objetos, es decir, a mí me interesa realizar una determinada tarea con un objeto (por ejemplo imprimir una pantalla o rellenar una factura), pero a mí no me interesa como realiza este proceso internamente el objeto que estoy utilizando. En este momento entra en juego otro concepto importante de la POO y que es la abstracción. La abstracción indica la capacidad de ignorar determinados aspectos de la realidad con el fin de facilitar la realización de una tarea. Nos permite ignorar aquellos aspectos de la realidad que no intervienen en el problema que deseamos abordar, y también nos permite ignorar los aspectos de implementación de los objetos en los pasos iniciales, con lo cual sólo necesitamos conocer qué es lo que hace un objeto, y no cómo lo hace, para definir un objeto y establecer las relaciones de éste con otros objetos. Un objeto lo podríamos representar como dos circunferencias, una interna que permanece oculta al mundo exterior y que contendría todos los detalles de la implementación del objeto, y otra circunferencia concéntrica externa, que representa lo que el objeto muestra al mundo exterior y le permite utilizar para interactuar con él. En la Figura 1 se puede ver dicha representación. La encapsulación ofrecida a través de objetos tienen varios beneficios, entre los que destacan la modularidad y la ocultación de la información. Mediante la modularidad podemos escribir código de manera independiente de cómo se encuentren construidos los diferentes objetos que vamos a utilizar. Y ocultando la información se permite realizar cambios en el código interno de los objetos sin que afecte a otros objetos que los utilicen o dependan de ellos. No es necesario entender la implementación interna de un objeto para poder utilizarlo. Figura 1 Mensajes Los mensajes son la forma que tienen de comunicarse distintos objetos entre sí. Un objeto por sí sólo no es demasiado útil, sino que se suele utilizar dentro de una aplicación o programa que utiliza otros objetos. El comportamiento de un objeto está reflejado en los mensajes a los que dicho objeto puede responder. Representan las acciones que un determinado objeto puede realizar. 13 Programación en Java © Grupo EIDOS Un mensaje enviado a un objeto representa la invocación de un determinado método sobre dicho objeto, es decir, la ejecución de una operación sobre el objeto. Es la manera en la que un objeto utiliza a otro, el modo en el que dos objetos se comunican, ya que la ejecución de ese método retornará el estado del objeto invocado o lo modificará. Los mensajes se utilizan para que distintos objetos puedan interactuar entre sí y den lugar a una funcionalidad más compleja que la que ofrecen por separado. Un objeto lanzará o enviará un mensaje a otro objeto si necesita utilizar un método del segundo objeto. De esta forma si el objeto A quiere utilizar un método del objeto B, le enviará un mensaje al objeto A. Para enviar un mensaje se necesitan tres elementos: el objeto al que se le va a enviar el mensaje, el nombre del método que se debe ejecutar y los parámetros necesarios para el método en cuestión. Clases Una clase es un molde o prototipo que define un tipo de objeto determinado. Una clase define los atributos y métodos que va a poseer un objeto. Mediante las clases podremos crear o instanciar objetos de un mismo tipo, estos objetos se distinguirán unos de otros a través de su estado, es decir, el valor de sus atributos. La clase la vamos a utilizar para definir la estructura de un objeto, es decir, estado (atributos) y comportamiento (métodos). La clase es un concepto abstracto que generalmente no se va a utilizar directamente en nuestros programas o aplicaciones. Lo que vamos a utilizar van a ser objetos concretos que son instancias de una clase determinada. La clase es algo genérico y abstracto, es similar a una idea. Cuando decimos piensa en un coche todos tenemos en mente la idea general de un coche, con puertas, ruedas, un volante, etc., sin embargo cuando decimos "ese coche que está aparcado ahí fuera", ya se trata de un coche determinado, con una matrícula, de un color, con un determinado número de puertas, y que podemos tocar y utilizar si es necesario. Sin embargo como ya hemos dicho la clase es la idea que define al objeto concreto. Un ejemplo que se suele utilizar para diferenciar y relacionar clases y objetos es el ejemplo del molde de galletas. El molde para hacer galletas sería una clase, y las galletas que hacemos a partir de ese molde ya son objetos concretos creados a partir de las características definidas por el molde. Una vez implementada una clase podremos realizar instancias de la misma para crear objetos que pertenezcan a esa clase. Las clases ofrecen el beneficio de la reutilización, utilizaremos la misma clase para crear distintos objetos. Y luego veremos que una vez que tenemos una clase podremos aprovecharla heredando de ella para complicarla o especializarla para una labor concreta. Si comparamos las clases y objetos de la POO con la programación estructurada tradicional, se puede decir que las clases son los tipos de datos y los objetos las variables de esos tipos de datos. De esta forma si tenemos el tipo entero, en la POO diríamos que es la clase entero, y si tenemos una variable de tipo entero, en la POO diríamos que tenemos un objeto de la clase entero. Herencia La herencia es un mecanismo mediante el cual podemos reutilizar clases ya definidas. Es decir, si tenemos una clase botón que define un tipo de objeto que se corresponde con un botón que tiene un 14 © Grupo EIDOS 1. Introducción a la POO texto, que se puede pulsar, etc., si queremos definir una nueva clase llamada botón de color, no tenemos que rescribir todo el código y crear una clase completamente nueva, sino lo que haremos será heredar de la clase botón, utilizar lo que nos ofrezca esta clase y añadirle lo que sea necesario para la nueva funcionalidad deseada. La herencia dentro de la POO es un mecanismo fundamental que se puede definir también como una transmisión de las características de padres a hijos. Entendiendo aquí características como métodos y atributos de una clase. La clase hija puede añadir atributos, métodos y redefinir los métodos de la clase padre. Podemos ver la herencia como una sucesiva especialización de las clases. La clase de la que se hereda se suele denominar clase padre o superclase, y la clase que hereda se denomina clase hija o subclase. El mecanismo de herencia es muy potente, puesto que nos permite agregar funcionalidades nuevas a una clase ya existente, reutilizando todo el código que ya se tenga disponible de la clase padre, es decir, se heredarán sus atributos y métodos, como ya habíamos indicado con anterioridad. En las clases hijas podemos redefinir el comportamiento de la clase padre. Para verificar que la herencia entre dos clases es correcta y coherente, debemos hacernos la pregunta de "¿es un?" o "¿es un tipo de?". Por ejemplo, en el caso que comentábamos del botón, tenemos una clase BotonColor que hereda de la clase Boton. Esta herencia contesta perfectamente a la pregunta definida antes: ¿un botón de color es un botón?, evidentemente sí. Mediante el mecanismo de herencia podemos definir superclases denominadas clases abstractas que definen comportamientos genéricos. De esta clase pueden heredar otras clases que ya implementan de forma más concreta estos comportamientos. De esta forma podremos crear jerarquías de clases. Así por ejemplo podemos tener una clase bicicleta, que será más o menos genérica. De esta clase pueden heredar la clase bicicleta de montaña, bicicleta de carreras y tandem, que ya ofrecen una clase más especializada que la clase padre. Los métodos que se heredan de la clase padre no tienen porqué utilizarse sin realizar ningún cambio, se puede llevar a cabo lo que se denomina la sobrescritura de métodos. Podemos heredar un método de la clase padre, pero en la clase hija le podemos dar una implementación diferente para que se adecue a la nueva clase. La herencia puede ser simple si la clase hija hereda de una única clase padre o múltiple si hereda de varias clases padre, más adelante veremos que el tipo de herencia que soporta Java es una herencia simple. Algunas veces la herencia múltiple puede llegar a ser confusa. Métodos Como ya hemos dicho anteriormente los métodos son las acciones que se pueden realizar con los objetos. También se podría definir un método como la implementación de un mensaje, al fin y al cabo, un mensaje es la llamada o invocación de un método de un objeto. Existen dos métodos especiales dentro de la POO que se denominan constructor y destructor. El método constructor se ejecuta automáticamente cada vez que se crea un objeto de la clase en cuestión, sobre el objeto que acaba de crearse, inmediatamente después de haberse asignado memoria a dicho objeto. Algunos lenguajes proporcionan un constructor por defecto, pero generalmente lo correcto es que lo defina el diseñador de la clase y que en él se lleven a cabo las inicializaciones y todas aquellas operaciones que se necesiten para poder usar el objeto. 15 Programación en Java © Grupo EIDOS El método destructor se invoca automáticamente inmediatamente antes de liberar la memoria del objeto en cuestión, o lo que es lo mismo, antes de que se salga del ámbito de la declaración del objeto, por lo que se ha de emplear para que la destrucción del objeto se efectúe correctamente y contendrá operaciones tales como liberación de memoria asignada dinámicamente dependiente del objeto, grabación de todos o parte de los atributos del objeto en un fichero o base de datos y operaciones similares. Polimorfismo El término polimorfismo expresa la posibilidad de que el mismo mensaje, enviado a objetos distintos, ejecute métodos distintos. Esto significa que podemos definir dentro de dos clases distintas dos operaciones con el mismo nombre y aspecto externo, pero con distintas implementaciones para cada clase. En el momento de emplear estas operaciones, el lenguaje es capaz de ejecutar el código correspondiente dependiendo de la clase del objeto sobre el que se ejecuta la operación. Esto permite definir un interfaz común, un aspecto externo idéntico, para una serie de clases. De esta forma podemos definir un método suma() para la clase Enteros y otro método suma() para la clase Matrices. El mensaje será el mismo, pero la implementación de los métodos será distinta ya que no es lo mismo sumar enteros que matrices. Sobrecarga La sobrecarga de métodos se produce cuando una clase tiene métodos con el mismo nombre pero que difieren o bien en el número o en el tipo de los parámetros que reciben dichos métodos. Un ejemplo de sobrecarga los podemos tener en el método imprimir(), mediante este método vamos a imprimir en pantalla y realizar un salto de línea. Este método acepta como parámetro una variable de tipo entero o una variable de tipo cadena de caracteres, incluso si no indicamos ningún parámetro realizaría un salto de línea. La ley de Demeter En lo que respecta a la implementación de métodos de una clase, existe una ley que nos da una serie de pautas para realizar una Programación Orientada a Objetos correcta, esta ley es la Ley de Demeter. La Ley de Demeter, enunciada por Karl Lieberherr, determina el acceso o visibilidad de los objetos en la implantación de un método, y se rige por el siguiente principio: en la implantación de un método se puede tener acceso a los siguientes objetos: • Atributos de su clase. • Los parámetros que se pasan al método. • Objetos temporales creados dentro del método. Existen dos formas de la Ley de Demeter, la flexible y la estricta, la flexible permite acceder a los atributos de la clase padre o superclase y la estricta indica que este acceso se debe lograr mediante la utilización de métodos de acceso de la clase padre. 16 © Grupo EIDOS 1. Introducción a la POO Modelo de objetos El modelo de objetos es un conjunto de principios que se deben dar para que se puedan modelar objetos computacionales (objetos dentro de nuestro código y programas) a partir de objetos de la realidad de manera que éstos reflejen los posibles comportamientos presentes en la realidad y compongan un modelo computacional válido. Es decir, definido e identificado un problema se trata de realizar una representación lógica de los objetos que forman parte del problema en el mundo real, de esta forma podremos utilizar en nuestro programa estos objetos para dar la solución a través de nuestro código. El modelo de objetos que definen un problema de la realidad se basa en los siguientes principios: abstracción, encapsulación, herencia y polimorfismo. Todos estos conceptos básicos de la POO ya han sido discutidos en este tema en mayor o menor medida. Por lo tanto antes de empezar a construir un programa para una tarea específica, es necesario construir y diseñar su modelo de objetos. Existen diversas técnicas de modelización, pero el objetivo de este curso no es el de explicarlas, así que no entraremos en más detalles. Relaciones entre clases Una clase por sí sola no ofrece una funcionalidad demasiado interesante, como ya veremos a lo largo del curso, el lenguaje Java se encuentra formado por un gran número de clases que forman parte de una compleja y completa jerarquía. Cada una de las clases está especializada en una función o tarea específica, es decir, se da una gran modularidad, cada clase tiene su cometido. Por lo tanto para ofrecer una mayor funcionalidad y realizar tareas más complejas es necesario que exista una relación entre distintas clases. Un ejemplo podría ser las piezas del motor de un coche. Las piezas por si solas no realizan ninguna tarea de importancia, cuando realmente se saca provecho de ellas es cuando se relacionan entre sí y se construye con ellas un motor. A su vez este motor puede ser incorporado dentro de un coche, que estará compuesto a su vez de más objetos o piezas. Como vemos la correspondencia entre los objetos del mundo real y los objetos del mundo computacional es bastante obvia en algunos casos. Básicamente una clase se puede relacionar con otra de tres formas diferentes. Las relaciones que podemos distinguir son: • Relación de composición: una clase puede estar compuesta de otras clases. Esto se consigue implementando los atributos de la clase como objetos de otra clase. Una clase hace uso de otra a través de sus atributos, una clase se encuentra formada por varias clases. Por ejemplo la clase Coche tiene atributos que son de la clase Puerta, Rueda, Motor, etc. • Relación de uso: una clase se relaciona con otra a través de los mensajes que le envía. Esto se consigue pasándose una instancia de la clase como uno de los parámetros del método invocado por el mensaje. Es decir, si tenemos la clase Pantalla con el método dibujar() y queremos dibujar un objeto de la clase Rectangulo, deberíamos realizar lo siguiente objPantalla.dibujar(Rectangulo), es decir, el parámetro que se le pasa al método dibujar() de la clase Pantalla es un objeto de la clase Rectangulo. • Relación de herencia: este tipo de relación entre clases ya la conocemos y consiste en que una clase hija hereda de una clase padre o superclase pudiendo utilizar así toda la funcionalidad ofrecida por la clase padre y añadir nuevas funcionalidades. En esta forma de relación se 17 Programación en Java © Grupo EIDOS consigue la reutilización del código y una progresiva especialización a través de una jerarquía de clases. Ventajas e inconvenientes de la POO En este apartado vamos a comentar que ventajas nos ofrece el paradigma de la POO, también mostraremos algunas de sus desventajas. Como ventajas o aportaciones podemos destacar las siguientes: • Facilita la reutilización del software. A través de la herencia se nos permite utilizar en un objeto las operaciones implementadas para otros sin esfuerzo adicional. Por otro lado la encapsulación nos facilita el uso de objetos que no hemos definido ni implementado. Se pude considerar que la encapsulación y el polimorfismo son las herramientas más potentes del paradigma de la POO. • Facilita la construcción de programas portables. Es posible diseñar una capa de objetos que se comunique con la máquina o el sistema operativo, y sobre ésta los objetos que dan funcionalidad a la aplicación. Una migración a otra arquitectura sólo requerirá cambios en dicha capa, y la encapsulación nos garantiza que los cambios se van a limitar a esos objetos. Java refuerza más esta característica de la portabilidad, ya que, como veremos en el próximo capítulo, el lenguaje Java es independiente de la plataforma en la que se ejecuta. • Facilita el mantenimiento. El encapsulamiento nos garantiza que las modificaciones realizadas en un objeto tendrán un efecto limitado. Si un elemento de datos se accede directamente y en un momento dado cambia de tipo o formato, habrá que localizar todos los puntos en los que se accede a dicho elemento y modificarlos en consecuencia. Si este elemento de datos está encapsulado en un objeto y siempre es accedido mediante las operaciones disponibles, un cambio como el indicado se limitará al objeto, del que sólo habrá que cambiar el elemento de datos y las operaciones del objeto que lo utilizan. Lo recomendable es desconocer la implementación interna del objeto, nosotros necesitaremos utilizar un objeto que ofrece una serie de funcionalidades, pero no nos interesa de que forma nos ofrece las mismas. • Provoca que las tareas de análisis, diseño e implementación sean más intuitivas, ya que se manejan objetos, concepto con el que todos estamos familiarizados, y estructuradas, ya que podemos asignar las tareas de diseño e implementación en el ámbito de objetos. Se debe recordar que los objetos en nuestros diseños van a representar objetos presentes en el mundo real. No todos son beneficios, la POO ofrece también una serie de desventajas, aunque más que desventajas o inconvenientes podemos decir que presenta una serie de dificultades, las cuales se comentan a continuación: 18 • Curvas de aprendizaje largas, debido principalmente a que implica un cambio mentalidad, y no sólo el aprender un nuevo lenguaje. • Dificultad en determinar las características de un objeto. Debido a que un objeto no se define aisladamente, sino que depende de las relaciones con otros objetos, el establecimiento de nuevas relaciones puede implicar un cambio en la definición del objeto y viceversa. Por todo ello es conveniente iterar, esto es, repetir el proceso de definición de objetos y de identificación de relaciones, tantas veces como sean necesarias para alcanzar un modelo estable. En muchos casos resulta complicado llegar a un modelo de objetos definitivo con el que se pueda abordar el problema planteado de forma completa. © Grupo EIDOS • 1. Introducción a la POO Jerarquías de herencia complejas: en muchos casos se tiende a utilizar de forma desmedida el mecanismo de herencia, ofreciendo en algunos casos unas jerarquías de herencia difíciles de seguir y abordar. Un ejemplo sencillo No hace falta decir que el tema que nos ocupa es un tema eminentemente teórico, pero aun así vamos a incluir un breve y sencillo ejemplo con el que se pretende mostrar de forma sencilla como se puede construir un modelo de objetos, identificando los objetos que intervienen en el problema a resolver, definiendo los atributos de la clase, los métodos, relaciones entre las diferentes clases, etc. En este caso vamos a abandonar los típicos ejemplos de la clase Coche y similares y vamos a mostrar un ejemplo más lúdico. En nuestro ejemplo vamos a tener la clase VideoJuego, es decir, vamos a crear el juego de matamarcianos conocido por todos. No vamos a profundizar demasiado en todos los atributos y métodos de las clases, sino que vamos a mostrar únicamente los más relevantes. En el primer paso vamos a identificar las clases necesarias para abordar nuestro problema, en este caso, la creación de un juego de matamarcianos. Primero tengamos en cuenta las especificaciones: • En el juego tenemos dos tipos de enemigos: marcianos y venusianos. • El protagonista del juego es un terrícola. • El terrícola dispone de un arma para defenderse de los enemigos, un lanza cohetes. Como se puede observar no vamos a tener demasiadas complicaciones a la hora definir el modelo de objetos de nuestro programa. Se pueden distinguir seis clases: VideoJuego, Enemigo, Marciano, Venusiano, Terricola y LanzaCohetes. La clase VideoJuego sería la clase principal y contendría al resto de las clases. Por lo tanto entre la clase VideoJuego y el resto de las clases existe una relación de composición. Esta clase tendría como atributos posibles: Enemigos, Heroes y Armas; y como métodos: comenzarPartida(), interrumpirPartida(), reanudarPartida() y finalizarPartida(), la finalidad de cada uno de ellos está bastante clara. La clase Enemigo en realidad va a ser una clase abstracta, ya que no vamos a instanciar objetos de esta clase. La clase Enemigo va a ser la clase padre de la clase Marciano y Venusiano, y lo que se consigue con esta clase es agrupar todo el comportamiento que es común a los enemigos que aparecen en el juego. Esta clase tiene los siguientes atributos: Color, NumeroOjos y NumeroPiernas. Estos atributos serán comunes a las clases Marciano y Venusiano. Existe una relación de herencia entre la clase Enemigo y las clases Marciano y Venusiano. La clase Enemigo podría tener los siguientes métodos: mover(), atacar() y disparar(). Estos métodos también serán heredados por la clase hijas mencionadas. Las clases hijas tienen dos opciones, si la implementación de los métodos anteriores que realiza la clase padre es la adecuada, los heredarán y utilizarán sin más, pero si las clases hijas quieren realizar modificaciones o ampliaciones sobre los métodos heredados deberán implementarlos y por lo tanto sobrescribirlos aportando un comportamiento diferente. 19 Programación en Java © Grupo EIDOS Las clases Marciano y Venusiano pueden además de sobrescribir los métodos heredados de la clase Enemigo, aportar nuevos métodos y nuevos atributos. La clase Marciano añade el atributo Visible, es decir, nuestro amigo tiene la capacidad de hacerse invisible, y la clase Venusiano añade el atributo NumeroCabezas. La clase Terricola representa al héroe de nuestro juego y tiene los atributos: NumeroVidas y Municion, ambos requieren poca explicación. Esta clase implementa el método disparar(), que recibe como parámetro un objeto de la clase LanzaCohetes, la relación entre ambas clases es una relación de uso. La clase LanzaCohetes que representa el arma que va a utilizar la clase Terricola y tienen el atributo NumeroCohetes, y posee el método lanzarCohete(). Podríamos entrar en más detalles e ir afinando más en la identificación de objetos, podríamos definir también la clase Cohete, que representaría a los cohetes lanzados por una instancia de la clase LanzaCohetes. Pero no vamos a entrar en más detalles y vamos a dejar de definir más clases, quedándonos con las vistas hasta ahora. En la Figura 2 se trata de mostrar un esquema de todas las clases que implementan el problema propuesto y la relación existente entre cada una de ellas. Cada clase se representa por una circunferencia y las relaciones existentes mediante flechas. Figura 2 20 Introducción al lenguaje Java Introducción En este capítulo vamos a comentar las características principales del lenguaje Java, también comentaremos algunos conceptos interesantes que aporta el lenguaje. Como su nombre indica este capítulo es introductorio, por lo tanto algunos de los conceptos los explicaremos de forma breve y en los sucesivos capítulos profundizaremos en los temas más interesantes. Por lo tanto en este capítulo todavía no vamos a ver ni una sola línea de código en Java, antes debemos tener claro una serie de puntos acerca del lenguaje. También comentaremos tres herramientas de desarrollo que podemos utilizar para realizar nuestros desarrollos en Java, se trata de las siguientes: • JDK (Java Development Kit): herramienta ofrecida por Sun MicroSystems, implementa la versión última y oficial de Java. • Microsoft Visual J++ 6.0: incluida dentro de Visual Studio 6, ofrece la versión 1.1 de Java. • Borland JBuilder 3.5: potente entorno de desarrollo que implementa la versión de Java perteneciente a Java 2. Que el lector no tema por las distintas versiones de Java, en el apartado correspondiente se aclaran las distintas versiones existentes. Programación en Java © Grupo EIDOS Breve historia del lenguaje Los orígenes de Java se remontan al año 1990, cuando un equipo de la compañía Sun Microsystems investigaba, bajo la dirección del ingeniero James Gosling, en el diseño y elaboración de software para pequeños dispositivos electrónicos de consumo. En un primer momento se pensó en la utilización de lenguajes de programación como C o C++, pero para poder compilar un programa en estos lenguajes es preciso adaptarlo a las características de la plataforma en la que debe funcionar, esta situación constituía un gran inconveniente para las compañías dedicadas a la construcción de dispositivos electrónicos, pues cada pocas semanas aparecen en el mercado versiones más potentes y baratas de los chips utilizados, y por lo tanto, el software que se había diseñado para un chip determinado debía modificarse y adaptarse para explotar las características de los chips de reciente aparición. Se hace patente la necesidad de introducir un nuevo lenguaje de programación, que permita desarrollar programas independientes del tipo de plataforma. Los dispositivos que se pretenden fabricar son calculadoras, relojes, equipos de música, cafeteras, etc.…, que no tienen una gran capacidad computacional, por lo que el nuevo lenguaje debe ser capaz de generar programas pequeños y rápidos, además de ser fiables y robustos. La primera versión de este nuevo lenguaje se denominó Oak (roble), pero más tarde Sun descubrió que este nombre estaba ya registrado, y lo tuvieron que cambiar, el nuevo nombre fue Java (una de las versiones sobre el significado del nombre es que Java es un término popularmente empleado en California para designar café de buena calidad). A comienzos de 1993 aparecieron nuevas herramientas gráficas para facilitar la comunicación y navegación por Internet, se concibió la idea de aplicar técnicas de documentos con enlaces de tipo hipertextual para facilitar la navegación por Internet, y se desarrolló el primer browser (navegador Web o visualizador) de lo que se comenzó a denominar World Wide Web. Esta herramienta era denominada Mosaic. El equipo de James Gosling se planteó como objetivo la utilización de Java como lenguaje en el que escribir aplicaciones que pudiesen funcionar a través de Internet. Como resultado de su trabajo se desarrolló un nuevo navegador completamente escrito en Java, llamado HotJava. Este navegador permitía la integración de pequeñas aplicaciones en el interior de las páginas Web. El desarrollo de HotJava hizo patente que las características de Java se adaptan perfectamente a las peculiaridades de Internet. A partir de su primera y sencilla versión Java ha ido creciendo progresiva y espectacularmente para pasar a ofrecer un potente y complejo lenguaje con el que se pueden abarcar una gran cantidad de campos. Descripción del lenguaje En este apartado vamos a resaltar las características principales del lenguaje Java y comentaremos una serie de términos y conceptos que se hacen indispensables para comprender lo que nos ofrece el lenguaje de programación que nos ocupa. Java es un lenguaje de programación orientado a objetos de propósito general, de ahí la necesidad de estudiar el capítulo anterior dedicado íntegramente a la Programación Orientada a Objetos. Según indican desde Sun Microsystems, Java es un lenguaje creado para realizar una programación en Internet rápida y fácil, rápida y fácil si ya poseemos conocimientos previos de C++ y de programación 22 © Grupo EIDOS 2. Introducción al lenguaje Java orientada a objetos. Por lo tanto si el lector ya ha programado en C++ la curva de aprendizaje del lenguaje Java se suavizará considerablemente. Aunque no se debe considerar a Java como una herramienta exclusiva y únicamente para la programación en Internet, ya que su uso, lejos de restringirse a este campo, puede y debe extenderse a problemas y situaciones de todo tipo, por lo tanto para evitar confusiones el lenguaje Java se podría definir mejor de la siguiente forma: Java es un lenguaje de programación orientado a objetos, de propósito general que presenta características especiales que lo hacen idóneo para su uso en Internet. Una de estas características son los applets. Un applet es un programa dinámico e interactivo que se puede ejecutar dentro de una página Web que se carga en un navegador Web, y otra característica para la utilización de Java en Internet son los servlets. Un servlets es una aplicación Java que se ejecuta sobre un servidor y que atiende una serie de peticiones realizadas desde un cliente que será un navegador Web. A diferencia de los applets los servlets no presentan interfaz gráfico. Java toma prestadas características y sintaxis de diferentes lenguajes de programación. La sintaxis básica de Java está sacada del lenguaje C/C++ aunque al contrario de estos lenguajes Java es un lenguaje fuertemente tipado. De Smalltalk Java toma conceptos como el recolector de basura (garbage collector, concepto que se explicará más adelante) y un sólido modelo de orientación a objetos, como iremos comprobando a lo largo del presente curso. De Objective-C, Java toma el concepto de interfaz, en el capítulo correspondiente veremos como define e implementa Java los interfaces. El mecanismo de herencia que posee Java, se denomina herencia simple, esto quiere decir que cada clase Java sólo puede tener una superclase o clase padre. En otros lenguajes de programación como C++, las clases pueden heredar de diferentes superclases, esto se denomina herencia múltiple, la cual no emplea Java. Pero Java posee un mecanismo que le permite simular la herencia múltiple, este mecanismo se consigue a través de los interfaces. Como ya adelantábamos, el concepto de interfaz lo toma Java del lenguaje Objective-C. Un interfaz es una colección de nombres de métodos sin definiciones reales que indican que una clase tiene un conjunto de comportamientos, además de los que la clase hereda de sus superclases. Por lo tanto un interfaz es una lista de métodos sin ninguna implementación, la palabra reservada implements es utilizada en la declaración de una clase para indicar que implementa los métodos de un interfaz determinado. Más tarde, en el curso, retomaremos el concepto de interfaz. Al ser un lenguaje de Programación Orientada a Objetos, la unidad básica dentro de la programación en Java va a ser la clase y el objeto. Java está compuesto por un gran número de clases que se agrupan y clasifican en paquetes, en el capítulo dedicado a la POO con Java veremos como se estructura la jerarquía de clases que presenta el lenguaje. Pero a pesar de estar compuesto de clases, en Java nos encontramos también con una serie de tipos de datos predefinidos similares a C o C++, estos tipos, denominados tipos primitivos son: int, byte, short, char, long, boolean, float y double. Por lo tanto en Java podremos definir variables de estos tipos de datos, en este caso se empleará la denominación variable en lugar de objeto, ya que los tipos primitivos no son clases. En el próximo capítulo volveremos a retomar los tipos primitivos. Java presenta una completa y compleja jerarquía de clases, esto le hace un lenguaje muy potente, ya que para cada tarea a realizar existe una clase determinada que se encuentra especializada para realizar una función específica. Se puede considerar que por un lado tenemos el lenguaje Java, que es con el que escribimos nuestras clases y compilamos, y por otro se encuentra la Máquina Virtual de Java, JVM (Java Virtual 23 Programación en Java © Grupo EIDOS Machine). Para garantizar que los programas son independientes de la plataforma (capacidad del programa de trasladarse con facilidad de un sistema computacional a otro) hay una sola arquitectura a la que todos los programas Java son compilados, es decir, cuando se compila un programa Java en una plataforma Windows/Intel, se obtiene la misma salida compilada que en un sistema Macintosh o Unix. El compilador compila no a una plataforma determinada, sino a una plataforma abstracta llamada Máquina Virtual de Java. La especificación de la Máquina Virtual de Java define la JVM como: una máquina imaginaria que se implementa emulando por software una máquina real. El código para la Máquina Virtual de Java se almacena en ficheros .class, cada uno de los cuales contiene al menos el código de una clase pública, en el capítulo dedicado a la POO con Java comentaremos en detalle la visibilidad de las clases (públicas o privadas). Por lo tanto, cuando se escribe una aplicación Java o un applet Java (después veremos estos dos tipos de programas que se pueden construir en Java), se está escribiendo un programa diseñado para ejecutarse en la Máquina Virtual de Java. La Máquina Virtual de Java requiere un código binario especial para ejecutar los programas Java, este código no debe tener instrucciones relacionadas específicamente con la plataforma. Los archivos binarios Java, que se obtienen al compilar el código fuente, son independientes de la plataforma y pueden ejecutarse en múltiples plataformas sin necesidad de volver a compilar el fuente. Los archivos binarios Java se encuentran en una forma especial llamada bytecode, que son un conjunto de instrucciones muy parecidas al código máquina, pero que no son específicas para ningún procesador. El compilador Java toma el programa Java y en lugar de generar código máquina específico para los archivos fuente, genera un bytecode. Para ejecutar un programa Java, se debe ejecutar un programa llamado intérprete de bytecode, el cual a su vez ejecuta el programa Java deseado. En el caso de los applets el intérprete se encuentra integrado en el navegador Web con capacidad para Java y es ejecutado automáticamente. El concepto de independencia de la plataforma es ilustrado en la Figura 3. Figura 3 24 © Grupo EIDOS 2. Introducción al lenguaje Java La desventaja de utilizar los bytecodes es la velocidad, estos programas tienen una menor velocidad de ejecución, ya que previamente a ejecutarse sobre el sistema, deben ser procesados por el intérprete. Aunque según van apareciendo nuevas versiones del lenguaje Java la velocidad de ejecución se va mejorando, se alcanzará la máxima velocidad y eficacia cuando aparezcan los chips Java. Se trata de una versión hardware del intérprete Java presente en los programas navegadores con capacidad para este lenguaje, es decir, es una implementación hardware de la Máquina Virtual de Java. Estos chips serán los encargados de traducir el código Java sobre la marcha, por lo tanto no será necesaria la etapa intermedia de interpretación de los bytecodes, como se veía en la figura, con lo que se aumentará de forma considerable la velocidad de ejecución. En un principio Sun MicroSystems tiene pensado comercializar estos chips en una especie de tarjeta, que se insertará en un slot del ordenador. Una polémica entorno a Java se produjo cuando Microsoft lanzó su versión del lenguaje, a través de su herramienta de desarrollo Visual J++. Desde esta herramienta corremos el peligro de generar código Java que sí sea dependiente de la plataforma, en este caso de la plataforma Windows, y si estamos utilizando el lenguaje Java es porque nos interesa su característica de independencia de la plataforma, por lo tanto hay que ser cuidadosos en este aspecto. Java incluye características de seguridad para reforzar su empleo en Internet. Un problema de seguridad potencial tiene que ver con los applets de Java, que son código ejecutable que opera en la máquina local del usuario que se conecta a la página Web en la que se encuentran los applets. Java emplea verificación de código y acceso limitado al sistema de archivos para garantizar que el código no dañe nada en la máquina local. En el tema y apartados correspondientes comentaremos las restricciones de seguridad que presentan los applets. Debido a que en Java no existen punteros, la asignación y liberación de recursos tiene algunas particularidades. El recolector de basura (garbage collector) es otro de los aciertos del lenguaje Java, se trata de un hilo de ejecución (thread) que se encarga de rastrear en tiempo de ejecución cada objeto que se ha creado, advierte cuándo desaparece la última referencia a él y libera el objeto por el programador. Es decir, en Java la asignación de memoria es dinámica y automática, cuando se crea un nuevo objeto se destina la cantidad de memoria necesaria, y una vez que se ha dejado de utilizar ese objeto el recolector de basura lo localiza y se apropia de la memoria que empleaba el objeto. Por lo tanto no es necesario realizar ninguna liberación explícita de memoria. Programas en Java: applets y aplicaciones Los programas Java comprenden dos grupos principales: applets y aplicaciones. Un applet (denominado por algunos autores miniaplicación) es un programa dinámico e interactivo que se ejecuta dentro de una página Web, desplegada por un navegador Web con capacidad para Java como puede ser Navigator de Netscape o el Internet Explorer de Microsoft, por lo tanto los applets para ejecutarse dependen de un navegador Web habilitado para Java. Los applets es uno de los principales motivos que han hecho al lenguaje Java tan popular, sin embargo no son tan potentes como las aplicaciones. Las aplicaciones Java son programas más generales que los applets. No requieren un navegador para ejecutarse sólo necesitan de un intérprete de Java para la plataforma en la que se ejecutarán, y pueden emplearse para realizar todo tipo de aplicaciones posibles. Una de las primeras aplicaciones Java que se realizaron fue el navegador Web HotJava. Una aplicación Java consiste en una o más clases, lo único que se necesita para ejecutar una aplicación es tener una clase que funcione como punto de arranque para el resto del programa. 25 Programación en Java © Grupo EIDOS Para ejecutar una aplicación Java en un sistema es necesario ejecutar primero la Máquina Virtual, es decir, el intérprete de Java, que permitirá la ejecución de la aplicación, ya que traduce los bytecodes en código nativo. Este paso intermedio a la ejecución de la aplicación Java puede ser molesto, por ello algunos constructores de software han anunciado que pronto incorporarán la Máquina Virtual de Java a sus sistemas operativos, de esta forma las aplicaciones Java se ejecutarán de una manera muy similar a las aplicaciones de otros lenguajes (C, C++, Pascal, etc.…), todas las llamadas a la Máquina Virtual serán transparentes al usuario y serán manejadas por el sistema operativo. Esto es similar a lo que ocurre cuando un navegador carga un applet y lo ejecuta en la Máquina Virtual de forma automática. Similitudes y diferencias entre Java y C++ Se debe indicar que existen pocas diferencias entre Java y C++, al menos en cuanto al aspecto del código fuente al menos. Las diferencias, gratas en algunos puntos e ingratas en otros, comenzamos a avistarlas cuando profundizamos en el lenguaje. Podremos rentabilizar la inversión de tiempo y esfuerzo invertidos en aprender C++ y controlar la programación orientada a objetos. Todos estos conocimientos serán aprovechables por el programador en Java, sin embargo no es cierto que para conocer Java se debe estudiar previamente C/C++, Java es un lenguaje por sí mismo. Las cadenas de Java no son punteros a cadenas de caracteres como en C/C++, ya que Java no soporta punteros. Este modo de concebir las cadenas libera al programador de la engorrosa manipulación de cadenas de C/C++. Para manejar cadenas en una aplicación Java disponemos del tipo de dato String, que no es más que una clase definida en el sistema. Los archivos .class funcionan en cualquier máquina, ya que el intérprete encargado de ejecutarlos está codificado de forma nativa en el sistema receptor de la aplicación. Para lograr lo anteriormente expuesto Java es interpretado, por lo que la velocidad de los ejecutables (que realmente no lo son) es menor que la que lograríamos con el mismo código en C++ (de 10 a 20 veces mayor). Esto no debe preocuparnos, ya que diferentes tecnologías consiguen disminuir esta diferencia llegado, en algunos casos, a igualar la velocidad de C++. De entre estas tecnologías destaca la compilación Just-in-time, que permite la conversión en tiempo de ejecución a instrucciones nativas de la máquina en la que se estén ejecutando los bytecodes. Dispondremos de una inmensa jerarquía de clases diseñadas para la creación de aplicaciones en la Web. Jerarquía probada y en constante crecimiento. Sun Microsystems facilita gratuitamente una copia del JDK del producto (acorde al sistema operativo bajo el que funcionará), con todos los elementos necesarios para crear aplicaciones con Java. La distribución del intérprete no es necesaria si estamos distribuyendo una miniaplicación (applet) enlazada a una página Web, ya que el navegador correspondiente sería capaz de realizar una compilación Just-in-Time del applet. En caso de no ser así y sí necesitar el intérprete, éste sería de libre distribución. Otra diferencia notable es que Java no soporta la herencia múltiple, es decir, no puede heredar de varias clases, sin embargo C++ si soporta heredar de varias clases padre. Se puede decir que Java ofrece una Programación Orientada a Objetos simplificada en este aspecto. Versiones del lenguaje La última versión del lenguaje Java es lo que se denomina Plataforma Java 2 (Java 2 Platform) que se corresponde con las dos últimas versiones de la herramienta de desarrollo de Sun Microsystems, es decir, con el JDK (Java Development Kit) 1.2 y el JDK 1.3, este último de muy reciente aparición. Anteriormente había una correspondencia entre la versión del lenguaje Java y la versión de la herramienta JDK, así la versión 1.1 de Java se correspondía con la versión 1.1 del JDK. 26 © Grupo EIDOS 2. Introducción al lenguaje Java Ahora vamos a comentar las correspondencias entre las versiones de Java y las distintas herramientas de desarrollo de las que vamos a tratar en este curso. Aunque desde este momento se debe aclarar que este curso no está enfocado a la utilización de ninguna herramienta de desarrollo de terceros como Borland JBuilder o Microsoft Visual J++, sino que se trata la versión estándar del lenguaje, es decir, la que ofrece Sun con su JDK, y en este caso se trata del JDK 1.3, aunque todo lo que vamos a ver es aplicable también al JDK 1.2. La herramienta de desarrollo de Java de la compañía Microsoft, Visual J++ 6.0, soporta la versión 1.1 del lenguaje. Esta herramienta puede interesarnos si lo que vamos a utilizar es la versión 1.1 de Java, esto no es ninguna contradicción, ya que la propia Sun sigue ofreciendo una versión del JDK para la versión 1.1 de Java, se trata del JDK 1.1.8. La versión 1.1 de Java sigue estando vigente debido a la rápida evolución que está sufriendo el lenguaje. Algunos de los ejemplos ofrecidos en el curso no funcionarán dentro de Visual J++ ya que se corresponderán con nuevas características de Java, de todas formas esta situación se le indicará al lector cuando se produzca. La herramienta de desarrollo Borland JBuilder 3.5, de la compañía Inprise, implementa la Plataforma Java 2, equiparándose a la herramienta de Sun JDK 1.2. Si el lector quiere utilizar la última versión de Java es recomendable que utilice JBuilder 3.5. Además en diversos estudios comparativos entra distintas herramientas de desarrollo del lenguaje Java, Borland JBulider ha sido casi siempre la mejor considerada. El lenguaje Java todavía no ha tenido una versión definitiva ni estable, cosa que supone un grave inconveniente para los sufridos desarrolladores, que tenemos que estar actualizándonos continuamente. Según se ha comunicado desde Sun Microsystems, la versión Java 2 Platform es la versión definitiva del lenguaje, esperemos que esto sea cierto, ya que desde mediados/finales del año 1996 se han sucedido un gran número de versiones, y el seguimiento y aprendizaje del lenguaje resulta verdaderamente agotador y desconcertante. Entornos de desarrollo Como ya hemos dicho a lo largo de este capítulo vamos a comentar en el curso tres herramientas de desarrollo distintas para utilizar en la realización de nuestros programas en Java, aunque siempre utilizando el estándar del lenguaje Java. La herramienta de desarrollo oficial de Java es el JDK (Java Development Kit). La herramienta JDK la podemos obtener de forma gratuita desde el sitio Web que posee Sun Microsystems dedicado por completo al lenguaje Java, http://java.sun.com. Las versiones actuales de este producto son la 1.1.8, la 1.2 y la 1.3, correspondiéndose la primera con la versión 1.1 de Java y las dos últimas con la última versión denominada Java 2 Platform. Debido a que muchos fabricantes todavía no soportan la versión nueva de Java y también debido a que la nueva versión es bastante reciente, coexisten las dos versiones del lenguaje. Sun Microsystems no nos lo pone fácil con las nomenclaturas de versiones y de productos, y si acudimos a su sitio Web para descargar el JDK, vemos que no aparece la versión 1.2 ni la 1.3. Esto es debido a que el JDK se encuentra incluido en lo que se denomina Java 2 SDK (Software Development Kit). De esta forma si deseamos la versión 1.2 del JDK veremos descargar el producto Java 2 SDK Standard Edition v 1.2, y si lo que deseamos es tener el JDK 1.3 debemos conseguir el Java 2 SDK Standard Edition v 1.3. A los efectos del presente curso las dos versiones del Java 2 SDK son válidas. 27 Programación en Java © Grupo EIDOS El código que generemos en los ejemplos y las características del lenguaje que vamos a utilizar a lo largo de todo el curso se corresponden todas con el JDK de Sun Microsystems, es decir, vamos a utilizar Java estándar. Por lo tanto el único software completamente necesario para seguir el curso satisfactoriamente es el Java 2 SDK Standard Edition. El entorno de desarrollo ofrecido por Sun es bastante pobre, ya que se basa en modo comando, es decir, no ofrece un entorno gráfico, el código lo escribiremos con un editor de texto y luego lo podremos compilar, ejecutar, depurar, etc., con una serie de programas incluidos en el JDK, pero siempre desde la línea de comandos. Pero para mostrar el panorama actual de herramientas de desarrollo de Java se ha decido incluir dos entornos de desarrollo más. Además estos entornos son más amables de utilizar que el ofrecido por Sun, ofreciendo cada uno de ellos un interfaz gráfico que permite que el desarrollo de aplicaciones Java sea más sencillo y rápido. Una de estas herramientas de desarrollo que vamos a comentar en este curso es, como ya hemos adelantado, Microsoft Visual J++ 6.0. Esta herramienta forma parte de la suite de herramientas de Microsoft Visual Studio 6.0. Microsoft Visual J++ 6.0 ofrece un entorno de desarrollo amigable. Sin embargo, como ya habíamos comentado anteriormente, el JDK ofrece un entorno de desarrollo bastante árido, el editor que se propone es el Edit de MS-DOS o cualquier otro editor de texto que no genere un formato para el texto. El entorno de compilación y depuración es bastante pobre y además se basa completamente en la línea de comandos de MS-DOS. El mayor inconveniente que presenta Visual J++ 6.0 es que en algunos casos puede generar código Java que no pertenece al estándar del lenguaje. Pero en este curso se va a tratar el lenguaje Java estándar no la implementación particular de Microsoft. Se podría decir que vamos a ver el lenguaje Java 100% de Sun. Aunque no utilicemos la implementación que realiza Microsoft del lenguaje, nos podemos beneficiar del entorno de desarrollo que ofrece Microsoft, que como ya hemos dicho es mucho más fácil de utilizar y más amigable que el ofrecido por Sun. A lo largo del curso comentaremos en detalle Visual J++ 6.0 y cómo debemos utilizarlo para generar código Java completamente estándar. Pero el mayor inconveniente que presenta Visual J++ 6.0 es que recoge únicamente hasta l versión 1.1 del lenguaje Java, esto en algunos casos puede ser suficiente, pero veremos que en otros no, sobre todo si queremos utilizar las nuevas características de Java 2, como pueden ser la utilización de componentes avanzados (Swing) para construir interfaces de usuario o el acceso a datos mejorado que ofrece a través de JDBC 2.0. Otro punto en contra de Visual J++ es que ofrece un entorno gráfico, pero no es todo lo gráfico que se podría desear, es decir, no permite construir interfaces de usuario al modo de otras herramientas como pueden ser Visual Basic o Delphi, dónde podemos arrastran controles o elementos del interfaz de usuario y situarlos en el lugar deseado, sino que todo lo debemos hacer a través de programación sin que Visual J++ genera una única línea de código fuente. Podemos decir que el segundo entorno de desarrollo utilizado en este curso Borland JBuilder 3.5 soluciona las dos deficiencias que plantea Visual J++. Por un lado ofrece la versión del lenguaje correspondiente a la Java 2 Platform(Plataforma Java 2) y un entorno de desarrollo que permite la creación visual de interfaces de usuario con generación de código Java incluida. 28 © Grupo EIDOS 2. Introducción al lenguaje Java De todas formas en los sucesivos capítulos veremos con más detenimiento cada uno de estos entornos de desarrollo. Características de la plataforma Java 2 Este apartado muestra de forma general las características que incluye el lenguaje Java en su última versión y que han ido evolucionando desde la versión 1.0. El contenido de este apartado es a modo informativo, el lector no tiene porque comprender los términos que se van a utilizar en este apartado. Este apartado puede ser útil a lectores que ya conozcan algunas de las versiones anteriores del lenguaje y también nos sirve para observar las nuevas características que ha ido implementando el lenguaje. • Internacionalización: permite el desarrollo de applets localizables, es decir, un mecanismo de localización sensible a la hora y fecha locales. Se incluye también la utilización de caracteres Unicode. Unicode tiene la capacidad de representar unos 65.000 caracteres, un número bastante amplio para incluir los caracteres de la mayoría de los lenguajes hablados hoy en día. • Seguridad y firma de applets: el API (aquí entendemos como API un conjunto de clases más o menos complejo que cumplen una funcionalidad común) de seguridad de Java está diseñado para permitir a los desarrolladores incorporar funcionalidades de seguridad a sus aplicaciones. Contiene APIs para firma digital y tratamiento de mensajes. Existen interfaces para la gestión de claves, tratamiento de certificados y control de accesos. También posee APIs específicos para el mantenimiento de certificados X.509 v3 y para otros formatos de certificado. Además se ofrecen herramientas para firmar ficheros JAR (Java Archive). • Ampliaciones del AWT: el AWT (Abstract Window Toolkit) es un API encargado de construir el GUI (Graphical User Interface, interfaz de usuario gráfico). El AWT de la versión 1.0 fue diseñado para construir sencillos interfaces de usuario por lo tanto se hizo necesario el ampliar el AWT en la versión 1.1. Estas ampliaciones tratan de resolver las principales deficiencias del AWT , además, representan un comienzo en la creación de una infraestructura más rica para el desarrollo de complicados interfaces de usuario, esto incluye: APIs para la impresión, componentes scroll, menúes popup, portapapeles (copiar/pegar), cursores para cada componente, un modelo de eventos nuevo basado en la delegación de eventos (Delegation Event Model), ampliaciones en el tratamiento de imágenes y gráficos, y un tratamiento de fuentes más flexible de cara a la característica de internacionalización. • JavaBeans: inicialmente se puede definir un JavaBean como un componente software reutilizable, que puede ser manipulado visualmente en una herramienta de desarrollo. Consta de una colección de una o más clases Java que suelen encontrarse en un único fichero JAR (Java Archive). Un JavaBean sirve como un objeto independiente y reusable. El API de los JavaBeans define un modelo de componentes software para Java. Este modelo, desarrollado de forma coordinada entre las compañías Sun, Borland Inprise y otras, es una especificación de como codificar estos componentes para que puedan ser utilizados en diferentes entornos de programación. Un ejemplo de la utilización de los JavaBeans la ofrece la herramienta de desarrollo de Java JBuilder, de la compañía Borland Inprise (también ofrece una herramienta llamada BeansExpress que permite una fácil construcción de JavaBeans). Esta herramienta posee un gran número de JavaBeans como pueden ser los diferentes componentes para la construcción interfaces de usuario (botones, listas, paneles, ventanas, botones de selección, barras de menú, etc.…). Al igual que estos componentes son utilizados por el entorno JBuilder, se pueden incluir dentro de cualquier herramienta de desarrollo que respete y reconozca las características de los JavaBeans. Si creamos un componente con las 29 Programación en Java © Grupo EIDOS especificaciones de los JavaBeans, ocultando los detalles de implementación y solamente mostrando las propiedades, métodos y eventos públicos conseguiremos las siguientes ventajas: 1. Pueden ser utilizados por otros desarrolladores usando un interfaz estándar. 2. Se pueden situar dentro de las tablas de componentes de diferentes herramientas de desarrollo. 3. Se pueden comercializar por separado como si se tratara de un producto. 4. Pueden ser actualizado con un impacto mínimo sobre los sistemas en los que se encuentre. 5. Es un componente escrito en Java puro, por lo tanto es independiente de la plataforma. 30 • Ficheros JAR (Java ARchive): este formato de fichero presenta muchos ficheros dentro de uno, y además permite la compresión de los mismos. Varios applets y sus componentes requeridos (ficheros .class, imágenes y sonidos) pueden encontrarse dentro de un fichero JAR y por lo tanto cargados por un navegador en una sola petición HTTP. Este formato de ficheros es parecido a los ficheros Cabinet de Microsoft. • Mejoras en Swing: Swing es el otro API existente para crear interfaces de usuario gráficos, ofrece muchos más componentes que el API AWT. Además los componentes Swing permiten modificar su aspecto y comportamiento. • Ampliaciones de Entrada/Salida: el paquete java.io ha sido ampliado con flujos de caracteres, que son parecidos a los flujos de bytes excepto en que contienen caracteres Unicode de 16 bits en lugar de 8 bits. Los flujos de caracteres simplifican la escritura de programas que no dependen de una codificación de caracteres determinada, y son más fácil de internacionalizar. • El Paquete java.math: este paquete ofrece dos nuevas clases BigInteger y BigDecimal, para el tratamiento de operaciones numéricas. Un paquete, que definiremos en detalle más adelante, podemos decir que es un conjunto de clases relacionadas entre sí. Los paquetes es una manera de organizar y clasificar las clases que posee el lenguaje Java. • JDBC 2.0: JDBC es un API para ejecutar sentencias SQL (Structured Query Language), que ofrece un interfaz estándar para el acceso a bases de datos. Ofrece un acceso uniforme a un amplio número de bases de datos. El código de este API está completamente escrito en Java, de hecho se encuentra en el paquete java.sql, por lo tanto ofrece también independencia de la plataforma. Está basado y es muy similar a ODBC (Open Database Connectivity). Usando JDBC es muy sencillo enviar sentencias SQL a cualquier base de datos relacional, gracias al API de JDBC no es necesario escribir un programa que acceda a una base de datos Sybase, otro programa que acceda a una base de datos Oracle u otro que acceda a una base de datos Informix, el mismo programa servirá para ejecutar las sentencias SQL sobre todas estas bases de datos. Básicamente JDBC permite: establecer una conexión con una base de datos, enviar sentencias SQL y procesar los resultados. En la versión 2.0 de JDBC se ha mejorado el acceso a datos haciéndolo más potente a través de la creación de diferentes tipos de cursores para acceder a los datos. • JFC (Java Foundation Classes): con este nombre se agrupan un gran número de clases especializadas en la construcción de interfaces de usuario. Dentro de JFC se encuentran las APIs ya mencionadas para la construcción de interfaces de usuario, es decir, Swing y AWT. Además incluye características de accesibilidad que permiten utilizar tecnologías tales como lectores de pantalla o dispositivos Braille, utilización de gráficos en 2 dimensiones, soporte © Grupo EIDOS 2. Introducción al lenguaje Java para arrastrar y soltar (Drag and Drop), posibilidad de modificar el aspecto y comportamiento de los componentes gráficos (pluggable Look and Feel), etc. • Servlets: los servlets son aplicaciones Java que se ejecutan en servidores Web y que permiten construir páginas Web de forma dinámica. También permiten obtener información de los formularios enviados por los clientes. Son una tecnología que realiza funciones similares a los scripts CGI (Common Gateway Interface) y a las páginas ASP (Active Server Pages). 31 Sintaxis del lenguaje Java Introducción Este capítulo no podría faltar tampoco en nuestro curso de Java, este es el típico capítulo que tienen todos los cursos o libros que traten un lenguaje de programación. En este capítulo vamos a tratar la sintaxis del lenguaje Java. Los alumnos que ya conozcan C o C++ verán que la sintaxis es muy similar, de todas formas recomiendo a todos la lectura de este capítulo. Este capítulo es necesario para saber como escribir el código Java, en el próximo capítulo veremos como implementar los mecanismos de la POO a través de Java. Veremos que muchos de los elementos del lenguaje Java son comunes al resto de los lenguajes existentes. Identificadores Los identificadores son literales que representan nombres únicos para ser asignados a objetos, variables, clases y métodos, de este modo el compilador puede identificarlos unívocamente. Estos nombres sirven al programador, si éste les da sentido. Por ejemplo, si vamos a implementar una clase para manejo del fichero de clientes, sería recomendable que la misma se llamara algo así como ClientManager, o ManejadordeCliente, no tendría sentido, aunque es totalmente válido llamarla Aj23. Programación en Java © Grupo EIDOS Existen de todos modos algunas limitaciones a la hora de dar nombres a los identificadores, que son las mismas que en la mayoría de los lenguajes. Los identificadores tienen que comenzar por una letra, un subrayado o un símbolo de dólar, tras este carácter, se pueden utilizar combinaciones de los ya mencionados y números del 0 al 9. No pueden utilizarse, como es lógico, las palabras clave del lenguaje como identificadores (que mostraremos más adelante). Aunque estos identificadores pueden ser cualquier longitud, para el compilador sólo son significativos los primeros 32 caracteres. Se debe tener cuidado al escribir los identificadores, porque Java distingue entre mayúsculas y minúsculas, de este modo, Nombre y nombre son dos variables diferentes. Java permite el uso de cualquier carácter del código Unicode para definir identificadores, de esta forma, el identificador Año es totalmente válido en Java. A la hora de nombrar los identificadores, Java presenta una serie de normas de estilo de uso generalizado, lo que significa que se pueden adoptar o rechazar sin que ello influya en lo más mínimo en el comportamiento del código, pero se debe advertir que la mayor parte del código Java existente utiliza estas normas. En Java se utiliza el word-mixing (EsteEsUnEjemploDeWordMixing), la primera letra se escribe en mayúscula (ManejadorDeClientes, por ejemplo) para las clases, y no se utilizan los signos subrayado ni dólar como primer carácter de un identificador. Esta última norma, tiene su explicación: las librerías de C que se utilizan en Java, suelen utilizar los caracteres de subrayado y dólar como primeros caracteres de sus identificadores, por lo que evitándolos, nos evitamos problemas de conflictos de duplicidad. Otro dato de interés a este respecto es que a las clases no se les pone ninguna identificación delante, ya que en Java todo son clases. Los nombres de variables, objetos y métodos en Java, aunque se utiliza word-mixing, comienzan con una minúscula, por ejemplo: toUpper(). Aun cuando esta es una sugerencia y no una norma, es conveniente seguirla, ya que así está notada toda la jerarquía de clases de Java, y al distinguir el lenguaje entre mayúsculas y minúsculas, ni podemos escribir estos nombres de otro modo ni debemos dar a nuestros identificadores de variables y métodos nombres con la primera letra en mayúsculas (para mantener la consistencia con el estilo de los creadores del Java). Sin embargo, en el caso de las variables (aquí y en adelante el termino variable se refiere al nombre de los objetos o de las variables de los tipos primitivos de Java), a muchos programadores nos gusta notar el tipo al que pertenecen, clarificando así el código. De este modo, si una variables es de tipo cadena (string) al nombre le precedería la letra "s". Por ejemplo, sNombre es el identificador de una variable que contiene una cadena. En la Tabla 1, se ofrece una sugerencia para los prefijos de nombres de variables atendiendo a su tipo. Por supuesto, que esto es sólo una indicación y no una norma que se tenga que seguir. Identificadores de variables 34 Prefijo Tipo Ejemplo a array aElementos b boolean bCancelar © Grupo EIDOS 3. Sintaxis del lenguaje Java c char cInicial d double dDistancia f float fInterés i integer iColumna l long lCantidad s string sNombre Tabla 1 Para los identificadores de variables que se refieren a objetos, utilizaremos tres letras como prefijo, intentando que estas tres letras indiquen de la forma más clara posible el nombre de la clase a la que el objeto pertenece. Por ejemplo, si instanciamos un objeto de la clase Frame, el prefijo podría ser "frm", seguido del nombre que queramos darle a la variable, por ejemplo, frmClientes. Palabras clave Las palabras clave son identificadores reservados por el lenguaje. Aunque el alumno sea programador de C o C++, le sugerimos que lea con detenimiento la siguiente tabla, ya que Java tiene un número mayor de palabras reservadas que C y C++. Palabras Clave abstract continue for new switch boolean default goto null synchronized break do if package this byte double implements private threadsafe byvalue else import protected throw case extends instanceof public transient catch false int return True char final interface short Try class finally long static Void const float native super while Tabla 2 35 Programación en Java © Grupo EIDOS Además de estas, existen algunas palabras reservadas, ahora no se utilizan pero es posible que se conviertan en palabras clave en un futuro. Son las siguientes: cast, future, generic, goto, inner, operator, outer, rest, var Literales Llamamos literales a aquellos elementos de un programa que no pueden variar. Se les llama también constantes. Los literales pueden ser: números, caracteres y cadenas. Los literales numéricos se dividen en: enteros, coma flotante y booleanos (álgebra de Boole). De hecho, los literales booleanos están incluidos en Java dentro de los numéricos por su herencia del C, ya que en C verdadero es 1 y falso 0. Los literales de caracteres no son ASCII sino Unicode, por ello un carácter en Java necesita 16 bits para ser representado, en lugar de 8 como ocurre en otros lenguajes. Los literales dan en Java lugar a cada uno de los tipos básicos, anteponiéndoles la palabra reservada correspondiente a la hora de declararlos. Enteros Los enteros, dependiendo de la precisión que el número pueda tener se dividen en los siguientes tipos primarios: • byte 8 bits complemento a dos • short 16 bits complemento a dos • int 32 bits complemento a dos • long 64 bits complemento a dos Los enteros se almacenan por defecto internamente como tipo int, que es un valor de 32 bits con signo. Si fuese necesario que un literal entero se almacenase como de tipo long, puede hacerse añadiendo una l o L al final del literal, lo que hace que el número se guarde internamente como un valor de 64 bits. Así, el número 42 se representaría como de tipo int, mientras que 42L, sería un long. Estos, pueden representarse en tres formatos diferentes: decimal, octal y hexadecimal. Dependiendo de la base del sistema numérico en la que se deseen representar. Los enteros en su representación decimal, no necesitan ninguna notación especial. Para hexadecimal (base 16) se antepone el prefijo 0x o 0X, y para base 8 (sistema octal) se antepone un 0 inicial. Así, el número decimal 42 en Java puede representarse como indica la Tabla 3. Decimal Hexadecimal Octal 42 0x2A Tabla 3 36 052 © Grupo EIDOS 3. Sintaxis del lenguaje Java Coma flotante Al igual que los literales enteros pueden ser de cuatro tipos diferentes, los reales en coma flotante – números con parte entera y parte decimal–, pueden ser de dos tipos: float y double. Los primeros se almacenan en 32 bits, mientras que los segundos se almacenan en 64 bits. La única diferencia, es su capacidad de precisión, debida la mayor cantidad de información que necesitan para representarlos. Por defecto, los literales en coma flotante son del tipo double, a diferencia de los enteros, donde no se toma como predeterminado el de mayor precisión. Pero si se desea expresar uno de estos literales en tipo float, basta con añadir una f o F al final del literal (para forzar tipo a double se añade una d o D, pero puesto que este es ya tipo predeterminado, esto sólo sirve en caso de querer dar mayor claridad al código). Ambos (float y double) pueden representarse en notación estándar (3,14159) o científica (314159e-5). • float 32 bits IEEE 754 • double 64 bits IEEE 754 Booleanos El tipo booleano se ha implementado en Java para representar los valores de verdadero y falso, que en C se representan con un literal entero (0 falso y 1 o cualquier cosa que no sea 0, verdadero). Se utiliza para ello dos palabras reservadas: true y false. • true verdadero • false falso Caracteres Los caracteres, que se refieran un único carácter Unicode, se representan al igual que en C, entre comillas simples. También pueden incluirse caracteres de control no imprimibles. En la Tabla 4 se ofrecen estos caracteres. Descripción Representación Barra invertida \\ Continuación \ Retroceso \b Retorno de carro \r Alimentación formularios \f Tabulador horizontal \t 37 Programación en Java © Grupo EIDOS Línea nueva \n Comillas simples \’ Comillas dobles \" Carácter unicode \udddd Carácter octal \ddd Tabla 4 Ejemplos de literales de carácter: a \n \u0042 Cadenas Un literal de cadena es un conjunto de caracteres agrupados. Se representan entre comillas dobles. Java, cada vez que encuentra un literal de tipo cadena, crea una nueva instancia de la clase String, a diferencia del lenguaje C, donde un string es un array de caracteres. En Java podemos también, si así lo deseamos, crear un array de caracteres, pero el lenguaje proporciona el tipo String para estos menesteres. Por ejemplo: "Mi mamá me mima, mi mamá me ama" El literal null El literal null se corresponde con el tipo null, quien tiene un solo valor: la referencia nula. Operadores Los operadores en Java, como se verá tienen también gran influencia del C, de hecho, son casi los mismos, y funcionan casi exactamente igual. Los veremos más con detenimiento en el apartado de expresiones y operadores. Operadores (según tipo) Unarios ++ -- - ~ Binarios + - * / % De bits & | ^ << >> Relacionales < > <= >= = = != Booleanos & | ^ || Aritméticos 38 && >>> ! = = != ?: © Grupo EIDOS 3. Sintaxis del lenguaje Java Cadenas + Asignación = += -= *= /= %= &= |= ^= Tabla 5 Separadores Sólo hay un par de secuencias con otros caracteres que pueden aparecer en el código Java; son los separadores simples, que van a definir la forma y función del código. Los separadores admitidos en Java son los que se detallan en la Tabla 6 Separadores ( ) paréntesis Para contener listas de parámetros en la definición y llamada a métodos. También para definir precedencia en expresiones, contener expresiones para control de flujo y rodear a las conversiones de tipo. { } llaves Para contener los valores de matrices inicializadas automáticamente. También se utiliza para definir un bloque de código, para clases, métodos y ámbitos locales. [ ] corchetes Para declarar tipos matriz así como para referenciar los valores de la matriz. ; punto y coma Separador de sentencias. , coma Separa identificadores consecutivos en una declaración de variables. También se utiliza para encadenar sentencias dentro de una sentencia for. . punto Para separar nombres de paquete de subpaquetes y clases. También se utiliza para separar una variable o método de una variable de referencia. Tabla 6 Comentarios Los comentarios pueden ser considerados como un caso especial dentro de los elementos de la sintaxis del lenguaje, ya que aunque estos sean reconocidos por el compilador, éste los ignora. Las razones son tan obvias, que no vamos a entrar en detalles. En Java existen tres tipos de comentarios (los dos del C y uno distinto). Tipos de Comentarios // comentario Se ignoran todos los caracteres desde // hasta fin de línea 39 Programación en Java © Grupo EIDOS /* comentario Se ignoran todos los caracteres entre /* y */ */ /** comentario Comentario de documentación (funciona como el anterior) */ Tabla 7 Merece especial atención el comentario de documentación. Cuando se emplea este tipo de comentarios, podemos utilizar una herramienta especial llamada JavaDoc (desarrollada por Sun e incluida en la herramienta JDK) para convertir automáticamente el texto que se encuentra abarcado por este tipo de comentarios en documentación de nuestro código fuente. De este modo, la documentación de nuestro código está dentro del propio código, y podemos crearla al tiempo que lo escribimos o modificamos. Esta herramienta además reconoce ciertos tokens dentro de esta área para dar formato al texto de documentación que se va a crear, pero no vamos a profundizar más en ello. Tipos de datos en Java Como ya comentamos al hablar de los literales, cada uno de ellos se refiere a un tipo básico de datos, aunque en Java hay algunos tipos de datos que no tienen representación como literales. Estos son los arrays (o matrices) y las clases. Quizás sea discutible considerar las clases como tipos de datos, pero si retomamos el capítulo dedicado a POO, nos daremos cuenta de que al fin y al cabo, cuando creamos una clase lo que estamos creando no es más que un sistema algebraico: definiendo elementos y operaciones sobre dichos elementos; que es al fin y al cabo, lo que hacemos con los enteros, con los números reales o con las cadenas, es decir con los tipos de datos preestablecidos por el lenguaje. Ya comentábamos que una clase se podía asemejar a un tipo de datos y un objeto de la clase a una variable de ese tipo de datos. Veamos, aunque sea muy someramente, ya que los hemos explicado al hablar de los literales, cada uno de los tipos de datos que Java como lenguaje nos proporciona. Aunque previamente, y para poder poner ejemplos de declaración de tipos, debemos indicar la sintaxis de la sentencia de declaración. Esta es (se debe observar que en Java, al igual que en C, las sentencias finalizan con un punto y coma ";"): <tipo> <idetificador1> [= <valor1>] [, <identificadorN> [= <valorN>]]; o bien, con asignación múltiple: <tipo> <idetificador1>,... ,<identificadorN> = <valor>; Enteros Véase el apartado de Enteros en Literales 40 © Grupo EIDOS 3. Sintaxis del lenguaje Java Tipos de datos Enteros Tipo Tamaño Ejemplo byte 8 bits Byte edad; short 16 bits Short alturaEdificio = 614; int 32 bits Int miCantidad = 3456740, tuCantidad = 645780; long 64 bits Long distanciaInical, distanciaFinal = 86868688; Tabla 8 Coma flotante Véase el apartado de Coma flotante en Literales Tipos de datos de Coma Flotante Tipo Tamaño Ejemplo float 32 bits float presión; double 64 bits double distanciaEstelar; Tabla 9 Booleanos boolean seguir; boolean cancelarProceso = true, abandonarAplicación = false; Véase el apartado de Booleanos en Literales Carácter char inicial; char miInicial = ´F´; Véase el apartado de Caracteres en Literales Cadenas String miNombre = "Pep Rubirá" String comillasDobles = "/"" 41 Programación en Java © Grupo EIDOS Se deben hacer las siguientes consideraciones: • En este caso, la inicial del nombre de tipo (String, que como ya comentamos es una clase) está en mayúsculas, esto, como ya dijimos es fundamental, ya que Java distingue entre mayúsculas y minúsculas, por lo que si hubiésemos escrito el tipo de dato como "string", el compilador nos reportaría un error al no encontrar ninguna clase que tuviese este nombre. • En la definición de datos de tipo carácter las comillas son simples y en la de tipo String son dobles. • Para crear una cadena de caracteres donde se incluyan las comillas, utilizamos la secuencia de escape correspondiente. Véase el apartado de Cadenas en Literales. Arrays En Java, los arrays son objetos, son creados dinámicamente y pueden ser asignados a variables de tipo Object, por lo que todos los métodos de la clase Object (esta clase la trataremos en un próximo capítulo) pueden ser invocados desde un array. Un array tiene un número entero de elementos que lo componen, y nos referimos a ellos por su índice, que obviamente es un entero. Si un array tiene n elementos, el índice varía entre 0 y n-1 ambos inclusive. Un array puede tener 0 elementos, decimos entonces que el array está vacío. Del mismo modo, un array puede tener una o más dimensiones. En cualquier caso, una vez creado el array éste no puede cambiar de tamaño, es estático (existe en Java la clase Vector, que permite trabajar con "arrays" dinámicos). Todos los elementos de un array tienen que ser del mismo tipo, aunque estos pueden ser arrays, en tal caso, debe llegar un momento en que los elementos de estos elementos sean datos de otro tipo. Un array se "fabrica" en dos tiempos: el de declaración y el de creación. En el primero sólo se indican el tipo y las dimensiones de éste, esta información es procesada por el compilador. En el segundo tiempo se crea el array en memoria, para lo que se utiliza el operador new (este operador lo veremos en siguiente capítulo para la creación de objetos), es entonces cuando indicamos el número de elementos que va a contener. Java no permite por lo tanto declarar arrays al estilo de C, donde al tiempo que se declara un array se indica el número de elementos (int n[10] no es válido en Java). Esto es así porque Java no utiliza punteros, sino objetos para crear variables de tipo array, lo que permite entre otras cosas comprobar los límites del array cada vez que se va acceder a él, y reportar un error si se intenta sobrepasar sus límites, en lugar de dejar colgada la aplicación y el sistema operativo, como ocurre en C. La declaración de arrays en Java es casi igual que en C, salvo que los corchetes pueden utilizarse tras el nombre de la variable (como en C) o tras el tipo de la variable. También pueden definirse los elementos de un array en la declaración del mismo. Veamos algunos ejemplos de declaración de arrays en el Código fuente 1. int iNumeros[]; // Declara un array de enteros char cAlafabeto[]; // Declara un array de caracteres 42 © Grupo EIDOS 3. Sintaxis del lenguaje Java float fTemp[]; // Declara un array de floats float[] fTemp; // Equivalente a la anterior long lTabla[][]; // Array de dos dimensiones char cAlfabeto []= new char[27] // Declara y construye in array de 27 elementos float fTemp [][] = new float [10][3] // Declara un array de 2 dimensiones: // una con 10 elementos y la otra con 3 elementos int iPrimos = { 1,3,5,7 } // Declara un array de 4 elementos y le da valores Código fuente 1 Esto mismo podría haber hecho como indica el Código fuente 2. int iPrimos[]; iPrimos = new int[4]; iPrimos[0] = 1; iPrimos[1] = 3; iPrimos[2] = 5; iPrimos[3] = 7; Código fuente 2 Sin embargo, en las especificaciones de Sun para la versión 1.1 de Java, se ha ampliado levemente la forma en cómo se pueden inicializar los arrays. Ahora se permite inicializar los contenidos de un array al utilizar el operador new. Lo que flexibiliza la creación de arrays. El Código fuente 3es ahora correcto. String[] martians = new String[] { "Gidney", "Cloyd" }; Código fuente 3 Empaquetar tipos primitivos Aunque Java es un lenguaje casi 100% orientado a objetos, internamente trabaja con dos tipos de entidades: tipos primitivos y objetos. La forma en como maneja los tipos numérico, booleano y carácter es muy similar a como lo hacen otros lenguajes procedurales como puedan ser C o Pascal, a diferencia de otros lenguajes como SmallTalk para quien todo son objetos. La única razón de que exista esta diferencia en Java, es que el manejo de tipos primitivos es más eficiente que el de los objetos; y por lo tanto se puede conseguir un mayor rendimiento del sistema. Sin embargo, muchos de los componentes del lenguaje Java sólo pueden trabajar con objetos, es decir tienen que recibir una instancia que directa o indirectamente provenga de la clase Object, que es la raíz de la jerarquía de clases de Java. Para resolver este conflicto, el lenguaje Java dispone de una serie de clases que empaquetan los tipos primitivos. El paquete java.lang (se debe recordar que un paquete es una forma de organizar y agrupar clases relacionadas, en los capítulos siguientes trataremos más en detalle los paquetes) contiene las clases de que se dispone en Java para empaquetar los tipos primitivos. 43 Programación en Java © Grupo EIDOS Clase Tipo primitivo que empaqueta Integer Int Long Long Float Float Double Double Boolean Boolean Tabla 10 Si se observa la tabla anterior, nos damos cuenta que no existen clases empaquetadoras para los tipos primitivos byte y short, lo que realmente no supone ningún problema, ya que estos dos tipos pueden tratarse como objetos utilizando la clase Integer. Si echamos un vistazo a la jerarquía de clases del Java, se observará que todas las clases empaquetadoras descienden de la superclase Number. Conversión de tipos de datos Java al igual que C admite el moldeado de tipo (casting) o conversión de tipos, lo que nos permite convertir un tipo de dato en otro. Lo que se suele utilizar principalmente para cambiar el tipo de dato devuelto por una función para que coincida con el tipo de dato esperado por otra a la que se lo vamos a pasar como parámetro. Para realizar el moldeado de tipo (casting), se coloca el tipo desea entre paréntesis a la izquierda del valor que vamos a convertir. El típico ejemplo que muestra esto es el de conversión del valor devuelto por el método read() del sistema estándar de entrada. Este método devuelve un dato de tipo int, y normalmente deseamos almacenarlo como un tipo char, lo convertiremos como muestra el Código fuente 4. char c; ...................... ...................... c = (char) System.in.read(); Código fuente 4 A la hora de convertir unos datos a otros, puede perderse información dependiendo de la capacidad de almacenamiento que uno y otro tipo de dato tengan. Por lo que debe tener muy en cuenta la información que hemos dado a este respecto en esta sección. Si deseamos convertir un tipo de dato cuya capacidad de almacenamiento es de 64 bits (por ejemplo un long) en otro de menor capacidad (por ejemplo un int, con 32 bits), es muy posible que en la conversión perdamos información. Esto ocurrirá si los bits altos (aquellos que están a la izquierda en 44 © Grupo EIDOS 3. Sintaxis del lenguaje Java una representación binaria del número) del dato long tenían información (lo que ocurre con toda seguridad para números muy grandes). Otro caso distinto de pérdida de información es el que ocurre cuando se moldea un double para convertirlo en un long. Aunque la capacidad de almacenamiento de ambos sea de 64 bits, el tipo double tiene parte entera y parte fraccionaria, mientras que el long sólo tiene parte entera. Todo esto se debe tener muy presente cuando se vaya a realizar una conversión de tipo, ya que estos errores a veces son difíciles de detectar y nos pueden dar más de un quebradero de cabeza. En la Tabla 11 presentamos las conversiones en las que no puede haber pérdida de información (conversiones seguras). Moldeado sin Pérdida de Información Tipo fuente Tipo Destino byte short, char, int, long, float, double short int, long, float, double char int, long, float, double int long, float, double long float, double float Double Tabla 11 Bloques y ámbitos Bloques Al igual que C, Java utiliza las llaves ({} la primera de inicio y la otra de fin de bloque) para determinar los bloques dentro de un programa. Todo lo que se encuentra entre estas dos llaves se considera un bloque. Los bloques son parte de la sintaxis del lenguaje. Los bloques pueden y suelen anidarse; utilizándose la sangría para clarificar el contenido de un bloque. De este modo, el bloque más externo se sitúa al margen izquierdo del fichero de código fuente, y cada vez que se anida un bloque se indenta (sangra) el texto que lo contienen un número determinado de columnas, normalmente tres o cuatro. El sangrado no tiene ninguna utilidad para el compilador, pero sin ella la lectura del código por parte del programador es casi imposible. 45 Programación en Java © Grupo EIDOS Ámbitos Los bloques además, definen los ámbitos de las variables. El ámbito se refiere a la longevidad de las variables. Una variable existe sólo dentro del bloque donde ha sido declarada, eliminándola el compilador una vez que se sale de dicho bloque. Esto es cierto para todos los tipos de datos de Java. Si el dato es un objeto, y su clase tiene un destructor asociado (distinto al destructor por defecto, que es interno y Java lo maneja por sí solo) este es invocado al salir la variable de ámbito. Cuando una variable sale de ámbito, es eliminada y la memoria que ésta ocupaba es liberada por el recolector de basura (garbage collector). Expresiones Una expresión no es más que una secuencia de elementos que puede ser evaluada por el compilador. Una llamada a una función, una asignación, un cálculo aritmético o lógico son algunos ejemplos de expresiones. Las expresiones suelen dividirse según su tipo, es decir según el tipo del valor que devuelven al ser resueltas, en: aritméticas y lógicas. Dentro de cada una de ellas existen subdivisiones. El compilador las analiza y, si está bien construido, las simplifica todo lo posible, evaluándose en tiempo de ejecución. Algunos ejemplos de expresiones aparecen en el Código fuente 5. System.out.println( "Hola" + c ); a = 21; b = a + b + c + 15; c = (a + b) * (y-7); d = 2 / 3 + 4 * 5 d = a && b; e = (a && b) || c Código fuente 5 Las expresiones se analizan de izquierda a derecha, salvo que existan paréntesis, en tal caso (como ocurre en matemáticas), los fragmentos de la expresión encerrados entre paréntesis son evaluados antes. Si no hubiese paréntesis, hay algunos fragmentos que son evaluados antes que otros, dependiendo de la precedencia (importancia) de los operadores que se encuentran entre los operandos, pero a igual nivel de precedencia, se mantiene la evaluación de izquierda a derecha (sobre precedencia de operadores, consultar el apartado Precedencia de los operadores). En cualquier caso, se recomienda efusivamente para aclarar el código y eliminar posibles errores, utilizar siempre paréntesis para determinar qué fragmentos de la expresión se van a evaluar primero. En el caso de la expresión de ejemplo cuyo resultado se asigna a la variable d, podemos interpretarla de varias maneras, ya que de no saber en qué sentido se evalúan las expresiones y cual es la precedencia de los operadores, el resultado es imprevisible. De todos modos, adelantamos que esta expresión en Java equivale a lo que muestra el Código fuente 6. 46 © Grupo EIDOS 3. Sintaxis del lenguaje Java d = (2 / 3) + (4 * 5) Código fuente 6 Este ejemplo ilustra claramente la conveniencia de utilizar los paréntesis al declarar expresiones. En Java, a diferencia de otros lenguajes de programación, al evaluar una expresión lógica, podemos hacer que esta sea evaluada de forma perezosa o no perezosa, dependiendo del operador que utilicemos. Una expresión, se evalúa de forma perezosa cuando tras conseguir el resultado buscado, el evaluador no continúa evaluando el resto de las expresiones por la derecha. Veamos un ejemplo. Supongamos que las siguientes funciones devuelven los valores a continuación indicados: • Hora() Devuelve la hora actual en formato numérico (0-24 hrs) • DíasMes() Devuelve el día del mes en formato numérico (1-31) • Año() Devuelve el año en curso en formato numérico (0-...) Siendo los operadores tal como se describen: • || OR no exclusivo • && AND Entonces, la siguiente expresión lógica (que puede estar dentro de una instrucción de bifurcación condicional if): Hora() >= 12 || (DíaMes() > 15 && Año() == 1997) Se evaluará como indica la Tabla 12. Evaluación Perezosa Hora() DíaMes() Año() Al ser true el resultado la función Hora(), la expresión es verdadera, por lo que no es necesario invocar las funciones DíaMes() ni Año(). 14 10 10 Forma de evaluarse 10 Al ser false el resultado devuelto por Hora(), es necesario invocar DíaMes(), pero como su resultado es también false, no es necesario invocar Año(), ya que el resultado de la expresión es false. 25 Al ser false el resultado de Hora(), es necesario invocar DíaMes(), y como su resultado es true, el resultado de la expresión depende del valor devuelto por Año(), por lo que es necesario invocar esta función, y en este caso, al ser éste true, el resultado de la expresión es true. 1997 47 Programación en Java 10 25 © Grupo EIDOS Este caso es idéntico al anterior, pero al ser false el resultado de Año(), el resultado de la expresión es false. 1998 Tabla 12 Sin embargo, en ciertos casos deseamos que la expresión se evalúe completamente, normalmente porque deseamos que todas las funciones que la componen sean invocadas y realicen su cometido. Para estos casos, Java dispone de operadores que fuerzan al evaluador a recorrer toda la expresión. Estos son: &, | y ^. Puesto que es muy común que las expresiones contengan operadores, ahora vamos a entrar en más detalle sobre los operadores que Java nos proporciona. Clasificación de operadores En este apartado veremos, organizados según el tipo, los distintos operadores que el lenguaje Java incorpora. El programador de C y/o C++, comprobará que son prácticamente los mismos, aunque Java permite mayor flexibilidad en algunos de ellos y añade algún otro. Aritméticos Operadores sobre enteros Estos operadores pueden aplicarse a todos los tipos de datos enteros: byte, short, int, long. Se dividen en: • Unarios: utilizan un solo operando para realizar la operación. • Binarios: utilizan dos operandos para realizar la operación. • Relacionales: se comparan dos operandos. Operadores Aritméticos para Números Enteros Op. Descripción ++ Incremento. Añade una unidad al valor actual de la variable. (1) -- Decremento. Sustrae una unidad al valor actual de la variable. (1) - Negación. Equivale a multiplicar por –1 el valor de la variable. ~ Complemento a nivel de bits. (2) Unarios 48 © Grupo EIDOS Binarios Relacionales 3. Sintaxis del lenguaje Java + Adición - Sustracción * Multiplicación / División % Módulo << Desplazamiento a la izquierda. (3) >> Desplazamiento a la derecha. (3) >>> Desplazamiento a la derecha con relleno de ceros. (3) & AND a nivel de bits. | OR a nivel de bits. ^ XOR a nivel de bits. < Menor que > Mayor que <= Menor o igual que >= Mayor o igual que == Igual que != Distinto que Tabla 13 Tanto los operadores unarios como los binarios, independientemente del tipo de los operandos sobre los que se realice la operación, devuelven un int para todos los casos, excepto que uno de los operandos sea un long, en cuyo caso el valor devuelto es de tipo long. (1) Estos operadores pueden utilizarse en forma de prefijo (++variable) o forma de sufijo (varible++). En el primer caso el incremento (o decremento) de la variable se realiza antes de evaluar la expresión, y en segundo se realiza tras evaluar la expresión. (2) Este operador conmuta el número a nivel de bits, es decir, todos los bits que estaban a 0 pasan a ser 1, y todos los bits que estaban a 1 pasan a 0. (3) Estos operadores desplazan los bits a la derecha o izquierda el número de posiciones especificado como segundo operando. 49 Programación en Java © Grupo EIDOS Operadores sobre reales Estos operadores trabajan sobre número de coma flotante, es decir, los tipos: float y double. Lo dicho anteriormente acerca de los operadores sobre enteros es aplicable a los operadores sobre números reales salvo que el resultado de la expresión será de tipo float si ambos operandos son de este tipo y en caso contrario el tipo devuelto será double. Operadores Aritméticos para Números Reales Op. Descripción ++ Incremento. Añade una unidad al valor actual de la variable. -- Decremento. Sustrae una unidad al valor actual de la variable. + Adición - Sustracción * Multiplicación / División % Módulo < Menor que > Mayor que <= Menor o igual que >= Mayor o igual que == Igual que != Distinto que Unarios Binarios Relacionales Tabla 14 Booleanos Operadores Booleanos 50 Operador Descripción & AND (no perezoso) | OR (no perezoso) © Grupo EIDOS 3. Sintaxis del lenguaje Java ^ XOR (no perezoso) && AND (perezoso) || OR (perezoso) ! NOT (negación) == Igualdad != Distinto a ?: Condicional (op. ternario) Tabla 15 Son aquellos que efectúan operaciones sobre datos de tipo booleano, y como es lógico, el tipo de dato que devuelven es booleano. Relacionales < Menor que > Mayor que <= Menor o igual que >= Mayor o igual que == Igual que != Distinto que Relacionales Tabla 16 Cadena En Java existe un solo operador que utilice cadenas como operandos, este es el operador binario +, este operador concatena cadenas. En el Código fuente 7 vemos un ejemplo. String str1 = str2 = str3 = str1, str2, str3; "En lugar de la Mancha"; "de cuyo nombre no quiero acordarme"; str1 + " " + str2; Código fuente 7 Tras aplicar el operador de concatenación de cadenas, el valor la variable str3 es: 51 Programación en Java © Grupo EIDOS "En lugar de la Mancha de cuyo nombre no quiero acordarme" Asignación Estos operadores, salvo el operador de asignación simple (=) se utilizan para escribir menos, y por lo tanto, su forma simplificada es exactamente equivalente a su forma extendida. Operadores de Asignación Op. Descripción Equivalencia = Simple += Adición var1 = var1 + var2 Æ var1 += var2 –= Sustracción var1 = var1 – var2 Æ var1 –= var2 *= Multiplicación var1 = var1 * var2 Æ var1 *= var2 /= División var1 = var1 \ var2 Æ var1 \= var2 %= Módulo var1 = var1 % var2 Æ var1 %= var2 &= AND var1 = var1 & var2 Æ var1 &= var2 |= OR var1 = var1 | var2 Æ var1 |= var2 ^= XOR var1 = var1 ^ var2 Æ var1 ^= var2 Tabla 17 Precedencia de los operadores A continuación se presenta todos los operadores que hemos visto, según su orden de precedencia de mayor a menor, es decir, según su importancia a la hora de ser evaluados. Esto quiere decir, que salvo que se agrupen expresiones mediante el uso de paréntesis, aquellos operadores que tengan mayor orden de precedencia, serán evaluados primero. 52 • [] () • ++ -- • ! ~ instanceof • /% • +- • << >> >>> © Grupo EIDOS • < > <= >= = = != • &^| • && || • ?: • = += –= *= /= %= &= |= ^= 3. Sintaxis del lenguaje Java Control de flujo Las sentencias de control de flujo nos permiten que ciertas partes de un programa no se ejecuten (condicionales) o que se ejecuten más de una vez (bucles). Sin ellas, los programas Java se ejecutarían de arriba abajo, se procesarían todas sus líneas y una sola vez cada una de ellas. Las condiciones permiten bifurcar el flujo de la aplicación y en Java se dispone de las sentencias ifelse y switch, los bucles permiten repetir un fragmento de código un número determinado de veces, y las sentencias disponibles en Java para realiza bucles son: for, while y do-while. Todas estas sentencias funcionan exactamente igual que sus homónimas de C, por lo que si el alumno las conoce, puede saltárselas tranquilamente. Java dispone además de otras dos sentencias que permiten hacer saltos: break y continue. If-else La sintaxis de esta estructura es la siguiente: if (<ExprBooleana>) sentencia1; [else [if (<ExprBooleana>) ] sentencia2;] Aunque también pueden utilizarse bloques en lugar de sentencias, del siguiente modo: if (<ExprBooleana>) { sentencias; } [else [if (<ExprBooleana>) ] { sentencias; }] <ExprBooleana> puede ser cualquier expresión que, al ser evaluada, devuelva un resultado de tipo Booleano. Como esta estructura es la más común a través de todos los lenguajes, asumimos que es conocida de sobra, y no entraremos en los detalles de su funcionamiento. 53 Programación en Java © Grupo EIDOS Switch La sintaxis de esta estructura es la siguiente: switch( <Expresión> ) { case <Constante1>: sentencias; [break;] [case <Constante2>: sentencias; [break;] ........................ ] [default: sentencias; [break;]] } En esta sentencia, el resultado de la evaluación de <Expresión> es comparado con cada una de las constantes (<Constante1>, <Constante2>,.....) hasta que se encuentra una que coincida, en ese momento se ejecutan todas las sentencias que se encuentran dentro de ese case, hasta encontrar un break, por lo que si no hay un break al final del case, los cases siguientes también se ejecutarán. <Expresión> debe pertenecer a alguno de los tipos primitivos de Java, no puede ser una clase. Si no se encontrara coincidencia alguna, y existiera la cláusula default, estas sentencias serán ejecutadas hasta encontrar un break. De todos modos, como esta cláusula, caso de existir, siempre se pone la última, no es necesario incluir el break. Esta rama puede simularse mediante el uso de la rama if-else, pero switch ha sido diseñada específicamente para escoger un resultado de entre un grupo de resultados posibles, y es de suponer que el código que se genera es más eficiente que el que se generaría si usáramos para el mismo propósito una estructura if-else. Veamos un ejemplo simple de switch, en el Código fuente 8. switch( iDíaSemana ) { case 1: sDia = "lunes"; break; case 2: sDia = "martes"; break; case 3: sDia = "miércoles"; break; case 4: sDia = "jueves"; break; case 5: sDia = "viernes"; break; case 6: sDia = "sábado"; break; case 7: sDia = "domingo"; break; 54 © Grupo EIDOS 3. Sintaxis del lenguaje Java default: sDia = "No se qué día es"; } Código fuente 8 En el Código fuente 9 vemos otro ejemplo en el que no utilizamos una sentencia break tras cada case. int iMes; int iDías; long nAño; . . . switch (iMes) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: iDías = 31; break; case 4: case 6: case 9: case 11: iDías = 30; break; case 2: if ( ((lAño % 4 == 0) && !(lAño % 100 == 0)) || (lAño % 400 == 0) ) iDías = 29; else iDías = 28; break; default: iDias = 0; } Código fuente 9 For La sintaxis del bucle for es la siguiente: for ( [<ExprInicializa>]; [<ExprCondición>]; [<ExprIteración>]) sentencia; O bien: for ( <ExprInicializa>; <ExprCondición>; <ExprIteración>){ sentencias; } Aunque estas tres expresiones pueden utilizarse para el propósito que se desee, normalmente se utilizan para los siguientes fines: • ExprInicializa: en esta expresión se inicializa la variable(s) que va a controlar el bucle. 55 Programación en Java © Grupo EIDOS • ExprCondición: esta expresión controla cuando finalizan las iteraciones del bucle. • ExprIteración: esta expresión indica cómo cambia la variable de control del bucle. Se debe señalar que las expresiones son todas optativas, y aunque se pueda escribir un bucle sin ninguna de ellas, no tiene mucho sentido hacerlo. Si así se hiciera, se tendría que mantener los puntos y comas que separan las tres expresiones. Cada una de estas expresiones que se definen en la cabecera del bucle pueden ser simples o expresiones compuestas separadas por comas. Veamos un ejemplo de cada una de ellas: Vemos, en el Código fuente 10, expresiones simples. for (x = 1; x <= 10; x++) System.out.println( x ); Código fuente 10 La cabecera de este bucle for puede leerse del siguiente modo: "Para x igual a uno, y mientras que x sea menor o igual que diez, incrementa x en una unidad". El Código fuente 11, nos muestra expresiones compuestas. for (x = 1, t = 30; x <= 10 && t-=x != 20; x++, t -= 2 ){ ............................ } Código fuente 11 La cabecera de este bucle for puede leerse del siguiente modo: "Para x igual a uno, y t igual a treinta, mientras que x sea menor o igual que diez y mientras x menos t sea distinto de 20, incrementa x en una unidad y disminuye t en dos unidades". Debido a la potencia y la versatilidad que esta forma de concebir los bucles for ofrece, a veces el programador siente el deseo de agrupar varias expresiones y parte de lo que serían las sentencias del cuerpo del bucle en la cabecera del mismo, haciéndola compleja y de difícil lectura. Recomendamos huir de este estilo de programación por lo complicado que puede ser posteriormente entender un código escrito de este modo. Resulta más apropiado escribir un poco más de código si con ello conseguimos claridad, y dejar que el compilador haga las optimizaciones que considere oportunas, en lugar de enredarlo todo en un esfuerzo de incrementar la velocidad de nuestra aplicación o de demostrar a los demás o a nosotros mismos cuan inteligentes somos. While Las principales diferencias entre el bucle for y el bucle while son que en éste no se inicializan los valores de control ni se dispone de la expresión de iteración. La sintaxis de este bucle es la siguiente: 56 © Grupo EIDOS 3. Sintaxis del lenguaje Java while (CondBooleana) sentencia; O bien: while (CondBooleana){ sentencias; } La sentencia o sentencias definidas en el cuerpo de un bucle while se repiten mientras que la condición de su cabecera sea verdadera. Es por esto que esta condición tiene que ser una expresión cuyo resultado sea de tipo booleano. Debido a que este tipo de bucles se encuentra en todos los lenguajes, no vamos a entrar en más detalles acerca de su funcionamiento. Do-while: La sintaxis de este bucle es la siguiente: do sentencia; while (CondBooleana); O bien do { sentencias; } while (CondBooleana); Como puede verse, este bucle es casi idéntico al bucle while, salvo que en este caso, la expresión booleana se evalúa tras haberse realizado la primera iteración del bucle. Debido al parecido de este con el anterior, y por las mismas razones ya expuestas al hablar del bucle while, no vamos a entrar en más detalles. Break, continue y etiquetas Java dispone de las sentencias break y continue y las etiquetas para realizar saltos. El caso de la sentencia return, es especial y lo trataremos al final de este apartado. Ya hemos visto el uso de la sentencia break cuando tratamos la sentencia switch. Sin embargo, break puede utilizarse en cualquier sitio de la aplicación, aunque sólo tiene sentido dentro de la sentencia switch o dentro de los bucles. break, hace que el flujo de la aplicación salte al estamento siguiente al que se está ejecutando en el momento de ser encontrado. Veamos un ejemplo de uso de esta sentencia dentro de un bucle, en el Código fuente 12. for( i = 1; i <= 10; i++ ) { 57 Programación en Java © Grupo EIDOS if (i == 5 ) break; System.out.println( i ); } Código fuente 12 Este bucle pinta solamente los números del 1 al 4. Ya que break se ejecuta cuando i == 5, lo que hace que el flujo de la aplicación pase control instrucción que se encuentra inmediatamente tras el bucle. La sentencia continue es similar a break, pero en lugar de transferir el flujo de la aplicación fuera del bucle, continue lo lleva a la cabecera del bucle. Por lo tanto, y como es lógico, esta instrucción sólo puede utilizarse dentro de bucles. En el Código fuente 13 podemos ver un ejemplo del uso de esta sentencia. for( i = 1; i <= 10; i++ ) { if (i == 5 ) continue; System.out.println( i ); } Código fuente 13 Este bucle muestra todos los números del 1 al 10 excepto el número 5. Ya que continue se ejecuta cuando i == 5, lo que hace que se salten todas las sentencias por debajo de continue y que el flujo de la aplicación vuelva a la cabecera del bucle. Aunque goto es una palabra reservada, en Java los saltos no están permitidos. Esto como ya hemos comentado en alguna ocasión, es debido a que los arquitectos de este lenguaje han intentado eliminar todos aquellos recursos que puedan dar lugar a un mal estilo de programación. Y está aceptado por toda la comunidad de teóricos de la programación que los saltos a una etiqueta son totalmente desaconsejables. Sin embargo, Java permite asociar una etiqueta cuando se va a realizar un salto, para de este modo clarificar el código. La etiqueta se asocia con las instrucciones break y continue, que son las únicas sentencias de salto que Java contempla. Se dice en estos casos que se ha hecho un break etiquetado o un continue etiquetado. En cualquier caso esta técnica es sólo aconsejable cuando se está trabajando con bucles anidados y se quiere salir de ellos a distintos niveles. No ayudando a la legibilidad del código en ningún otro supuesto teórico. Para realizar un salto a una etiqueta, primero debemos definir la etiqueta, lo que se hace escribiendo un literal que va a ser la etiqueta seguida del operador dos puntos (:) antes de una sentencia de Java. Y desde cualquier punto se puede hacer un break con el formato: break <etiqueta> Veamos un ejemplo (en el Código fuente 14) del uso de las etiquetas en el entorno para el que fueron pensadas. uno: for( ){ 58 © Grupo EIDOS 3. Sintaxis del lenguaje Java dos: for( ){ continue; // seguiría en el bucle interno break; // se saldría del bucle interno continue uno; // seguiría en el bucle principal break uno; // se saldría del bucle principal } } Código fuente 14 Return Esta instrucción es un caso especial, ya que aunque permite realizar un salto, no es una instrucción de salto propiamente dicha. Esta sentencia tiene la siguiente sintaxis: return <valor>; Cuando return es encontrado en el programa, el flujo salta fuera del método donde fue encontrado, no ejecutándose ninguna otra instrucción por debajo de esta y devuelve el control al método que lo invocó. Si el método donde se encuentra return es el principal, la aplicación termina. Esta sentencia permite devolver un valor al método que llama a otro tras la ejecución de un proceso determinado. Lo que en Java es muy importante, porque los métodos, si se especifica que tienen que devolver un dato de un tipo determinado (cualquiera excepto void), tienen que contener una instrucción return que devuelva un valor coincidente con la declaración del tipo del método. En otro caso un mensaje de advertencia será reportado por el compilador. Un programa Java no puede compilarse si este contiene errores o advertencias. Try, catch, finally y throws: Estas sentencias de control de flujo son: try-catch-finally y throws. Permiten el manejo de excepciones en Java. Cuando un error ocurre dentro de un método, el método puede lanzar (throws) una excepción para indicar a quien lo invocó que un error ha ocurrido y cual ha sido el error. El método que ha llamado al que ha lanzado la excepción, puede entonces utilizar try, catch y finally para capturar y manejar la excepción. Del uso estas sentencias hablaremos en profundidad en el capítulo correspondiente. 59 POO en Java: objetos Introducción A lo largo de este capítulo, y en los dos que le siguen, vamos a abordar tanto la sintaxis de la POO en Java como otros elementos del lenguaje que se encuentran íntimamente relacionados con las clases. En el capítulo anterior vimos la sintaxis general del lenguaje, y los capítulos siguientes veremos como utilizando esa sintaxis se implementa toda la teoría de la Programación Orientada a Objetos vista en el primer capítulo de este curso. Se debe señalar, como ya hemos comentado con anterioridad, que las piezas clave del lenguaje Java, como lenguaje de POO que es, son las clases y los objetos. Java se apoya sobre una completa y compleja jerarquía de clases predefinidas del lenguaje, pero en esta serie de capítulos, referenciados como POO en Java, además de comentar la estructura de la jerarquía de clases existente, mostraremos como crear nuestras propias clases, para poder reutilizarlas en nuestros programas como si formaran parte de la jerarquía de clases estándar presentes en el lenguaje Java. A continuación vamos a ir viendo como utilizar objetos y clases dentro del lenguaje Java y ya comenzaremos a aprender a escribir nuestro código Java. Objetos Como ya hemos comentado en varias ocasiones, los objetos son las piezas fundamentales de cualquier lenguaje de programación que sea orientado a objetos, por lo tanto vamos a tratar en un primer lugar los objetos en Java. Lo haremos a lo largo de varios apartados, para pasar después a explicar como crear nuestros propios tipos de objetos, es decir, como implementar nuestras propias clases. Programación en Java © Grupo EIDOS En un programa Java vamos a estar creando objetos de distintos tipos de clases, estos objetos interactúan entre sí a través del mecanismo de mensajes, es decir, invocando métodos. Una vez creado el objeto realizará la función que se le haya encomendado: ejecutar una animación, formar parte de un interfaz de usuario, enviar o recibir información a través de la red, etc. Cuando el objeto ha terminado su trabajo para el cual fue creado es destruido por el recolector de basura para liberar recursos que podrán ser reutilizados por otros objetos. A la vista de lo anterior podemos decir que un objeto tiene un ciclo de vida, y que en este ciclo de vida podemos distinguir tres fases o etapas: 1. Creación. 2. Utilización. 3. Destrucción. En los siguientes apartados vamos a tratar cada una de las etapas del ciclo de vida de un objeto y como las implementamos desde el lenguaje Java. Creación de objetos En este apartado se va a tratar la primera fase del ciclo de vida de un objeto. El proceso de crear un objeto a partir de una clase se divide en tres pasos: declaración, instanciación e inicialización. Vamos a ver cada uno de ellos. Declaración La sintaxis para declarar un objeto en Java es la siguiente: <tipo> <nombre>; Donde: <tipo> es el nombre de clase, es decir, el tipo de dato que se va a crear. <nombre> es el nombre de la variable con el que nos referiremos al objeto. Por ejemplo, vemos el Código fuente 15. String sNombre; Date dHoy; Código fuente 15 Como se puede observar, no hay ninguna diferencia entre estas declaraciones y las declaraciones de los tipos primitivos, es decir las que muestra el Código fuente 16. 62 © Grupo EIDOS 4. POO en Java: objetos int iEdad; char cInicial; Código fuente 16 Y esto, en un lenguaje de POO, es lo lógico. Como ya comentamos al hablar de tipos y conversión de datos (en el capítulo anterior), cuando creamos una nueva clase, estamos sencillamente creando un nuevo tipo de datos. Hasta el momento, no hay ningún objeto por sitio alguno, de hecho, las variables sNombre y dHoy, todavía no contienen objetos, es decir, no se ha creado todavía ningún objeto ni de clase String ni de clase Date, ¿entonces qué hemos hecho?. Pues simplemente le hemos indicado al compilador que la variable sNombre es de tipo String, es decir que en algún punto del programa va a contener un objeto de la clase String, y que la variable dHoy es de tipo Date, es decir que en algún punto del programa va a contener un objeto de la clase Date. De este modo, si más adelante intentamos instanciar un objeto de una clase distinta a la declarada, el compilador detectará el error y lo reportará, impidiendo que finalice este proceso. Terminado con el primer paso vamos con el segundo dentro del proceso de creación de objetos. Instanciación Para instanciar una clase, en Java se utiliza el operador new. Si el alumno es programador de C++, el operador new le resultará familiar. Pero debe tener cuidado, porque en Java no funciona del modo en como lo hace en C++. Mientras que en C++ new se utiliza para crear un objeto dinámicamente en memoria, y devuelve un puntero al objeto creado, en Java este operador devuelve una referencia al propio objeto (se debe recordar que en Java no existen los punteros). El operador new invoca al constructor (de ellos hablaremos la fase de inicialización) de la clase y le pasa los parámetros de inicialización si los hubiere. El operador new crea el objeto y el constructor lo inicializa. La sintaxis del operador new es la siguiente: [<Variable> =] new <Constructor>( [<par1>, <par2>, ..., <parN>] ) De este modo, para crear un objeto de la clase String, haríamos lo que muestra el Código fuente 17. sNombre = new String(); Código fuente 17 Y para crear uno de la clase Date, lo que nos muestra el Código fuente 18. dHoy = new Date( ); Código fuente 18 63 Programación en Java © Grupo EIDOS Evidentemente, si a la izquierda de la expresión no hubiéramos puesto una variable, la referencia devuelta por el operador new se habría perdido; del mismo modo, si el tipo de variable no coincidiera con el tipo devuelto por el operador, el compilador reportaría un error indicando que los tipos no concuerdan. Inicialización Ya tenemos un objeto de la clase String y otro de la clase Date creados, pero ¿Qué contienen estos objetos? dicho de otro modo ¿Cuál es su valor?. En el caso de sNombre, la cadena es una cadena vacía y en el caso de dHoy, la hora a la cual fue el objeto creado. Sin embargo, en casi todas las ocasiones, cuando al crear un objeto, solemos querer darle un valor. Esto es lo que se llama inicialización del objeto. Este proceso lo realiza el constructor de la clase. Un constructor no es más que un método con el mismo nombre que la clase y que puede o no recibir una serie de parámetros que se utilizan para inicializar el objeto. Gracias a la sobrecarga de métodos (concepto que debe conocer el alumno del primer capítulo) es posible, y muy útil, disponer de varios constructores. Existe un constructor especial, llamado constructor por defecto, que es aquel que no recibe ningún argumento o parámetro. En el Código fuente 19 aparecen algunas inicializaciones posibles para los objetos anteriormente creados. sNombre = new String( "Albert Einstein" ); sNombre = new String( "1234" ); dHoy = new Date( 2000, 6, 1 ); dHoy = new Date( 2001, 1, 1 ); Código fuente 19 Como hemos comentado, para poder inicializar los objetos de distintos modos, las clases suelen proporcionar varios constructores. Así, en Java, la clase String, dispone de siete constructores distintos, y la clase Date de seis. La declaración y la instanciación también las podemos realizar en una única línea de código, como se puede observar en el Código fuente 20. Date dHoy = new Date( 2000, 1, 1 ); String sNombre = new String( "Albert Einstein" ); Código fuente 20 Utilización de objetos Una vez creado el objeto debemos ver los mecanismos que existen para poder utilizarlo. En este apartado se va a explicar cómo se acceden a los datos y métodos de un objeto, que por otra parte, es el modo estándar en la POO. El operador de envío de mensaje en Java, es el punto (.), es decir, es el 64 © Grupo EIDOS 4. POO en Java: objetos operador para lanzar o invocar un método de un objeto. Siendo la sintaxis genérica de envío de mensaje la siguiente: objeto.mensaje Y para acceder a los atributos de un objeto (o variables miembro) es: objeto.atributo Y para los métodos (o funciones miembro) es: objeto.método( [parámetros] ) Veamos algunos ejemplos en el Código fuente 21. Para ello, vamos a construir un nuevo objeto, utilizando la clase Rectangle contenida en el paquete java.awt de la librería de clases de Java, más delante en siguientes capítulos comentaremos con detenimiento en que consisten los paquetes y los principales que ofrece Java, de momento se debe indicar que un paquete es una forma de organizar clases, al igual que organizamos ficheros en un directorio en nuestro disco duro. // Instanciación del objeto rctCaja con unas dimensiones: //x, y, alto, ancho Rectangle rctCaja = new Rectangle( 100, 100, 200, 200 ); System.out.println( "x = " + rctCaja.x ); // Consultar x System.out.println( "y = " + rctCaja.x ); // Consultar y System.out.println( "Alto = " + rctCaja.height ); // Consultar Alto System.out.println( "Ancho = " + rctCaja.width); // Consular Ancho Código fuente 21 En el Código fuente 21 se utiliza el siguiente código System.out.println() que puede despistar a algunos lectores, de momento únicamente comentaremos que de esta forma conseguimos imprimir algo por pantalla, en el capítulo en el que se comentan las aplicaciones Java, entraremos en más detalles. Para cambiar un valor simplemente haríamos lo que muestra Código fuente 22. rctCaja.x = 150; Código fuente 22 O para realizar un cálculo (Código fuente 23). area = rctCaja.height * rctCaja.width; Código fuente 23 Para acceder a un método, empleamos el mismo operador, como se puede ver en el Código fuente 24. 65 Programación en Java © Grupo EIDOS rctCaja.resize( 150, 250 ); Código fuente 24 Si el método devolviese un valor podríamos almacenarlo en una variable como muestra el Código fuente 25. String sCaja; Caja = rctCaja.toString();//Representación del rectángulo como cadena Código fuente 25 Destrucción de objetos Al igual que existe un método constructor para crear objetos existe un método destructor para liberarlos. Del mismo modo que el constructor es llamado por el operador new cuando el objeto es creado, el destructor, si existe, es invocado también. El nombre del método destructor debe ser finalize(). Del hecho de que el destructor es llamado cuando el objeto va a ser eliminado de memoria, se deduce que el destructor no es llamado cuando el objeto sale fuera de ámbito, sino cuando va a ser destruido por el recolector de basura (garbage collector). Como ya hemos comentado, cuando una variable sale de ámbito, y un objeto siempre está referenciado por una variable, ésta es marcada para ser eliminada de memoria, y el recolector la eliminará cuando pueda hacerlo, ya que es una tarea que se desarrolla en background (en un segundo plano), y de forma incremental. Esto implica que no podemos saber cuando el método de finalización de un objeto va a ser invocado, lo que es parcialmente cierto, aunque existe la posibilidad de forzar que ocurra el proceso de finalización llamando al método runFinalization() de la clase System (de momento simplemente diremos que esta clase representa al sistema en ejecución) lo que se hace como muestra el Código fuente 26. System.runFinalization(); Código fuente 26 Este método llama a todos los métodos finalize() de todos los objetos que están esperando al recolector de basura. Pero no existe un modo automático para asegurarse de que el destructor va a ser llamado al salir el objeto de ámbito. En cualquier caso, y por el tipo de tareas que los destructores realizan, esto no tiene la menor importancia, ya que los destructores suelen encargarse de liberar los recursos capturados por el objeto (lo que normalmente se hace en el constructor), cerrar ficheros abiertos, cerrar sockets utilizados, liberar memoria, etc. 66 © Grupo EIDOS 4. POO en Java: objetos Por otro lado, una clase no tiene por qué tener un destructor, pero si lo tiene, éste tiene que reunir las siguientes características: • El nombre del método tiene que ser finalize(). Ya que el destructor es invocado por el recolector de basura, éste no puede saber cómo se llama el método de la clase a menos que se establezca una forma de hacerlo, y lo que se ha hecho es darle un nombre fijo. El que se ha escogido en Java es finalize(), aunque podrían haber escogido otro cualquiera. También se podría haber hecho como en C++, que el destructor tiene el mismo nombre de la clase precedido por la virgulilla ("~"). • No puede recibir ningún parámetro. Puesto que el recolector de basura no puede saber qué parámetros desea recibir el destructor, y puesto que cuando el destructor va a ser invocado es posible que algunos o todos los parámetros que desea recibir no estén en disponibles. • No puede haber más que un destructor. Esto es conclusión directa de lo anterior, ya que al no tener parámetros, no puede existir la sobrecarga. • No puede devolver valor alguno. Es decir, el método tiene que ser de tipo void. Por el mismo motivo que el recolector de basura es quien invoca al destructor, éste no puede devolver valor alguno, ya que aun cuando devolviese un valor este no podría ser recogido por proceso alguno porque el objeto ya ha salido de ámbito. Para ser correctos, hay que decir que el destructor siempre existe, ya que la clase Object, de la que heredan todas las otras clases en Java, dispone del método finalize(), éste es un método que no hace nada, por lo que si creamos una clase que hereda directamente de la clase Object no es necesario llamar al destructor de la clase padre. Sin embargo, en otras ocasiones, al heredar de otras clases en las que existe un método finalize() que desempeña una tarea necesaria para la correcta finalización del objeto, es necesario que tras realizar la labor oportuna en el método finalize() de su clase, llame al método finalize() de la clase padre para que éste pueda realizar su labor. La sintaxis de un destructor es como sigue: void finalize() { // Tarea de finalización } Se puede observar que como los destructores no pueden devolver ningún valor, el tipo que devuelve este método es void, de todas formas en el apartado dedicado a los métodos en el próximo capítulo lo veremos en mayor detalle. Una vez comentado como podemos tratar los objetos en Java vamos a pasar a comentar en el siguiente capítulo la segunda pieza clave de la POO, las clases. 67 POO en Java: clases Clases Hasta ahora hemos creado objetos a partir de clases existentes, pero como es lógico, se pueden crear nuevas clases e instanciar objetos de las clases definidas por nosotros mismos. Una clase de forma genérica se compone de: la declaración de la clase y del cuerpo de la clase, el cual, a su vez se divide en la sección de declaración (opcionalmente también la inicialización) de los atributos (variables miembro) y la sección de declaración e implementación de los métodos (funciones miembro). De este modo, la plantilla de una clase quedaría de la siguiente manera: declaraciónDeLaClase { declaraciónDeLosAtributos declaraciónDeLosMétodos } Siendo el cuerpo de la clase la amplia sección encerrada entre las dos llaves. Una clase puede o no contener atributos y/o métodos, pero al menos tiene que contener uno de los dos componentes. En los siguientes apartados, a lo largo del presente capítulo, iremos viendo cada una de las secciones que compone la construcción de una clase en Java. Programación en Java © Grupo EIDOS Declaración de la clase La declaración de una clase implica indicar el nombre que va a tener la clase junto con otras características que puede presentar la clase. La sintaxis de la declaración de la clase es la siguiente: class <NombreClase> { .......... } Como se puede ver, la declaración de una clase se compone, al menos, de la palabra clave class y del nombre de la clase. El nombre de la clase tiene que ser un identificador (véase el apartado Identificadores del primer capítulo) válido en Java. Aunque esta mínima declaración suele ser suficiente en algunos casos, es posible indicar, al declarar una clase, alguna otra información adicional. La sintaxis de una declaración completa de la clase sería la siguiente: [<Modificador(es)>] class <NombreClase> [extends <NombreSuperClase>] [implements <NombreInterface1> [, <NombreInterface2>, ... ]] { ........................ } A continuación vamos a ir comentando cada una de las características adicionales que podemos indicar a la hora de declarar una clase y que aparece en la sintaxis mostrada arriba. Superclase de la clase En Java, para hacer que una clase herede de otra (véase para más detalles el apartado Herencia de este mismo capítulo), se utiliza la palabra reservada extends antes del nombre de la superclase de la cual deriva. La sintaxis es como sigue: class <NombreClase> extends <NombreSuperClase>{ .......................... } En Java, todas las clases heredan directa o indirectamente de la clase Object (para más información sobre la clase Object acudir al apartado del mismo nombre dentro del siguiente capítulo), ya que esta clase se encuentra en la raíz de la jerarquía de clases. Si no se especifica que una clase hereda de otra, Java asume que hereda directamente de la clase Object. Únicamente podremos heredar de una clase, ya que Java no implementa el mecanismo de herencia múltiple. Interfaces de la clase Si la clase implementara algún interfaz (véase el apartado Interfaces del capítulo siguiente), este o estos, se especifican en la declaración de la clase. En pocas palabras, un interfaz es un conjunto de constantes y métodos sin implementar, que la clase que lo utiliza, tiene que implementar (codificar si se prefiere). De todas formas trataremos los interfaces en más detalle más adelante en el siguiente capítulo. 70 © Grupo EIDOS 5. POO en Java: clases La sintaxis para declarar una clase que utiliza interfaces es la siguiente: class <NombreClase> implements <NombreInterface1> [,<NombreInterface2>, ... ]{ .......................... } Por convención, la cláusula implements se especifica después de la cláusula extends, si la hubiere. Como se puede observar se pueden implementar uno o más interfaces. Modificadores de la clase Existen varios modificadores que son aplicables a la clase, en caso de tener que especificar alguno, este se sitúa al principio de la declaración, según la siguiente sintaxis: <Modificador> class <NombreClase> { ........................ } Los modificadores nos dan información adicional de la forma en la que la clase va a ser tratada. Los modificadores posibles que se pueden anteponer a la clase son los siguientes: • public: este es un modificador de acceso que indica que la clase es accesible de forma pública, es decir, a la clase se puede acceder y ser instanciada desde cualquier otra clase. Si no indicamos este modificador la clase sólo podrá ser accesible para clases dentro del mismo paquete (para información sobre los paquetes en Java acudir al apartado Paquetes en el capítulo siguiente). • abstract: este modificador indica que la clase es abstracta y por lo tanto no es instanciable, es decir, no vamos a poder crear objetos de esta clase. Las clases abstractas las utilizaremos para representar una clase que se encuentra en la raíz de una jerarquía de clases y recoge algunas características que son comunes a todas las clases hijas, pero que difieren en su implementación, para más información sobre clases abstractas consultar el apartado que posee el mismo nombre. • final: si aplicamos este modificador a una clase indicaremos que la clase no puede ser subclasificable, es decir, ninguna clase podrán heredar de ella y por lo tanto nunca podrá ser una clase padre. Una clase la declararemos como final por cuestiones de seguridad, para asegurarnos que ninguna clase hereda de ella para modificar comportamientos, y también por cuestiones de diseño de nuestra jerarquía de clases, para limitar en que momento se debe detener el mecanismo de herencia. Una vez comentados todos los elementos que forman parte de la declaración de una clase, se puede ver que los únicos elementos necesarios para declarar una clase son la palabra del lenguaje class y el nombre de la clase. Si no se especifican los elementos opcionales, los valores por defecto que el compilador asumen son los siguientes: no-final, no-public, no-abstract, subclase de Object y no implementa interfaz alguno. El cuerpo de la clase se compone de atributos y métodos, analizaremos cada uno de estos componentes en los siguientes apartados. 71 Programación en Java © Grupo EIDOS Cuerpo de la clase. Declaración de atributos En este apartado abordaremos el primero de los elementos que podemos definir dentro del cuerpo de una clase, es decir, los atributos de la clase. La sintaxis general para declaración de los atributos de la clase es la siguiente: <Modificador (es)> <tipo> <nombreVariable> Veamos cada uno de los elementos de la declaración: Modificadores Dentro de los modificadores que le podemos aplicar a un atributo se puede distinguir un grupo de ellos que se denomina modificadores de acceso. Estos modificadores de acceso son excluyentes entre sí e indican la forma de acceso a los atributos de una clase desde otras clases. Los modificadores de acceso posibles son los siguientes: • public: con este modificador de acceso indicamos que todas las clases tienen acceso a este atributo. Se suelen declarar como públicos los atributos cuyo acceso a los mismos no puede producir resultados no deseables. Aunque los atributos de una clase pueden ser public, esto está totalmente desaconsejado en la POO, los atributos deben ser accedidos solamente desde dentro de la clase y para manejarlos de la clase debe proporcionar un conjunto de métodos, denominados métodos de acceso. • private: este modificador de acceso es todo lo contrario a public. Un atributo declarado como private sólo es accesible desde dentro de la clase en la que está declarado. Es el nivel de acceso más restrictivo de todos. • Modificador por defecto o package: es el modificador de acceso predeterminado, es decir, si no indicamos un modificador de acceso para un atributo, se aplicará por defecto este tipo de acceso. Este tipo de atributos es accesible desde las clases que se encuentren dentro del mismo paquete (ver apartado Paquetes en el siguiente capítulo) en el que se encuentra la clase en la que se declara el atributo. Ya que este tipo de acceso es el predeterminado no es necesaria una palabra reservada. • protected: los atributos con este modificador son accesibles desde las clases hijas de la clase en la que se encuentran declarados los atributos, y también es accesible desde las clases que se encuentren dentro del mismo paquete. En Tabla 18 se muestra los diferentes niveles de acceso de un atributo, y quién puede acceder en cada caso. Modificador 72 La propia clase Clase hija Paquete private X protected X X X public X X X Resto del mundo X © Grupo EIDOS 5. POO en Java: clases Por defecto o X package X Tabla 18 Además de los modificadores de acceso un atributo puede presentar otros modificadores denominados modificadores de contenido, que en este caso no son excluyentes, y que son los dos que indican a continuación: • static: un atributo con el modificador de contenido static mantiene su contenido para todas las instancias de la clase que se hagan, así como para las clases hijas que de ella se hereden. A estos atributos se les denomina atributos de clase o variables de la clase, como contraposición a los atributos de instancia. Mientras que los atributos de instancia se inicializan para cada nueva instancia que se haga de la clase, es decir, existe una copia por cada instancia de la clase, de los atributos de la clase existe una sola instancia, independientemente del número de instanciaciones que de la clase se realicen. De este modo, todos los objetos comparten un lugar de almacenamiento común. Debido a la particularidad de este modificador de contenido vamos a comentarlo más con detenimiento a través de un ejemplo. El ejemplo más típico de atributo de la clase es un contador del número de objetos existentes de la clase. Para ello, sólo hay que incrementar el contador desde el constructor de la clase y decrementarlo desde el destructor. El código podría ser algo como lo que muestra el Código fuente 27. class Cuenteo { //atributo de clase static long lObj = 0; // Constructor, incrementa el valor Cuenteo(){ lObj++; } // Destructor, decrementa el valor void finalize(){ lObj--; } // Obtener el número de objetos existentes long GetObj(){ //devolvemos el valor return lObj; } } Código fuente 27 Otra cualidad más de los atributos static es que no es necesario instanciar un objeto de la clase para acceder a ellos. Lo cual resulta muy práctico, sobre todo cuando se trabaja con ciertas clases. Cuando hacemos System.out.println(), lo hacemos sin crear previamente un objeto de la clase System. Cuando lo lógico sería lo que muestra el Código fuente 28. 73 Programación en Java © Grupo EIDOS System oSystem = new System(); oSystem.out.println( "Hola" ) Código fuente 28 No lo hacemos así, sino que llamamos directamente al método println() del atributo out de la clase System sin instanciar previamente un objeto de esa clase. Esto es posible porque out es un atributo static de la clase System. Una vez comentado el modificador de contenido static vamos a pasar al segundo de estos modificadores. • final: este modificador indica que el atributo es una constante., es decir, el valor de este atributo no puede ser modificado. Por convenio los atributos que son constantes se escriben todos en mayúsculas. Un ejemplo del uso de este tipo de atributos se puede observar en el Código fuente 29. class MiClase{ final float PI = 3.14159; // resto de la implementación de la clase } Código fuente 29 Una vez vistos los modificadores de acceso y los de contenido, vamos a ver el resto de modificadores que se le pueden aplicar a un atributo de la clase. • transient: por defecto, los atributos forman parte de la información persistente del objeto. Los atributos persistentes se salvan cuando se graba en un archivo el objeto, pero no ocurre así con aquellas declaradas como transient (transitorias). • volatile: este modificador indica al compilador que no debe realizar optimizaciones sobre este atributo. No se suele utilizar. Ahora podríamos rescribir la sintaxis de la declaración de un atributo indicando todos los modificadores de la siguiente forma: [public | protected | private] [static] [final] [transient] [volatile] <tipo> <nombreVariable> Y aquí termina la sección dedicada a los modificadores que se pueden utilizar en la declaración de atributos de la clase. Tipo Indica el tipo del atributo es decir, la clase o tipo primitivos a los que pertenece. 74 © Grupo EIDOS 5. POO en Java: clases Nombre variable Aquí indicamos el nombre con el que vamos a identificar al atributo, y puede ser cualquier identificador válido en Java, para más información sobre identificadores consultar el capítulo dedicado a la sintaxis del lenguaje Java. Un ejemplo simple podría ser el que muestra el Código fuente 30. class Caja { int iTop, iLeft, iBottom, iRight; ........................... } Código fuente 30 Cuerpo de la clase. Implementación de métodos Los métodos proporcionan la operatividad y el comportamiento de la clase. De forma similar a la implementación de una clase, la implementación de un método se divide en dos partes: la declaración del método y el cuerpo del método. declaraciónDelMétodo{ declaración de variables cuerpoDelMétodo } Por otro lado, un método puede recibir parámetros en su llamada, por lo que los componentes de un método quedarían del siguiente modo: declaraciónDelMétodo( [listaParámetros] ) { declaración de variables implementación } En las siguientes secciones vamos a ver cada uno de los componentes que forman parte de la implementación de un método. Declaración del método Del mismo modo que la declaración de la clase o de sus atributos proporcionan información al compilador acerca de estos componentes, la declaración de un método proporciona una información similar, la cual es utilizada por el compilador para hacer las comprobaciones necesarias sobre la correcta utilización del mismo en el resto del código. La sintaxis de la declaración de un método es la siguiente: <Modificador (es)> <tipoDeRetorno> <nombreMétodo> ([<listaParámetros>]) [throws <listaExcepciones>]{ } 75 Programación en Java © Grupo EIDOS Si detallamos los modificadores del método: [public | protected | private] [static] [final] [abstract] [native] [synchronized] <tipoReturn> <nombreMétodo> ([<listaParámetros>]) [throws <listaExcepciones>]{ } Y la sintaxis mínima necesaria para realizar la declaración de un método es: <tipoDeRetorno> <nombreMétodo> ([<listaParámetros>]){ } Algunos de los elementos que aparecen en la declaración general de un método son iguales que los que aparecían en la declaración de atributos y tienen el mismo significado, vamos a comentar cada uno de ellos. En un primer lugar tenemos los modificadores de acceso. Los modificadores de acceso de un método son los mismos que aparecían en los atributos, es decir, public, private, protected y el modificador por defecto o package, y además tienen el mismo significado. También los métodos presentan los modificadores de contenido static y final. Un método static es aquel cuya implementación es la misma para todos los objetos de la clase, y además un método static no puede acceder a los atributos de instancia de la clase, sólo tiene acceso a los atributos static de la clase (atributos de clase). Los métodos static pueden ser invocados sin necesidad de instanciar un objeto de la clase. Un ejemplo del modificador static lo encontramos en la clase Math, en la que todos sus métodos son estáticos, y por lo tanto nos permite realizar operaciones matemáticas sin tener que crear un objeto de esta clase. Por ejemplo veamos el Código fuente 31. Math.sin( 18 ); // Devuelve el seno de 18 Math.abs( -37 ); // Devuelve el valor absoluto de –37 Código fuente 31 Aunque nada nos impide crear un objeto de la clase y acceder a sus métodos a través del objeto. El modificador de contenido final en la declaración de métodos tiene un significado distinto que en la declaración de atributos. Cuando el modificador se aplica a un método, indica que éste no puede reemplazarse cuando se hereda de la clase, es decir, las clases hijas no pueden sobrescribir el método de la clase padre o superclase. Otro modificador que presenta la declaración de métodos es abstract, este modificador ya no aparece en la declaración de atributos, sino que aparecía en la declaración de clases. Un método abstract no tiene implementación, es decir, es un método con el cuerpo vacío y que debe encontrarse en una clase abstract. La implementación de los métodos abstractos se realizará en las clases hija de la clase abstracta que contenga dichos métodos, para más información sobre el tema consultar el apartado Clases Abstractas dentro de este mismo capítulo. Un modificador sólo aplicable a métodos es native. Este modificador indica que el método ha sido escrito en código nativo, es decir, en un lenguaje dependiente de la plataforma como puede ser C o C++. Por ello, estos métodos constan tan sólo de la declaración, no teniendo cuerpo. 76 © Grupo EIDOS 5. POO en Java: clases native long Cálculo(); Código fuente 32 Estos métodos residen en archivos externos de código fuente en el lenguaje nativo correspondiente y es necesario seguir unas normas determinadas para poder integrarlos con el resto de la aplicación Java, pero estas son características avanzadas del lenguaje que se escapan de los objetivos del presente curso. El último modificador utilizado para la declaración de métodos es synchronized. Si indicamos que un método es de tipo synchronized, la Máquina Virtual de Java se comportará de la siguiente forma: cuando un proceso esté accediendo a ese método, los demás procesos que deseen acceder al mismo método deberán esperar en una cola hasta que el proceso en curso termine. Puesto que Java permite que varios procesos se estén ejecutando al mismo tiempo, debe proporcionar los medios para crearlos, destruirlos y fabricarlos fiables. Para esto último sirve este modificador. Si, por ejemplo, escribimos un método que sabemos de antemano que puede ser llamado por varios procesos concurrentemente, en la mayor parte de los casos deberemos impedir que más de un proceso acceda al método (o a parte de él) mientras que otro proceso lo está haciendo. A continuación y para tener una visión general de todos los modificadores, se incluye una tabla que muestra en que casos se puede aplicar cada uno de los modificadores. Aplicabilidad de los Modificadores Clase Método Atributo Predeterminado (package) Sí Sí Sí public Sí Sí Sí protected No Sí Sí private No Sí Sí static No Sí Sí final Sí Sí Sí synchronized No Sí No native No Sí No abstract Sí Sí No Tabla 19 Una vez indicados los modificadores del método se debe indica el tipo del valor de retorno del método. En Java, un método puede devolver un valor o no devolver nada. En el primer caso, al 77 Programación en Java © Grupo EIDOS declarar el método se tiene que especificar el tipo de dato que se va a devolver, en el segundo caso, hay que declararlo como de tipo void. Para devolver el valor deseado, se utiliza la sentencia return seguida del valor, el cual puede ser el resultado de una expresión de cualquier tipo, lo que obviamente incluye el contenido de un dato de la clase o una variable, o bien un tipo primitivo (true, null, int, etc.). Un método que no sea declarado como void, debe contener en su implementación una sentencia return para devolver el valor correspondiente, que debe ser del mismo tipo que el indicado en la declaración del método. Un ejemplo podría ser el que muestra el Código fuente 33. public boolean estaVacio(){ if (items.size()==0) return true; else return false; } Código fuente 33 Una vez indicados los modificadores y el tipo del valor de retorno del método, lo siguiente que debemos facilitar es el nombre del método, el nombre puede ser es cualquier identificador válido en Java. Respecto al nombre de los métodos hay una serie de consideraciones: • Puesto que Java soporta sobrecarga de métodos, varios métodos dentro de la misma clase pueden tener el mismo nombre. • Si un método tiene el mismo nombre que la clase, es considerado el constructor de la clase. • Los métodos de las clases hijas, pueden redefinir los de las clases padres, pero en tal caso, ambos tienen que tener el mismo nombre, el mismo valor de retorno y la misma lista de parámetros (más información en el apartado Herencia). A continuación del nombre del método aparece la lista de parámetros que puede recibir el método. Si el método no recibe ningún parámetro sólo se indican los paréntesis. En el Código fuente 34 se muestra la declaración de un método con parámetros. void MiVentana( int iTop, int iLeft, int iBottom, int iRight, sTítulo,boolean BarraEstado ){ <implementación> } Código fuente 34 Como se puede observar, los argumentos que un método recibe son parejas de <tipo> <nombreVariable>, separados por comas. Este método recibe cuatro argumentos de tipo numérico entero, que son las coordenadas de la ventana, un argumento de tipo cadena que es el título de la ventana y un argumento lógico (booleano) que 78 © Grupo EIDOS 5. POO en Java: clases indica si la ventana tiene o no barra de estado. Veamos ahora algunas otras consideraciones acerca de los argumentos en Java: • En Java, cualquier tipo de dato válido puede ser pasado como argumento a un método: tipos primitivos, objetos, arrays, etc. • A diferencia de C, en Java, no se puede pasar una función (método en el caso de Java) como argumento a un método, pero se puede pasar el objeto e invocar el método deseado. • Cuando los nombres de los parámetros que recibe un método son iguales a los de los atributos de la clase, los parámetros son ocultados por los atributos, siendo necesario anteponer la palabra reservada this a los atributos para poder referirse a ellas (más adelante retomaremos la palabra reservada this), this representa una referencia a la clase actual. class Caja{ int iTop, iLeft, iBottom, iRight; Caja( iTop, iLeft, iBottom, iRight ){ this.iTop = iTop; this.iLeft = iLeft; this.iBottom = iBottom; this.iRight = iRight; } } Código fuente 35 • De lo dicho anteriormente, se puede deducir que no se permite que el nombre de un argumento sea igual al de las variables locales al método. Y obviamente, no se permite la duplicidad de nombres de argumentos para el mismo método. • En Java, los argumentos se pasan por valor, no por referencia, esto quiere decir, que para los datos de tipos primitivos, lo que se pasa es el valor de la variable, y para los datos de tipo referencia (como son los objetos) se pasa la referencia, pero ésta no puede ser cambiada. De este modo, si lo que se desea es cambiar un valor, es necesario pasar como argumento al método un objeto que contenga ese valor en un atributo, y que incluya algún mecanismo para poder modificarlo, ya sea porque el atributo es público y se puede acceder a él directamente, o porque el objeto dispone de un método para poder alterar el atributo. Después de la lista de parámetros del método podemos utiliza la cláusula throws. Esta cláusula se utiliza para lanzar los errores (excepciones) que puedan producirse en el método. A continuación de esta cláusula aparece el nombre de las las clases de las excepciones que se pueden lanzar, separadas entre comas. El tratamiento de excepciones lo veremos con detenimiento en el apartado correspondiente en el próximo capítulo. Un ejemplo de la utilización de la cláusula throws lo podemos ver en el Código fuente 36. public void borraFichero() throws IOException{ } Código fuente 36 79 Programación en Java © Grupo EIDOS Declaración de variables Esta sección, al igual que la siguiente, ya formaría parte del cuerpo del método. Tras la sección de declaración del método, se pueden declarar las variables que el método vaya a necesitar. Estas son locales al método y tanto su visibilidad como su longevidad están inscritas dentro del cuerpo del método. Estas variables también se denominan objetos locales. Los variables locales son creadas cada vez que el método es invocado y destruidas al terminar éste y son sólo conocidas por el método, sin embargo los atributos existen mientras exista el objeto y son conocidas (al menos) por todos los métodos de la clase. En el Código fuente 37 se puede observar la declaración de variables locales en un método. class Caja{ // Variables miembro de la clase int iTop, iLeft, iBottom, iRight; // Constructor Caja( iTop, iLeft, iBottom, iRight ){ .................... } // Extraer el área de la caja long Area(){ long lArea; // Variable local lArea = iTop * iLeft; return lArea; } } Código fuente 37 Implementación del método Implementar un método es simplemente escribir el código necesario para que el método realice la función para la que ha sido creado. Hay tres palabras clave del lenguaje que merecen una mención especial a la hora de implementar un método, estas palabras son: this, super y return. • this: con esta palabra, podemos referirnos a los miembros de la clase. Como ya vimos representa una referencia a la clase actual. Dentro de la implementación de un método utilizaremos this para deshacer la ambigüedad que se produzca entre los nombres de los atributos de la clase y el nombre de los parámetros del método, como ya vimos anteriormente. Para más información acerca de this, véase el apartado Herencia. Un ejemplo del uso de this se muestra en el Código fuente 38. class HSBColor { //atributos de la clase int hue, saturation, brightness; //constructor HSBColor (int hue, int saturation, int brightness) { this.hue = hue; this.saturation = saturation; 80 © Grupo EIDOS 5. POO en Java: clases this.brightness = brightness; } } Código fuente 38 • super: si un método de una clase hija redefine (sobrescribe) un método de su clase padre, se puede referir al método redefinido anteponiendo super. Esta palabra reservada también se puede utilizar para acceder a los atributos de la clase padre, cuando estos sean redefinidos por atributos de la clase hija. Para más información acerca de super, véase el apartado Herencia. • return: la palabra clave return permite devolver un valor desde un método, este valor puede ser cualquier tipo de dato válido de Java. Veamos un ejemplo del uso de return en el Código fuente 39. class Test { void nada( String s ) { System.out.println( "Cadena: " + s ); } int entero( int i ) { return i * i; } boolean lógico( boolean b ) { return ! b; } String cadena( String s ) { if( s.length ) == 0 return null; else return s.toUpper(); } } Código fuente 39 A la vista del Código fuente 39 se deben realizar los siguientes comentarios: • Los métodos devuelven un valor consecuente con la declaración formal que se ha hecho de los mismos. En caso de que no se hiciera así, el compilador reportaría un error en la coincidencia de los tipos y no permitiría generar la clase correspondiente. • El primer método al ser declarado de tipo void, no contiene la sentencia return. • En el caso de que un método tenga que devolver un objeto, el tipo de dato del objeto devuelto tiene que ser o una subclase de la clase indicada o la propia clase indicada, reportándose el error de tipos no coincidentes en cualquier otro caso. Una clase puede tener tantos métodos con el mismo nombre como se desee, siempre que el número y/o tipo de argumentos que cada método recibe sea diferente. Como el compilador guarda durante el proceso de compilación los parámetros junto con el nombre del método, sabe a quién va dirigida en cada caso la llamada. Esta capacidad presente en la POO se denomina sobrecarga En Java existe sobrecarga de métodos, pero no de operadores, como en C++. 81 Programación en Java © Grupo EIDOS Herencia En este apartado vamos a retomar los conceptos sobre la herencia en la Programación Orientada a objetos, y vamos a comentar como implementa el lenguaje Java el mecanismo de herencia. En Java, como en todo lenguaje de POO se pueden heredar unas clases de otras. A la clase heredada se le llama, subclase o clase hija, y a la clase de la que se hereda superclase o clase padre. De hecho, en Java todas las clases heredan, ya sea explícita o implícitamente, de la clase Object (esta clase se verá con detenimiento en el apartado correspondiente del siguiente capítulo). Al heredar, la clase heredada toma directamente el comportamiento de su superclase, pero puesto que ésta puede derivar de otra, y esta de otra, etc., una clase toma indirectamente el comportamiento de todas las clases de la rama del árbol de la jerarquía de clases a la que pertenece. Se heredan los atributos y los métodos, por lo tanto, ambos pueden ser redefinidos en las clases hijas, aunque lo más común es redefinir métodos. Gracias a la herencia, las clases se van convirtiendo en más especializadas. Muchas veces las clases, especialmente aquellas que se encuentran próximas a la raíz en el árbol de la jerarquía de clases, son abstractas, es decir, sólo existen para proporcionar una base para la creación de clases más específicas, y por lo tanto no puede instanciarse objetos de ellas; son las clases abstractas (en otros lenguajes de POO se denominan clases virtuales). Veamos ahora algunas de las particularidades de la herencia en Java: • En Java una clase no puede tener más que una clase padre o superclase, es decir, no se admite la herencia múltiple. • Java proporciona un gran control a la hora de manejar la herencia de clases, indicando qué se puede heredar y qué no, qué se puede redefinir y qué no así como qué hay que redefinir. • Se heredan los atributos y métodos public y protected. • Se heredan los atributos y métodos declarados con el acceso por defecto (package), siempre que la clase hija se encuentre dentro del mismo paquete que la clase padre. • No se heredan aquellos atributos y métodos que la subclase declara con el mismo nombre que en la superclase. Aunque se puede acceder a los atributos y métodos de la superclase mediante el uso de la palabra clave super, como veremos a continuación. • No se heredan los atributos y métodos private. Cuando una clase hereda de otra se indica al declarar la clase, y para ello se utiliza la palabra clave de Java extends. La sintaxis es la siguiente (para consultar la sintaxis completa de la declaración de una clase, véase en apartados anteriores): class <NombreClase> extends <NombreSuperClase>{ .......................... } Por ejemplo, veamos el Código fuente 40. 82 © Grupo EIDOS 5. POO en Java: clases class Marco extends Area{ <codificación de la clase> } Código fuente 40 Con la palabra reservada this, podemos referirnos a los atributos y métodos de la clase, permite diferenciar entre los argumentos de los métodos y los atributos de la clase con el mismo nombre. De hecho, siempre que dentro del cuerpo de un método nos refiramos a cualquier miembro de la clase, ya sea un atributo o método, podemos anteponer this, aunque en caso de no existir duplicidad, el compilador asume que nos referimos a un miembro (método o atributo) de la clase. Aquí bajo la denominación miembro se engloba a métodos y atributos. Algunos programadores prefieren utilizar this para dejar claro que se está haciendo referencia a un miembro de la clase. Al contrario que this, super permite hacer referencia a miembros de la clase padre (o a los ancestros anteriores, que no hayan sido ocultados por la clase padre) que se hayan redefinido en la clase hija. Si un método de una clase hija redefine un miembro, ya sea atributo o método, de su clase padre, es posible hacer referencia al miembro redefinido anteponiendo super. El siguiente ejemplo demuestra el uso de this y super, a la hora de redefinir o sobrescribir atributos y métodos de la clase padre. Si tenemos la clase padre UnaClase (Código fuente 41), y heredamos de ella la clase OtraClase que sobrescribe tanto el atributo de la clase padre como el método (Código fuente 42). class UnaClase { boolean bVariable; void Metodo() { bVariable = true; } } Código fuente 41 class OtraClase extends UnaClase { boolean bVariable; void Metodo() { this.bVariable = false; super.Metodo(); System.out.println(bVariable); System.out.println(super.bVariable); } } Código fuente 42 83 Programación en Java © Grupo EIDOS Entonces, el resultado de llamar a OtraClase.Método() será el siguiente: false true Ya que el método Metodo() de la clase hija llama al método de igual nombre, el cual pone a true el valor de su variable, y el de la clase hija pone a false el de la suya, aunque ambos métodos y variables tengan el mismo nombre, podemos distinguirlos al referirnos a ellos utilizando o no, según sea el caso, la palabra super. De hecho, el Código fuente 43, también podría haberse escrito como muestra el Código fuente 44 this.bVariable = false; Código fuente 43 bVariable = false; Código fuente 44 Ya que en ausencia del identificador this, el compilador asume que nos referimos al atributo de la clase. Se debe observar que por los mismos motivos, la línea que aparece en el Código fuente 45, equivale a la instrucción que muestra el Código fuente 46. super.Método(); Código fuente 45 super.bVariable = true; Código fuente 46 Cuando una clase hereda de otra puede bien ampliar el comportamiento o bien sustituir el comportamiento de los métodos de la clase padre. Veamos ambas posibilidades y algunos otros aspectos relacionados con este tema. Sustituir la implementación de un método Es muy común que en una clase se quiera cambiar completamente el comportamiento de un método de la clase de la que se hereda. Algunos métodos tienen que ser sustituidos por otros para un correcto funcionamiento de la clase heredada. 84 © Grupo EIDOS 5. POO en Java: clases Para sustituir un método de una clase superior por otro en la clase heredada, basta con crear un método en la clase hija con el mismo nombre que el que deseamos sustituir e implementar el comportamiento deseado. Veamos, por ejemplo el Código fuente 47. class A { void hacerAlgo() { // hacer lo que sea } } class B extends A { void hacerAlgo() { // aquí hacemos otra cosa } } Código fuente 47 Ampliar la implementación de un método En otras ocasiones, lo que se desea no es cambiar el comportamiento de un método de la superclase, sino ampliarlo. El caso más común es el de los constructores, donde suele llamarse al constructor de la superclase pasándole los parámetros que éste requiere y utilizándose el resto de parámetros para inicializar los valores de la subclase. Como es lógico, cuando se amplia la funcionalidad de un método, se tiene que preservar la funcionalidad antigua y añadirle la nueva. Para ello, se invoca desde el método de la clase heredada (subclase o clase hija) al método del mismo nombre de la superclase, lo que se hace con la palabra reservada super. class Padre { void elMétodo( int param1 ) { // hacer lo que sea } } class Hija extends Padre { void elMétodo( int param1, param2 ) { // llamamos al método de la clase padre super.elMetodo( param1 ); // aquí hacemos otra cosa más } } Código fuente 48 En los constructores es casi imperativo (a menos que hubiera que hacer transformaciones con los parámetros que el método recibe) llamar antes de hacer ninguna otra cosa, al constructor de la superclase. Métodos que una clase heredada no puede redefinir No se pueden sustituir los siguientes métodos (ya sea para ampliarlos o para reemplazarlos): 85 Programación en Java © Grupo EIDOS • Métodos declarados como final. Por definición, un método declarado como final no puede ser modificado en las clases heredadas. Si se intentara, el compilador generaría un error. • Métodos declarados como static: Un método static es un método de la clase, y lógicamente no puede ser sustituido. Métodos que una clase heredada tiene que redefinir En Java se pueden declarar métodos que existen sólo para ser redefinidos, estos son los métodos definidos con el modificador abstract. Las clases que hereden de una clase con métodos abstract deben obligatoriamente redefinir los métodos abstractos, ya que sino no tendrían ningún contenido. En el siguiente apartado se comenta con detenimiento las clases abstractas. Clases abstractas En este apartado vamos a comentar el concepto de clase abstracta y como es implementado el mismo en el lenguaje Java. De forma general podemos definir una clase abstracta como una clase que no es instanciable, y que se utiliza en el diseño de una jerarquía de clases. Si una clase contiene uno o más métodos abstractos, tiene que ser definida como abstracta. Los métodos abstractos no tienen contenido alguno, no tienen cuerpo, por lo que estas clases se utilizan solamente con fines de diseño, no de implementación, como ya habíamos indicado anteriormente no se pueden instanciar objetos de una clase abstracta. Cuando un método es abstracto, éste, al no tener contenido, tiene que ser redefinido e implementado en las clases hijas de la clase abstracta, es decir, una clase abstracta se suele situar en la raíz de una jerarquía de clases. Las clases abstractas suelen definir conceptos genéricos que no deben concretar en ningún objeto. En el mundo real podemos encontrar una clase abstracta como la clase Comida, sería una clase abstracta ya que nunca vemos objetos Comida, comemos objetos Manzana, Fresa, Filete, Sopa, etc. La clase Comida representa el concepto abstracto de las cosas que podemos comer, pero nunca vamos poder a comer un objeto Comida. De forma similar, en la Programación Orientada a Objetos, hay veces en las que necesitamos modelizar un concepto abstracto (como en el caso de Comida) sin tener que crear una instancia de ello. Por ejemplo la clase Number de Java representa el concepto abstracto de los números. Tiene sentido modelizar y definir el concepto de número en un programa, pero no tiene sentido crear un objeto genérico de número. La clase Number va a ser la clase padre de las clases Integer y Float, estas dos otras clases ya implementan un tipo específico de números. Por lo tanto una clase abstracta puede ser únicamente subclasificada, es decir, se puede heredar de ella, pero no se puede instanciar. La clase abstracta Number estaría declarada como indica el Código fuente 49. Como ya hemos indicado una clase abstracta puede contener métodos abstractos (y también métodos no abstractos), que no van a contener ninguna implementación, por lo tanto mediante estos métodos podremos definir el comportamiento que van a tener las clases que hereden de la clase abstracta. 86 © Grupo EIDOS 5. POO en Java: clases abstract class Number{ .......... } Código fuente 49 Las clases hijas de la clase abstracta deberán implementar (redefinir) obligatoriamente todos los métodos abstractos de la clase padre. Hay sin embargo tres tipos de métodos que no pueden hacerse abstractos: • Constructores: como es lógico, un constructor no puede ser abstracto ya que éste tiene que inicializar el objeto. • Estáticos: los métodos static no pueden ser abstractos porque no pueden ser redefinidos en las clases hijas. • Privados: los métodos abstractos tienen que ser redefinidos en las clases hijas, pero por definición, los métodos private no son visibles desde las clases heredadas por lo que esta labor se hace imposible. A continuación se muestra un ejemplo del uso de clases abstractas. Tenemos una clase abstracta denominada ObjetoGrafico, que representa un objeto que podemos dibujar. Esta clase ofrece dos atributos, x e y que indican la posición en la que se encuentra el objeto a dibujar; también ofrece dos métodos, el método moverA(), que no es abstracto y que permite desplazar el objeto a un lugar determinado, y un segundo método que si es abstracto y que de llama dibujar(), y su función es la de dibujar la figura. El método moverA() se implementa en la clase abstracta, ya que desplazar un objeto gráfico a una posición se hace siempre igual con independencia del tipo de objeto. Sin embargo, el método dibujar() será distinto en cada tipo de objeto gráfico o figura, no se dibuja igual una circunferencia que un rectángulo. El código de la clase ObjetoGrafico aparece en el Código fuente 50. abstract class ObjetoGrafico{ int x,y; .......... void moverA(int nuevaX, int nuevaY){ //implementación del método } abstract void dibujar(); } Código fuente 50 De la clase ObjetoGrafico heredan otras dos clases: Rectangulo y Circunferencia. Por lo tanto la jerarquía de clases quedaría como muestra la Figura 4. Las clases Rectangulo y Circunferencia, al heredar de ObjetoGrafico, deben redefinir o sobrescribir el método abstracto dibujar() para que tenga un contenido. 87 Programación en Java © Grupo EIDOS Figura 4 El aspecto que pueden tener las clases Rectangulo y Circunferencia es el que se muestra en el Código fuente 51. class Rectangulo extends ObjetoGrafico{ void dibujar(){ //implementación del método } } class Circunferencia extends ObjetoGrafico{ void dibujar(){ //implementación del método } } Código fuente 51 88 POO en Java: otros conceptos Introducción Este es el último capítulo de los dedicados a la forma en la que el lenguaje Java implementa el paradigma de la Programación Orientada a Objetos. En los capítulos anteriores hemos visto el tratamiento e implementación que el lenguaje Java hace de los objetos y clases, y en este capítulo vamos a ver el concepto de interfaz y excepción. Además comentaremos un concepto propio del lenguaje: los paquetes, describiendo de forma breve los paquetes principales que ofrece Java. Terminaremos realizando una descripción de la clase raíz de la jerarquía de clases de Java, es decir, la clase Object. Interfaces Un interfaz en Java es un conjunto de constantes y de métodos que no poseen una implementación, un interfaz define un comportamiento o un protocolo. Vamos a ver el concepto de interfaz a través de un ejemplo. Un interfaz podrá ser RobarCasa, en este interfaz se define el protocolo o comportamiento necesario para robar una casa. Este interfaz tiene los métodos accederCasa(), buscarBotin() y abandonarCasa(). Estos tres métodos indican a grandes rasgos los pasos para robar una casa. Programación en Java © Grupo EIDOS El primero de los métodos, consiste en conseguir entran en la casa, este método no tiene ninguna implementación, al igual que todos métodos que formen parte de un interfaz, ya que cada ladrón tendrá un procedimiento determinado: forzar la puerta, entrar por la ventana, etc. Lo mismo ocurre con los otros dos métodos. De esta forma la clase que implemente el interfaz obligatoriamente debe dar un contenido determinado a todos los métodos indicados por el interfaz. En el caso del ejemplo anterior podemos tener dos clases LadronPiso y LadronChalet, que van a implementar el interfaz RobarCasa. Cada uno de estos dos tipos de ladrones implementará de forma distinta el interfaz, ya que no es lo mismo entran a robar en un piso décimo que en una casa de campo, pero lo que si está claro es el protocolo que van a utilizar en todos los casos los ladrones que implementen el interfaz RobarCasa: acceder a la casa, buscar los objetos de valor y huir con ellos. Se podría pensar que el interfaz se puede sustituir por una clase abstracta y que las clases que implementan el comportamiento heredaran de ella, como ya habíamos visto en el apartado anterior. Pero esto no es adecuado en la mayoría de los casos, ya que por ejemplo, la clase LadronPiso puede ser una clase que herede de la clase Persona y no podríamos hacer que heredará también de la clase Robo, ya que en Java no se permite la herencia múltiple. Una vez comentado el concepto de interfaz e ilustrado con ejemplos en la vida real, vamos a ver como definimos en Java un interfaz. La definición de un interfaz tiene dos partes o secciones: la declaración del interfaz y el cuerpo del interfaz. Declaración del interfaz La declaración de un interfaz tiene la siguiente sintaxis general: [public] interface NombreInterfaz [extends <NombreInterface1> [, <NombreInterface2>, ... ]]{ } Un interfaz se puede declarar como público con el modificador public, para que lo pueda implementar cualquier clase, pero sino indicamos nada (por defecto package), sólo se podrá utilizar en las clases que se encuentren en el mismo paquete que el interfaz. La palabra reservada interface indica que lo que estamos definiendo es un interfaz. A continuación aparece el nombre del interfaz que puede ser cualquier identificador válido del lenguaje Java. A continuación se indican los interfaces de los que se hereda, en el caso de los interfaces si que se permite la herencia múltiple, como se puede observar. Un interfaz hereda todos los métodos y constantes de sus interfaces padre. Para heredar de los interfaces padre se sigue utilizando la palabra reservada extends. Cuerpo del interfaz En el cuerpo del interfaz nos encontramos la definición de los métodos y constantes del interfaz. 90 © Grupo EIDOS 6. POO en Java: otros conceptos Los métodos declarados en el interfaz son implícitamente abstractos, ya que no ofrecen ningún tipo de implementación, esta implementación la realizarán las clases que implementen el interfaz. Por lo tanto no es necesario utilizar la palabra reservada abstract con los métodos de un interfaz. Los atributos que se definen en un interfaz son implícitamente constantes, y es como si se aplicaran los modificadores public static final. Aunque no es necesario utilizarlos, y como regla de estilo los modificadores static y final nunca aparecen en la declaración de los atributos (en este caso constantes) de un interfaz. Un ejemplo de código de un interfaz lo tenemos en el Código fuente 52 que representa el comportamiento de un reloj despertador. public interface Despetador{ //atributos del interfaz public long UN_SEGUNDO=1000; public long UN_MINUTO=60000; //método del interfaz public void despertar(); } Código fuente 52 La clase Reloj implementaría este interfaz dándole un contenido al método despertar(). Excepciones En el presente apartado vamos a comentar el mecanismo de errores y recuperación de los mismos que nos ofrece el lenguaje Java. Una excepción lo podemos definir como un suceso excepcional que se produce durante la ejecución de un programa y que interrumpe el flujo normal de las instrucciones del programa. Existen distintos tipos de errores que pueden causar excepciones, desde problemas en el hardware a problemas en la programación, al acceder por ejemplo a un elemento de un array que no existe. En Java, cuando se produce un error en un método, el método crea un objeto excepción y se lo pasará al entorno de ejecución. Este objeto contendrá información acerca del error que se ha producido. El entorno de ejecución es el responsable de encontrar el código que trate el error que se ha producido. En la terminología del lenguaje Java, crear un objeto excepción y pasárselo al entorno de ejecución se denomina lanzar una excepción. Los candidatos a manejar o tratar el error que se ha producido son los métodos que se encuentran en la pila de llamadas del método en el que se ha producido el error, es decir, los métodos que se han invocado antes. El entorno de ejecución va buscando hacia atrás en la pila de llamadas desde el método en el que se ha producido el error, hasta que encuentra el método que tiene el tratamiento de la excepción adecuado. Un tratamiento de excepción adecuado se produce cuando la excepción que se trata es del mismo tipo que la que se ha lanzado al producirse el error. Cuando se trata la excepción en la terminología Java se dice que se ha atrapado la excepción. 91 Programación en Java © Grupo EIDOS Si el entorno de ejecución no encuentra ningún método que trate la excepción se detendrá la ejecución del programa. En Java un método en el que se pueden dar excepciones, tiene dos posibilidades, o atrapar la excepción o lanzarla. Un método atrapará una excepción ofreciendo un manejador de la excepción. Más adelante mostraremos como se implementan en Java. Por el contrario, si un método no atrapa una excepción deberá lanzarla, para que sea atrapada por otro método. Java tiene diferentes tipos de excepciones, excepciones de entrada/salida, en tiempo de ejecución e incluso podemos crear nuestras propias excepciones. Las excepciones en tiempo de ejecución son aquellas que se producen durante la ejecución del programa, es decir, en su entorno de ejecución. Este tipo de excepciones incluye: errores aritméticos, de punteros, acceso a índices, etc. Estas excepciones no son obligatorias tratarlas, sin embargo hay otras, que no son de tiempo de ejecución, y que el compilador si obliga a tratarlas, es decir, el método debe lanzarlas o atraparlas. Estas excepciones se denominan excepciones verificadas. A continuación vamos a explicar como lanzar nuestras excepciones dentro de un método. Si queremos lanzar una excepción en un método utilizaremos la sentencia throw. Esta sentencia requiere un único argumento: un objeto que descienda de la clase Throwable. La sintaxis de esta sentencia es: throw SubClaseThrowable Para indicar que un método puede lanzar una excepción se utiliza la cláusula throws, ya que Java nos obliga a atrapar la excepción, o bien a lanzarla, como es el caso. La cláusula se sitúa a continuación de la lista de parámetros del método. Un ejemplo de un método que genera una excepción e indica que la va a lanzar es el que muestra el Código fuente 53. public Object pop() throws EmptyStackException{ Object obj; if (size==0) //si la pila esta vacía se crea una instancia de la excepción throw new EmptyStackException(); //se desapila el elemento de la pila obj=objectAt(size-1); setObjectAt(size-1,null); size--; return obj; //se devuelve el elemento desapilado } Código fuente 53 En el ejemplo anterior el método pop() desapila un elemento de una estructura de datos pila, pero si la pila está vacía crea una instancia de la clase EmptyStackException y la lanza. En esta caso la excepción la hemos lanzado nosotros mismos, pero hay otras ocasiones en las que invocamos a un método de un objeto determinado, y este método es el que va a lanzar la excepción, en este caso, en el método nuestro en el que le estemos llamando deberemos utilizar la cláusula throws 92 © Grupo EIDOS 6. POO en Java: otros conceptos para indicar que la excepción no la vamos a tratar y que nuestro método también la va a lanzar. Así por ejemplo, si queremos llamar al método pop() desde otro método distinto, y éste nuevo método lanza la excepción del método pop(), escribiremos el Código fuente 54. public miMetodo() throws EmptyStackException{ Object objeto; objeto=pop(); } Código fuente 54 Ya hemos visto como lanzar excepciones, ahora debemos ver como atraparlas. En Java para atrapar y tratar excepciones se utilizan las palabras reservadas try, catch y finally, formando parte de un bloque que va a ser el manejador de la excepción. Para tratar excepciones, el primer paso es declarar un bloque try que englobe a todas las sentencias susceptibles de producir una excepción. Si se produce alguna excepción en los métodos comprendidos dentro del bloque try, se ejecutará el manejador de excepciones asociado al bloque try. try{ //sentencias que pueden lanzar excepciones } El manejador de excepciones asociado a un bloque try se indica mediante uno o más bloques catch. La sintaxis de la sentencia catch es: catch(SubClaseTrowable nombreVariable){ [//sentencias que manejan la excepción] } En su forma más sencilla el bloque catch puede ir vacío, sin ninguna sentencia. Los parámetros de catch declaran una variable del tipo de excepción que se desea atrapar. Mediante la variable de la excepción se recogerá el objeto correspondiente a la excepción que se ha producido y se utilizará para consultar la información que ofrece. Podemos utilizar para un mismo bloque try varios bloques catch, de esta forma podremos indicar diferentes tipos de excepciones a tratar. Se debe ir desde la excepción más particular a la más general, para que no se oculten entre sí, cuando se produzca la excepción se ejecutará el bloque catch que corresponda al tipo de excepción en cada caso. De esta forma la sintaxis de los bloques try y catch quedaría: try { . . . } catch (SubClaseTrowable nombreVariable) { . . . } catch (SubClaseTrowable nombreVariable) { . . . } Como hemos visto a un bloque try le deben corresponder uno o más bloques catch, y hay veces en las que también le puede corresponder un bloque finally, aunque no de forma obligatoria. 93 Programación en Java © Grupo EIDOS En un bloque finally escribiremos todo el código de limpieza (liberar objetos, ficheros, etc.) en el caso de que se produzca una excepción, finally nos asegura que todas las sentencias de su bloque se ejecutarán cuando se produzca la excepción, una vez tratada por el bloque catch correspondiente. En el Código fuente 55 ofrecemos un esquema de código que relaciona estos tres tipos de bloques, así un bloque try tendrá uno o más bloques catch y uno o ningún bloques finally. try { . . . } catch (SubClaseTrowable nombreVariable) { . . . } [catch (SubClaseTrowable nombreVariable) { . . . } [....] finally{ ..... } Código fuente 55 En siguientes capítulos volveremos a retomar el tema de las excepciones en diversos ejemplos, ya que es una práctica común en la programación en Java. Paquetes El término paquete dentro de la programación en Java ya lo hemos mencionado en algunos momentos del curso, ha llegado la hora de ver los paquetes en profundidad. Los paquetes es una forma de clasificar y organizar clases e interfaces, para evitar conflictos de nombres, para localizar una clase de forma más sencilla y rápida y para controlar el acceso (modificador de acceso por defecto) a las clases. Las clases que conforman la jerarquía de clases de Java se encuentran organizadas y son miembros de distintos paquetes, de esta forma las clases básicas del lenguaje están en el paquete java.lang, las clases de entrada/salida en el paquete java.io, etc. Es decir, cada paquete contiene una serie de clases e interfaces relacionados. Para crear un paquete simplemente deberemos situar la línea que muestra el Código fuente 56 al principio del fichero fuente de la clase o interfaz que queremos que pertenezca al paquete. package nombrepaquete; public class MiClase{ } Código fuente 56 Deberemos incluir la sentencia package en cada fichero fuente de las clases e interfaces que formen parte de un paquete determinado. Si en un fichero fuente se definen varias clases, todas ellas pertenecerán al paquete indicado por la sentencia package. 94 © Grupo EIDOS 6. POO en Java: otros conceptos Si no utilizamos una sentencia package al principio del fichero fuente se considera que las clases o interfaces pertenecen al paquete por defecto, que es un paquete sin nombre. Los paquetes nos permiten tener distintas clases con el mismo nombre si se encuentran en paquetes diferentes, el compilador distingue las clases a través de los paquetes. Para la nomenclatura de paquetes existe un convenio establecido, las compañías desarrolladoras de software utilizan su nombre de dominio en Internet como nombre de paquete. Normalmente se sigue la siguiente nomenclatura: com.compañia.paquete, como se puede observar no se utilizan letras mayúsculas. Así por ejemplo, un paquete de clases gráficas desarrollado por la empresa Grupo EIDOS sería: com.eidos.graficos. Podemos decir que el nombre completo de una clase se compone de: el nombre del paquete del que forma parte y el propio nombre de la clase. De esta forma si la clase Circunferencia forma parte del paquete del ejemplo anterior, su nombre completo sería: com.eidos.graficos.Circunferencia. Si hacemos referencia a esta clase de la siguiente forma: Circunferencia, decimos que estamos utilizando la nomenclatura reducida. Dentro del código fuente de una clase nos podremos referir a otra clase utilizando la nomenclatura reducida si la clase que estamos definiendo se encuentra en el mismo paquete que la que estamos referenciando en nuestro código. Pero para poder hacer referencia a una clase que se encuentra en un paquete distinto con esta nomenclatura reducida deberemos importar la clase desde su paquete correspondiente. Para importar una clase en un paquete deberemos utilizar la sentencia import antes de la declaración de cualquier interfaz o clase, y facilitando el nombre completo de la clase que queremos importar. La sintaxis de esta sentencia es: import nombrepaquete.NombreClase Si necesitamos importar varias clases de varios paquetes irán las diferentes sentencias import unas debajo de otras, como muestra el Código fuente 57. import com.eidos.graficos.Rectangulo; import com.eidos.graficos.Triangulo; import java.awt.Button; Código fuente 57 Para importar un paquete con todas sus clases, es decir, el paquete completo, utilizaremos en lugar del nombre de la clase, un asterisco (*), como indica el Código fuente 58. import com.eidos.graficos.*; import java.awt.*; Código fuente 58 No hay ningún tipo de eficiencia a la hora de importar una única clase de un paquete o el paquete completo. 95 Programación en Java © Grupo EIDOS Si el paquete contiene subpaquetes, mediante la notación * no se importan los subpaquetes, sino que tenemos que indicarlo de la siguiente forma: //se importan todas las clases del paquete pero no sus subpaquetes import com.eidos.graficos.*; //para importar el paquete 2d import com.eidos.graficos.2d.*; Código fuente 59 Por defecto, cuando creamos un fichero fuente, de forma implícita se importa por completo el paquete java.lang, este paquete contiene las clases básicas del lenguaje Java. De esta forma cada fichero fuente es como si tuviera la sentencia import (Código fuente 60). import java.lang.*; Código fuente 60 Además del paquete java.lang, el sistema en tiempo de ejecución del lenguaje Java importa el paquete por defecto (sin nombre) y el paquete actual. El nombre de los paquetes no es arbitrario, sino que tiene una correspondencia física en el sistema de archivos. Vamos a comentar esta afirmación detenidamente. El nombre de un fichero fuente en Java se construye con el nombre de la clase pública definido en el y la extensión .java. Es decir, en un fichero fuente sólo podremos definir una única clase como public, y además el nombre de esta clase debe coincidir exactamente (mayúsculas y minúsculas) con el nombre del fichero en la que se encuentra definida. De esta forma la clase pública Rectangulo debe estar definida en un fichero fuente llamado Rectangulo.java. Al compilar el fichero fuente se generará el fichero .class correspondiente, en este caso sería Rectangulo.class. Y el nombre del paquete en el que se encuentre la clase tiene una correspondencia con la estructura de directorios en la que se encuentra el fichero fuente. De esta forma, si la clase Rectangulo se encuentra en el paquete com.eidos.graficos, el fichero fuente, Rectangulo.java, debe encontrarse en el camino com\eidos\graficos. Ahora bien, es necesario identificar desde dónde se comienza a tomar la ruta para que se localice el fichero fuente que representa a una clase que pertenece a un determinado paquete. El compilador debe ser capaz de localizar todos los paquetes, tanto los estándar del lenguaje Java como los definidos por los programadores, es decir, el compilador debe conocer dónde comienza la estructura de directorios definida por los paquetes y en la cual se localizan las clases. Esto se consigue mediante el ClassPath (ruta o camino de clase). El ClassPath contendrá todas las rutas en las que comienzan las estructuras de directorios en las que están contenidas las clases. De forma predeterminada, todas las clases estándar de Java se indican en el ClassPath, este camino dependerá del entorno de desarrollo que hayamos elegido para construir nuestros programas Java. Vamos a comentar la forma en la que utilizan el ClassPath los distintos entornos de desarrollo que hemos elegido para el presente curso (JDK, Visual J++ y JBuilder). En la versión 1.1 del lenguaje Java las clases estándar de Java se encontraban en un fichero llamado classes.zip. 96 © Grupo EIDOS 6. POO en Java: otros conceptos En el caso de la herramienta de Microsoft Visual J++ 6.0 las clases se encuentran localizadas en diversos ficheros ZIP que se encuentran en el camino: directorioWindows\Java\Packages. Por lo tanto estas dos herramientas añaden estas rutas al ClassPath al instalarse. Sin embargo en la versión 2 del lenguaje Java el JDK 1.3 no requiere utilizar la variable ClassPath para indicar las clases estándar del lenguaje, el ClassPath sólo será utilizado para indicar clases adicionales, por ejemplo, clases desarrolladas por nosotros. El ClassPath lo podemos establecer de dos formas diferentes, a través las opciones de compilación del entorno de desarrollo, o bien a través de una variable de entorno del sistema denominada CLASSPATH. En la variable de entorno CLASSPATH se especifica, separadas entre punto y como (;), las rutas a las diferentes clases Java que deseamos localizar para poder utilizarlas en nuestro código. Para indicar la variable de entorno ClassPath directamente acudiremos al fichero AUTOEXEC.BAT en el caso de encontrarnos en sistemas operativos Windows 95/98 y añadiremos la línea correspondiente. CLASSPATH=ruta_clases_1;ruta_clases_2;... ;ruta_clases_n. Si estamos en sistemas operativos Windows NT/2000 seleccionaremos la opción Panel de Control/Sistema/Entorno, y dentro de ésta añadiremos una variable de entorno CLASSPATH si no existe, y si existe la modificaremos indicando el camino adecuado para las clases. Como ya hemos indicado, la herramienta JDK 1.3 de Sun no requiere que se establezca el ClassPath para las clases estándar, pero si es necesario para utilizar otras clases, como pueden ser clases creadas por nosotros o por terceros, en este caso estableceremos la variable de entorno CLASSPATH manualmente, como indicábamos en el párrafo anterior, Para indicar el camino a las clases desde Visual J++ debemos acudir a la opción Proyecto|Propiedades de Proyecto y seleccionar la pestaña Ruta de acceso a clases. Crearemos una nueva ruta pulsando el botón Nueva, en ese momento debemos especificar el camino en el que se encuentran las clases que queremos utilizar. En la figura 1 se puede ver la pantalla que nos permite gestionar las rutas de acceso a las clases. Figura 5 97 Programación en Java © Grupo EIDOS Principales paquetes del lenguaje Java En este apartado se van a describir brevemente los paquetes más importantes que ofrece el lenguaje Java. Estos paquetes contienen las clases principales y que más vamos a usar de la jerarquía de clases del lenguaje. Algunos de estos paquetes no los trataremos en el curso, ya que ofrecen características avanzadas de Java para las que necesaitaríamos otro curso de la extensión de este mismo o superior. 98 • java.lang: este paquete contiene las clases básicas del lenguaje, este paquete no es necesario importarlo, ya que como dijimos anteriormente, es importado automáticamente por el entorno de ejecución. El resto de los paquetes del lenguaje Java si es necesario importarlos, cuando deseemos utilizar sus clases, esto lo haremos mediante la sentencia import. En este paquete se encuentra la clase Object, que sirve como raíz para la jerarquía de clases de Java, esta clase la comentaremos en el apartado siguiente. Otras clases importantes comprendidas en este paquete son System, que representa al sistema en el que se está ejecutando la aplicación, Thread, que representa un hilo de ejecución y Exception que representa a las excepciones de forma general. De todas formas a lo largo del siguiente capítulo trataremos en más profundidad estas clases. • java.applet: en este paquete se encuentran todas las clases que son necesarias para construir los programas Java más populares, los applets. En algunos casos los applets también se denominan miniaplicaciones, término no del todo correcto, un applet puede ser una compleja aplicación, aunque muchas veces los applets que vemos en Internet parecen estar sólo destinados a tareas estéticas para las páginas Web. La clase principal es la clase Applet que representa a un programa que se ejecuta en el entorno de una página Web mostrada por un navegador. En este curso hay un capítulo completamente dedicado a los applets de Java. • java.awt: en este paquete se encuentran uno de los grupos de clases relacionadas con la construcción de interfaces de usuario, es decir, clases que nos permiten construir ventanas, botones, cajas de texto, etc. AWT son las iniciales de Abstract Window Toolkit, algo así como herramientas abstractas para ventanas. Los interfaces de usuario se podrán mostrar igualmente en aplicaciones Java y applets Java. Algunas de las clases que podemos encontrar en este paquete son Button, TextField, Frame, GridLayout, Label, etc., todas ellas útiles para crear interfaces de usuario. En este paquete encontramos un subpaquete especializado en el tratamiento de eventos, y es el que vamos a comentar a continuación. • java.awt.event: este subpaquete del paquete java.awt, es el encargado de proporcionar todas las clases que permiten realizar el tratamiento de eventos en Java. Existe una completa jerarquía de clases para representar cada uno de los eventos que se puedan producir a partir de la interacción del usuario con el interfaz de usuario. En el capítulo correspondiente trataremos ampliamente el tema de los eventos dentro de la programación en Java. En este paquete podemos encontrar clases como ActionEvent, que representa el evento que se produce cuando pulsamos un botón o MouseEvent que representa eventos del ratón. • java.io; este paquete reúne todas las clases relacionadas con la entrada/salida, ya sea para manipular ficheros, leer o escribir en pantalla, en memoria, etc. Este paquete ofrece clase como FileReader, que representa un fichero del que se quiere leer, ByteArrayOutputStream, representa un array que se quiere escribir en memoria. La entrada/salida en Java tiene una serie de complejidades y particularidades que se tratarán en el capítulo correspondiente. • java.net: aquí encontramos una serie de clases que tienen que ver directamente con la programación en Internet, como puede ser utilizar sockets, obtener información de URLs (Uniform Resource Locator), manipular direcciones IP (Internet Protocol). Es decir, este paquete tiene las clases que se encuentran relacionadas con la programación a través de la red. © Grupo EIDOS 6. POO en Java: otros conceptos En este paquete podemos encontrar las clases como: URL, que representa una localización de un recurso dentro de Internet, o Socket, que representa una conexión del lado del cliente con un servicio determinado a través de la red. A través de este paquete podremos construir aplicaciones dentro de la arquitectura cliente servidor. • java.sql: a través de este paquete se nos ofrecen todas las clases necesarias para programar en Java el acceso a bases de datos, a este conjunto de clases se le suele denominar API JDBC. El paquete java.sql se sale de los objetivos de este curso. Aunque podemos comentar que en este paquete se ofrecen clases como pueden ser Connection, que representa una conexión con la base de datos, Statement, representa una sentencia SQL, Driver, representa el driver utilizado para establecer la conexión con la base de datos. • javax.swing: este paquete reúne el segundo conjunto de clases que se utilizan para construir interfaces de usuario. Los componentes que se engloban dentro de este paquete se denominan componentes Swing, y suponen una alternativa mucho más potente que AWT para construir interfaces de usuario muchos más complejos. Este paquete es nuevo en Java 2 y tiene un gran número de clases: JFrame, JButton, JLabel, JOptionPane, JPanel, JApplet, etc. Este paquete añade nuevos componentes para la construcción de interfaces de usuario que no se encontraban presentes en el paquete java.awt, además se ofrecen nuevas características como son el aspecto y comportamiento para distintos sistemas, ventanas de diálogo configurables, componentes estructurados (JTable y JTree), capacidades para las funciones deshacer (undo), potentes manipulaciones de texto, etc. Los componentes Swing se denominan componentes ligeros ya que se encuentran completamente escritos en Java sin utilizar ningún código nativo y no depende su aspecto ni comportamiento del sistema operativo en el que se utilicen. Este paquete se encuentra englobado en lo que se denomina JFC (Java Foundation Classes). • javax.swing.event: este paquete contiene una serie de clases e interfaces relacionados con los nuevos eventos ofrecidos por los componentes Swing, como puede ser MenuListerner o TreeSelectionListener. • java.util: como su nombre indica este paquete ofrece una serie de clases que se utilizan como utilidades para el lenguaje. Algunas de estas clases son: Date, para el tratamiento de fechas, Random, para la generación de números aleatorios, Enumeration, para tratar un conjunto de objetos. • java.beans: como ya se había comentado, en capítulos anteriores, los JavaBeans son componentes software que permiten a los desarrolladores escribir y ofrecer componentes Java que pueden ser utilizados por otras herramientas de desarrollo. Este paquete nos ofrece los medios necesarios para desarrollar JavaBeans. • java.rmi: Remote Method Invocation (invocación remota de métodos, RMI) , permite crear objetos cuyos métodos pueden ser invocados por otros objetos ejecutándose en otras máquinas virtuales, incluso máquinas virtuales ejecutándose en otro host. • java.text: este paquete ofrece herramientas para la internacionalización de texto, tales como formato de fecha y formato numérico. • java.security: este paquete y sus subpaquetes ofrecen interfaces básicos para operaciones relativas a la seguridad, tales como autenticación, autorización, firma de datos y encriptación. • java.math: este paquete ofrece herramientas para manipulaciones matemáticas. • javax.accessibility: define una serie de contratos entre interfaces de usuario y tecnologías de rehabilitación. 99 Programación en Java © Grupo EIDOS A lo largo de los diferentes capítulos del curso iremos detallando algunas de las clases más importantes de estos paquetes. Además de los paquetes que hemos comentado, existen una serie de subpaquetes que ofrecen unas funcionalidades más específicas y que pasamos a exponer a continuación. • java.awt.dnd: este paquete ofrece las clases necesarias para implementar mecanismos Drag and Drop dentro de interfaces de usuario, es decir, permiten arrastrar y soltar componentes del interfaz de usuario para de esta forma intercambiar información. • java.awt.font: ofrece clases e interfaces para tratar y manipular fuentes. • java.awt.image: paquete especializado en el tratamiento y creación de imágenes. • java.awt.print: soporte para realizar tareas de impresión. • java.util.jar: ofrece clases para escribir y leer ficheros en formato JAR (Java ARchive). • java.util.zip: ofrece clases para escribir y leer ficheros en formato ZIP. • javax.sound.midi: presenta clases e interfaces para entrada/salida y síntesis de datos en en formato MIDI (Musical Instrument Digital Interface). • javax.swing.border: ofrece clases e interfaces para dibujar bordes entorno a componentes Swing. • javax.swing.plaf: mediante este paquete se ofrecen las clases e interfaces para implementar el mecanismo pluggable look and feel, es decir, el aspecto y comportamiento configurable de los componentes Swing. • javax.swing.table: permite manipular el componente Swing JTable, del paquete javax.swing, que representa la estructura de una tabla. • javax.swing.table: otro subpaquete del paquete javax.swing, en este caso permite manipular componentes de texto, tanto editables como no editables. • javax.swing.tree: permite manipular el componente Swing JTree, del paquete javax.swing, que representa una estructura de árbol. • javax.swing.undo: ofrece soporte para la funcionalidad deshacer/rehacer de aplicaciones tales como editores de texto Como se puede comprobar Java ofrece una completa jerarquía de clases organizadas a través de paquetes. La clase Object Se ha añadido este apartado dedicado a la clase Object debido a su importancia dentro de la jerarquía de clases de Java, y representa el comportamiento básico que todos los objetos deben tener. Como ya hemos comentado en alguna ocasión, la clase Object, se encuentra en la raíz de toda la jerarquía de clases que forman el lenguaje Java; y por lo tanto, toda clase escrita en Java desciende de 100 © Grupo EIDOS 6. POO en Java: otros conceptos esta clase, ya sea de forma directa, o indirectamente, ya que hereda de otra, la cual a su vez hereda de otra, y así sucesivamente hasta llegar a la clase Object. En aquellas clases en las que se no incluye la palabra extends en su declaración Java asume que la clase padre es Object. Con lo cual se hereda directamente de la clase raíz Object. A continuación vamos a comentar los distintos métodos que presenta la clase Object, ya que estos métodos los contendrán todas las clases del lenguaje Java. Constructor: Object() Éste es el constructor de la clase y simplemente crea una nueva instancia de la clase Object. Método clone() Éste método, permite obtener un duplicado del objeto. Es decir, crea un nuevo objeto de la misma clase e inicializa todos los atributos del objeto clonado asignándoles el mismo valor que el que tenían en el objeto original. Método equals() Indica si dos objetos son iguales. Para cualesquiera valores de x e y, x.equals(y) devolverá true sí y sólo si x e y se refieren al mismo objeto. Método finalize() Éste método es llamado por el recolector de basura. No hace nada, y por lo tanto para darle alguna tarea, debe ser redefinido o sobreescrito. Método getClass() Determina la clase de un objeto en tiempo de ejecución. Método hashCode() Proporciona el código hash de un objeto, y se proporciona para darle utilidad a las hashtables. Método notify() Reactiva una tarea que se encuentra en estado de espera. Método notifyAll() Reactiva todas las tareas que se encuentren en estado de espera. Método toString() Crea una cadena de texto que representa al objeto, de forma que una persona pueda identificar el objeto. Método wait() Pone en modo de espera una tarea. 101 POO en Java: un ejemplo. Resumen de conceptos Introducción Este capítulo propone un sencillo ejemplo en el que se retoman y utilizan muchos de los conceptos de la POO aplicados al lenguaje Java, y que hemos explicado en los tres capítulos anteriores. Además trataremos la herramienta de desarrollo de Sun Microsystems JDK (Java Development Kit) 1.3. Utilizaremos el JDK 1.3 para generar (compilar) nuestras clases e interfaces del ejemplo. En el siguiente capítulo trataremos las aplicaciones Java con detenimiento. Descripción del problema El ejemplo que vamos a presentar se trata de un videojuego que va a consistir únicamente en mostrar una serie de mensajes en pantalla, como se podrá comprobar no se trata de nada espectacular, pero muestra algunos de los conceptos principales de la POO aplicados al lenguaje Java. En este videojuego debemos dirigir a una serie de extraterrestres que tienen como misión la de invadir el planeta tierra.. A continuación se comentan las distintas clases que se van a utilizar en el ejemplo y la función de cada una de ellas. Programación en Java © Grupo EIDOS • Extraterrestre: clase abstracta que representa a los extraterrestres de forma general. Nunca vamos a instanciar objetos de esta clase, sino que vamos a tener dos tipos de extraterrestres que van a heredar de esta clase. • Selenita: es un tipo de extraterrestre concreto, por lo tanto heredará de la clase Extraterrestre. • Venusiano: es otro tipo de extraterrestre que hereda también de la clase Extraterrestre. • DominarPlaneta: se trata de un interfaz (comportamiento) que implementarán de forma distinta cada uno de los dos tipos de extraterrestres. • VideoJuego: es la clase principal o la clase de arranque y se va a encargar de poner el videojuego en funcionamiento instanciando los objetos de las clases correspondientes y lanzando métodos sobre ellos. Vamos a ver a continuación como implementar las distintas clases e interfaces y como utilizar de forma sencilla la herramienta de desarrollo JDK 1.3. Utilizando el JDK 1.3 Ya hemos comentado en capítulos anteriores y en este mismo capítulo, que el JDK 1.3 es la última versión del entorno de desarrollo que propone Sun Microsystems, los creadores del lenguaje Java, por lo tanto esta es la única herramienta de desarrollo en Java necesaria para seguir el presente curso. Aunque también comentaremos (ya hemos visto algo de ellas) otras dos herramientas, una de Microsoft (en el siguiente capítulo), Visual J++ 6.0 y otra de Borland, JBuilder 3.5. El JDK 1.3 recoge la última versión del lenguaje Java, es decir, lo que se denomina Java 2 o también la plataforma Java 2. Este software es gratuito y lo podemos obtener del sitio Web de Sun http://java.sun.com. Aunque curiosamente no lo encontraremos como JDK 1.3, sino que se incluye dentro de otro software denominado Java 2 SDK (Software Development Kit) Standard Edition 1.3.0. Una vez que hemos descargado el Java 2 SDK 1.3.0 procedemos a su instalación, que consiste en ejecutar el fichero que hemos descargado (j2sdk1_3_0-win.exe, en la versión Windows) y seguir los sencillos pasos que nos indica el programa de instalación. Por otro lado podemos descargar también la documentación del Java 2 SDK 1.3.0, ya que supone una referencia muy útil, tanto para expertos como para novatos del lenguaje Java. En esta documentación se detallan todas las clases e interfaces de cada uno de los paquetes que forman parte de la especificación del lenguaje Java. El software se instala por defecto en el directorio jdk 1.3. Puede ser útil incluir en él la variable PATH de Windows la ruta c:\jdk1.3\bin para poder ejecutar y compilar nuestras clases Java de forma rápida y sencilla. El JDK 1.3 funciona en forma de comandos, por ejemplo si queremos compilar una clase determinada escribiremos lo que muestra el Código fuente 61 en nuestra línea de comandos. javac NombreClase.java Código fuente 61 104 © Grupo EIDOS 7. POO en Java: un ejemplo. Resumen de conceptos Para escribir el código de nuestras clases utilizaremos un editor de textos como puede ser el bloc de notas de Windows o cualquier otro editor que no introduzca caracteres especiales. Cada clase pública debe definirse en un fichero de código fuente Java distinto, en nuestro caso vamos a tener cinco ficheros .java, ya que todas nuestras clases van a ser públicas. Además el fichero de código fuente debe tener el mismo nombre que la clase pública definida en el mismo, y debe coincidir en mayúsculas y minúsculas. Las herramientas más interesantes que ofrece el JDK y que podemos ejecutar desde la línea de comandos son las siguientes: • java: es la máquina virtual de Java, es decir, el intérprete que vamos a utilizar para ejecutar nuestras aplicaciones Java. • javac: es el compilador del código fuente de Java, generará los bytecodes correspondientes (en forma de fichero .class) y nos indicará si el código fuente es correcto. • javadoc: mediante esta utilidad se puede generar documentación de forma automática a partir de los comentarios de nuestro código fuente. • jar: utilidad que permite manejar los ficheros JAR (Java archive), ficheros comprimidos de Java. • appletviewer: es el visor de applets, ejecuta los applets fuera del contexto de un navegador Web. Estas herramientas las iremos utilizando y comentando según se vaya desarrollando el curso. Vamos a pasar a la implementación de las clases de nuestro ejemplo en el siguiente apartado. Creando las clases Vamos a comenzar por la clase abstracta Extraterrestre, que como ya hemos dicho va a representar a los extraterrestres de manera genérica. Esta clase se va componer de los siguientes elementos: • Tres atributos que, para cumplir con la normativa de la POO, se declararán como privados y que van a describir un extraterrestre. Estos atributos son del tipo primitivo de Java int y de la clase String. • Varios métodos de acceso para obtener y manipular los diferentes atributos de la clase. • Un constructor que inicializa los valores de los atributos de la clase. • Y otros métodos típicos de extraterrestres. Veamos el Código fuente 62 de la clase Extraterrestre. public abstract class Extraterrestre{ //atributos privados private int piernas; private int ojos; 105 Programación en Java © Grupo EIDOS private String color; //constructor public Extraterrestre(int piernas, int ojos, String color){ this.piernas=piernas; this.ojos=ojos; this.color=color; } //métodos public void atacarTerricola(){ System.out.println("Terrícola capturado"); } public void teletransportar(String lugar){ System.out.println("Ahora estoy en:"+lugar); } //métodos de acceso public int devuelvePiernas(){ return piernas; } public void asignaPiernas(int piernas){ this.piernas=piernas; } public int devuelveOjos(){ return ojos; } public void asignaOjos(int ojos){ this.ojos=ojos; } public String devuelveColor(){ return color; } public void asignaColor(String color){ this.color=color; } } Código fuente 62 Como se puede ver es una clase pública y abstracta y que hereda de la clase Object, ya que no hemos indicado que herede de otra clase. También se puede observar que en los métodos de acceso para diferenciar a los parámetros de los atributos de utiliza la palabra reservada this. El fichero fuente tiene el nombre Extraterrestre.java y al compilarlo con el comando javac del JDK 1.3 obtenemos el fichero .class correspondiente. Si se produce algún error en la compilación no se generará el fichero .class correspondiente y el JDK nos lo notificará por pantalla, como se muestra en la Figura 6. Pasemos ahora a la clase Venusiano. Esta clase hereda de la clase Extraterrestre, y por lo tanto presentará los mismos métodos públicos que los de su clase padre. Esta nueva clase añade un atributo y presenta un constructor sin parámetros, pero dentro de este constructor se llama al constructor de la clase padre a través de la palabra reservada super, para indicar las características de los venusianos. Veamos el Código fuente 63 de la clase Venusiano. public class Venusiano extends Extraterrestre{ //añade atributo private int cabezas; //constructor sin argumentos, todos los venusianos son iguales public Venusiano(){ //debemos llamar al constructor de la clase padre super(4,4,"Verde"); 106 © Grupo EIDOS 7. POO en Java: un ejemplo. Resumen de conceptos this.cabezas=2; } public void atacarTerricola(){ //si no queremos sobreescribir completamente el método de la //clase padre super.atacarTerricola(); //los venusianos vienen para experimentar System.out.println("Experimentar con terrícola"); } } Código fuente 63 Figura 6 Como se puede comprobar esta clase sobrescribe, no de forma total, el método atacarTerricola() de la clase Extraterrestre, se vuelve a hacer uso de la palabra reservada super. El interfaz DominarPlaneta presenta varios métodos que se pueden utilizar en este tipo de comportamiento, por lo tanto toda clase que quiera implementar este interfaz está obligada a dar un contenido a los métodos que define. El interfaz DominarPlaneta tiene el Código fuente 64. public interface DominarPlaneta { public abstract void aniquilarPoblacion(String planeta); public abstract void proclamarGobierno(String planeta); public abstract void controlarRecursos(String planeta); } Código fuente 64 Un interfaz se compilará de la misma forma que una clase. 107 Programación en Java © Grupo EIDOS La siguiente clase es la clase Selenita, que hereda también de la clase Extraterrestre y además como no vienen en son de paz implementan el interfaz DominarPlaneta, pero uno de los métodos no desean implementarlo, por lo que se deja el cuerpo del método vacío. Esta clase añade un nuevo atributo y nuevos métodos. Veamos su código, en el Código fuente 65, al igual que hemos hecho con el resto de las clases. public class Selenita extends Extraterrestre implements DominarPlaneta{ //añade atributo private String armamento; public void atacarTerricola(){ //si no queremos sobreescribir completamente el método de la //clase padre super.atacarTerricola(); //los venusianos vienen aniquilar la raza humana System.out.println("Aniquilar terrícola"); dispararArma(); } //añade método public void dispararArma(){ if (armamento.equals("cañón láser")){ System.out.println("BOUMMM"); } else{ System.out.print("Bzzzzzz"); } } //constructor con parámetro, cada selenita puede tener un arma //diferente public Selenita(String armamento){ super(2,1,"Azul"); this.armamento=armamento; } //añade método de acceso public void asignarArmamento(String armamento){ this.armamento=armamento; } //métodos del interfaz public void proclamarGobierno(String planeta){ System.out.println("Eliminar gobernantes del planeta:"+planeta); System.out.println("Gobernamos sobre el planeta "+planeta); } public void aniquilarPoblacion(String planeta){ System.out.println("Utilización de armas nucleares sobre el planeta "+planeta); } //no interesa este método public void controlarRecursos(String planeta){} } Código fuente 65 La última clase de nuestro ejemplo es la clase VideoJuego, esta clase va a ser la clase principal y se va a encargar de instanciar los distintos objetos de las clases Selenita y Venusiano que pertenecen al videojuego. Esta clase presenta un único método, el método main(), que veremos en detalle en el próximo capítulo, únicamente adelantaremos que es el método de arranque de una clase y es necesario si queremos ejecutar una clase que representa una aplicación Java. 108 © Grupo EIDOS 7. POO en Java: un ejemplo. Resumen de conceptos Veamos el código de la clase VideoJuego (Código fuente 66). public class VideoJuego{ public static void main(String args[]){ //Un venusiano: Venusiano alien=new Venusiano(); System.out.println("Es de color: "+alien.devuelveColor()); alien.teletransportar("Soria"); alien.atacarTerricola(); System.out.println(); //Un selenita Selenita alien2=new Selenita("cañón láser"); alien2.teletransportar("Madrid"); alien2.atacarTerricola(); //utilizamos el interfaz que implementa el selenita: alien2.aniquilarPoblacion("Tierra"); System.out.println(); } } Código fuente 66 Una vez compilada esta clase vamos a ejecutarla para observar el resultado de la utilización de los dos objetos de nuestro juego, el objeto de la clase Venusiano y el objeto de la clase Selenita. Para ejecutar una clase utilizamos el intérprete que ofrece el JDK, se trata del comando java. Para ejecutar una clase se debe escribir en la línea de comandos la instrucción que muestra el Código fuente 67. java NombreClase Código fuente 67 El resultado de ejecutar la clase VideoJuego es el que muestra la Figura 7. Figura 7 El código fuente completo de este ejemplo se puede obtener aquí. En el próximo capítulo se tratan las aplicaciones Java con detenimiento. 109 Aplicaciones Java Introducción Hasta ahora hemos definido primero conceptos de Programación Orientada a Objetos, luego vimos las particularidades del lenguaje Java, su sintaxis y también como implementa los mecanismos de la POO, por lo tanto ya estamos en disposición de comenzar a realizar programas Java. En este capítulo vamos a comentar todos los aspectos de uno de los tipos de programas que se puede realizar con el lenguaje Java, las aplicaciones Java. No se debe olvidar que Java ofrece dos tipos de programas: applets y aplicaciones. En este curso se ha dedicado un capítulo para cada tipo de programa. En el presente capítulo también vamos comentar y explicar como utilizar las herramientas de desarrollo Microsoft Visual J++ 6.0 y Borland JBuilder 3.5. Pero siempre teniendo en cuenta que vamos a generar código estándar del lenguaje Java. Las aplicaciones Java son aplicaciones de propósito general al igual que existen en otros lenguajes como C++ o Visual Basic, lo único que las distingue es que no vamos a generar un ejecutable como en los otros lenguajes de programación, sino que, como ya sabrá el lector, generamos ficheros de clase que se encuentran en bytecode y que son independientes de la plataforma. A la hora de ejecutar nuestra aplicación Java será necesario disponer del intérprete de Java adecuado para el sistema operativo en el que nos encontremos. Se ha creído conveniente explicar antes las aplicaciones Java que los applets Java, debido a que un applet se puede considerar un tipo particular de aplicación. Programación en Java © Grupo EIDOS En los siguientes apartados vamos a ir comentando el proceso para crear una aplicación y también nos adentraremos en algunos aspectos del lenguaje Java, según sea necesario. Introducción a Visual J++ 6 En este curso no se pretende realizar un tutorial completo de la herramienta de desarrollo de Microsoft Visual J++ 6 ni tampoco de la herramienta JBuilder 3.5 de Borland, sino que vamos a comentar aquellos aspectos que sean de un mayor interés para la programación en Java (JBuilder 3.5 lo trataremos en el apartado correspondiente). En este apartado vamos a comentar los pasos a seguir para poder realizar una sencilla aplicación Java dentro de Visual J++. MS Visual J++ 6 se encuentra formando parte de la suite de herramientas de desarrollo Microsoft Visual Studio 6, otras herramientas que podemos encontrar en este paquete son Visual C++ 6, Visual Basic 6, Visual InterDev 6... Una vez arrancado el entorno de desarrollo, seleccionando la opción correspondiente del menú de Inicio, aparece la ventana de Nuevo Proyecto, que se puede observar en la Figura 8. Se puede comprobar también que aparecen otras opciones correspondientes en este caso a Visual InterDev, esto es debido a que en mi equipo de pruebas tengo instalada esta herramienta, por lo tanto esta pantalla puede variar según los componentes que se tengan instalados de Visual Studio. Nosotros, como es evidente, nos vamos a ocupar de los Proyectos de Visual J++. Dentro de la carpeta Proyectos de Visual J++ existen otras tres subcarpetas, en este capítulo vamos a tratar la primera, la subcarpeta Aplicaciones. Figura 8 Seleccionamos la carpeta Aplicaciones, y se nos mostrarán tres tipos de aplicaciones distintas que podemos realizar con Visual J++: Aplicación Windows, Aplicación de consola y Asistente para aplicaciones. Vamos a comentar cada una de ellas: • 112 Aplicación Windows: si seleccionamos este tipo de aplicación, el entorno de desarrollo de forma automática nos creará un formulario que será el arranque de la aplicación. En este caso © Grupo EIDOS 8. Aplicaciones Java el interfaz que muestra el entorno de desarrollo es muy similar al de Visual Basic, es decir, un formulario sobre el que vamos añadiendo los controles seleccionados en la barra de herramientas correspondiente. Pero en Java y con Visual J++ no resulta tan sencillo, ya que en este tipo de aplicaciones no se utilizan las clases de interfaz de usuario estándar de Java, es decir, las clases del AWT (Abstract Window Toolkit) o componentes Swing, sino que se utilizan unas clases propietarias de Microsoft denominadas WFC (Windows Foundation Clases). Como ya comentamos al principio de este capítulo, vamos a realizar programas que sean 100% Java, por lo tanto no utilizaremos las clases contenidas en WFC y tampoco utilizaremos este tipo de aplicación Java. En la Figura 9 se puede ver lo amigable que resulta el entorno ofrecido por Visual J++. Lamentamos desanimar al alumno, pero si queremos generar código Java puro, en nuestro entorno de desarrollo nunca veremos este tipo de aplicaciones. Figura 9 • Aplicación de consola: este tipo de aplicación es la que vamos a utilizar para realizar nuestras aplicaciones Java. Representa una aplicación Java y nos ofrece un código muy básico que nos sirve de esquema para la aplicación y que comentaremos más adelante. En este caso, no disponemos de ningún formulario ni de ninguna barra de herramientas que nos permita ir añadiendo los diferentes controles a nuestra aplicación, aquí todo lo tendremos que hacer a través de código. En la Figura 10 se muestra el aspecto del entorno de desarrollo cuando se selecciona este tipo de aplicación, como se puede ver no es tan amigable como el caso anterior (por ejemplo, la barra de herramientas con las clases WFC se encuentra desactivada), pero estaremos generando código 100% Java, que es lo que nos interesa. Figura 10 113 Programación en Java • © Grupo EIDOS Asistente para aplicaciones: esta opción no es una aplicación en sí, sino que es un asistente que nos guía a través de sucesivos pasos para construir dos tipos de aplicaciones: aplicación basada en formularios y Aplicación basada en formularios con datos. Este asistente nos generará el código necesario y nos irá preguntando en cada paso nuestras preferencias, por ejemplo, información para la conexión de datos, si es necesaria una barra de menú, etc. También nos permite general para la aplicación un fichero ejecutable. Como se intuye, el código que se genera va a utilizar las clases de Microsoft, es decir, las WFC, por lo tanto, tampoco vamos a utilizar este asistente. En la Figura 11 se puede observar uno de los pasos del asistente. Figura 11 Hemos empezado a hablar de los proyectos dentro de Visual J++ pero todavía no hemos comentado que es exactamente un proyecto y que elementos forman parte del mismo. A continuación iniciamos esta tarea. Un proyecto dentro de Visual J++ va a ser un contenedor que va a agrupar desde el punto de vista organizativo todas las clases que creemos para una aplicación. Físicamente un proyecto se corresponde con un directorio en nuestro sistema de archivos, es decir, si tenemos el proyecto llamado Pruebas, existirá un directorio Pruebas que contendrá todos los ficheros que formen parte del proyecto. Fundamentalmente un proyecto va a contener ficheros fuente de Java, es decir, ficheros .java. Un proyecto se engloba en una entidad de nivel superior denominada solución. Una solución tiene la función de guardar referencias a proyectos, es decir, es una solución vamos a tener distintos proyectos. Una solución no se corresponde físicamente con un directorio, como ocurría en el caso de los proyectos, sino que se trata de un fichero .SLN que apunta a todos los proyectos que contiene. Podemos agregar a una solución tantos proyectos como queramos. 114 © Grupo EIDOS 8. Aplicaciones Java Una vez creado un proyecto, seleccionando la opción Proyectos en J++|Aplicaciones|Aplicación de consola, el entorno de desarrollo genera el Código fuente 68 para nosotros, con una serie de comentarios. /** * Esta clase puede tomar un número variable de parámetros * en la línea de comandos. La ejecución del programa comienza * con el método main(). La llamada al constructor de clase no tiene lugar a menos que se cree un objeto del tipo 'Class1' * en el método main(). */ public class Class1{ /** * Punto de entrada principal para la aplicación. * * @param args Matriz de parámetros pasados a la aplicación * mediante la línea de comandos. */ public static void main (String[] args){ // TODO: Agregar aquí el código de inicialización } } Código fuente 68 Como se puede observar, de forma automática se crea una clase pública denominada Class1, y dentro de esta clase se declara un método llamado main(), que va a ser el método de arranque de la clase y el protagonista de nuestro nuevo apartado. Como el lector habrá podido observar, delante de uno de los comentarios se añade la cadena TODO:, si a un comentario le añadimos esta cadena, aparecerá como una tarea pendiente de realizar en la lista de tareas. Esto resulta muy útil cuando dejemos en nuestro código secciones o sentencias pendientes de codificación, después consultando la lista de tareas sabremos que tenemos pendiente de realizar. Para ver la lista de tareas ( que se puede observar en la Figura 12) debemos seleccionar la opción de menú Ver|Otras Ventanas|Lista de tareas. Figura 12 Además de añadir tareas a través de comentarios especiales de código, también las podremos añadir a través de la lista de tareas de forma directa pulsando sobre la primera fila. La gestión de tareas a través de la lista de tareas es bastante útil y muy fácil de utilizar. Una vez que hemos creado una aplicación y ya la tenemos disponible, vamos a realizar con ella una tarea muy sencilla. Vamos a mostrar el típico saludo Hola Mundo a través de la pantalla. 115 Programación en Java © Grupo EIDOS Como ya veremos en el siguiente apartado, el método main() se ejecuta siempre, por lo tanto vamos a utilizar este método para mostrar el mensaje. Si eliminamos los comentarios generados por Visual J++, el código completo sería como el Código fuente 69. public class Class1{ public static void main (String[] args){ System.out.println("Hola Mundo"); } } Código fuente 69 Al escribir la sentencia anterior se habrá podido comprobar dos de las grandes ayudas que nos ofrece Visual J++ al programador. Por un lado tenemos el intellisense, este mecanismo se habrá observado al situar el punto delante de System. Este mecanismo nos ofrece una lista de los atributos y métodos que tenemos disponibles, también nos informa de los parámetros que debemos pasarle a un método, etc. La segunda ayuda es la comprobación de sintaxis que se va realizando, si se escribe alguna sentencia errónea, Visual J++ lo indicará subrayándola en rojo. Ahora vamos a pasar a ejecutar nuestra sencilla aplicación. Para ello debemos pulsar el botón de play en el entorno de desarrollo. Si lo ejecutamos de esta forma prácticamente no nos da tiempo a ver el resultado. También podemos ejecutar nuestra aplicación invocando directamente al intérprete de Java desde la línea de comandos de MS-DOS. El intérprete de Java que ofrece Visual J++ se denomina Jview (es el equivalente al comando java del JDK), y para utilizarlo desde la línea de comandos utilizaremos la sintaxis: jview NombreClase De esta forma se ejecuta una clase determinada. Si ahora nos desplazamos dónde se ha generado el fichero .class de nuestra aplicación y escribiremos jview Class1, se ejecutará nuestra clase y veremos el resultado (Figura 13). Si se intenta ejecutar una clase de una aplicación Java que no posee un método main() se producirá un error y no se ejecutará. Si queremos lanzar nuestra aplicación a través del intérprete Jview, antes deberemos compilarla para que se generen los ficheros .class correspondientes, que son los que contendrán los bytecodes. Figura 13 116 © Grupo EIDOS 8. Aplicaciones Java Para compilar una clase seleccionamos la opción de menú Generar|Generar NombreProyecto. Podemos seleccionar generar todas las clases de un proyecto o bien todas las clases de todos los proyectos incluidos en la solución de Visual J++. A la hora de generar los ficheros .class se nos informa de los errores que se hayan encontrado en nuestro código, si se producen errores, no se generarán los ficheros .class de las clases que los contengan. Los errores detectados durante el proceso de generación (compilación) de la clase aparecerán en la lista de tareas, junto con su descripción. Si pulsamos sobre un error, el cursor se desplazará a la línea del editor de código en la que se encuentra dicho error. Vamos a comentar que es lo que sucede en nuestro ejemplo cuando ejecutamos nuestra clase, ya sea desde el entorno de Visual J++ o desde la línea de comandos con Jview. En ambos casos se invoca al intérprete jview, aunque en el primer caso quede oculto porque la llamada la realiza el entorno Visual J++ por nosotros. Al intérprete se le debe facilitar el nombre de la clase principal de la aplicación, en este caso sólo tenemos la clase Class1. Una vez indicada la clase a ejecutar el intérprete comienza su trabajo y va ejecutando nuestro código línea a línea, comenzado por el método main(). En nuestro ejemplo sólo se ejecuta el método main(), ya que en realidad nuestra clase no hace nada, no posee ningún atributo ni ningún método más que el método main(), lo normal es que, como veremos en el siguiente apartado el método main() se encargue de crear una instancia de la propia clase y llame a algunos métodos de la misma para realizar alguna función. A lo largo del presente capítulo y de los siguientes iremos descubriendo y comentando las diferentes características y ayudas que nos ofrece el entorno de desarrollo Visual J++ 6. Introducción a JBuilder 3.5 En este apartado vamos a comentar la herramienta de desarrollo en Java Boland JBuilder 3.5. Realizaremos la misma acción que en el apartado anterior, es decir, construir la sencilla aplicación Hola Mundo. Avisamos desde aquí que la herramienta JBuilder 3.5 tiene unos requerimientos bastantes altos en lo que a hardware se refiere. Es necesario como mínimo un procesador Pentium II y 128 MB de RAM, y su versión para Windows es necesario Windows NT 4.0 Server o superior. Al igual que ocurría con Visual J++, JBuilder utiliza los proyectos para organizar y contener ficheros fuente de Java. Vamos a crear el proyecto que va a contener nuestra sencilla aplicación de ejemplo, para ellos seleccionamos la opción Archivo|Nuevo proyecto, y en ese momento se iniciará el asistente de proyectos. JBuilder incluye un asistente para proyectos que simplifica la creación de los mismos. Este asistente configura automáticamente el marco de trabajo del proyecto y permite la introducción de datos como pueden ser la ubicación del proyecto en disco, el autor y una descripción del mismo. En el primer paso del asistente de proyectos indicamos el nombre del proyecto y la localización del mismo. También podemos indicar si queremos que se genere una página HTML que va a contener las notas relativas al proyecto. Este primer paso se puede ver en la Figura 14. 117 Programación en Java © Grupo EIDOS Figura 14 Si pulsamos siguiente aparecerá un formulario como el de la Figura 15, en el que podremos indicar el título y autor del proyecto, a que empresa pertenece y también una descripción del mismo. Figura 15 Si pulsamos Finalizar habremos terminado con este asistente y podremos comprobar que JBuilder ha creado un fichero HTML con el mismo nombre del proyecto. Si damos doble click sobre él veremos una página HTML con los datos indicados en el asistente del proyecto. Esta página la podemos editar desde la vista Fuente y modificar y añadir la información que queramos, esta página HTML sirve como documentación para el proyecto. Cuando nos situamos en la vista Fuente, podemos ver el código HTML y en el panel inferior de la derecha, denominado panel de estructura, podemos ver la estructura del documento HTML. Ahora debemos añadir la clase que nos muestre el mensaje Hola Mundo por la pantalla, al igual que hacíamos en el apartado anterior. Para añadir una clase al proyecto seleccionamos la opción de menú Archivo|Clase nueva y en ese momento se iniciará la ejecución del asistente de clases. 118 © Grupo EIDOS 8. Aplicaciones Java Figura 16 Este asistente nos permite indicar una serie de características que va a tener nuestra nueva clase. Los datos que podemos indicar al asistente, que aparece en la Figura 17, se dividen en dos grupos, información de la clase y opciones de la misma. Figura 17 En la información de la clase encontramos lo siguiente: • El paquete al que pertenece nuestra clase. • El nombre de la clase, que si es pública deberá coincidir con el nombre del fichero fuente de Java. • La clase base, es decir, la clase de la que heredamos. En la lista desplegable aparecen tres clases: la clase Object, que es la clase raíz de todas las clases de Java, la clase Component que 119 Programación en Java © Grupo EIDOS es la raíz de los componentes AWT y la clase JComponent que es la raíz de los componentes Swing. Los componentes AWT y Swing los trataremos en próximos capítulos cuando abordemos la construcción de interfaces de usuario en Java. Y en opciones podemos indicar lo siguiente: • Pública: nuestra clase si es pública se podrá instanciar y utilizar desde cualquier otra clase. • Generar función Main: se generará el método main() que es el método de arranque de la clase y lo veremos con el detenimiento que se merece en el siguiente apartado. • Generar constructor por defecto: nos genera el constructor por defecto de nuestra clase. • Modificar métodos abstractos: con esta opción se generarán implementaciones vacías de los métodos abstractos de la clase padre de la que se hereda. En nuestro caso vamos a indicar que la clase es pública, que hereda de la clase Object, es decir, de la que heredan todas las clases de Java directa o indirectamente, también seleccionaremos que se genere la función Main. Nuestra clase se va a llamar HolaMundo y no forma parte de ningún paquete. Al pulsar Aceptar el asistente ha creado el fichero fuente de Java correspondiente (HolaMundo.java) y ha generado el Código fuente 70. /** * Título: Ejemplo sencillo con JBulider<p> * Descripción: Proyecto que muestra el mensaje Hola Mundo<p> * Copyright: Copyright (c) Angel Esteban<p> * Empresa: Grupo EIDOS<p> * @author Angel Esteban * @version 1.0 */ public class HolaMundo { public static void main(String[] args) { } } Código fuente 70 Ahora vamos a añadir la sentencia en el método main() que va a mostrar el mensaje por pantalla, como se puede observar JBuilder ofrece una ayuda similar al Intellisense de Visual J++, en este caso se denomina CodeInsight. System.out.print("Hola Mundo"); Código fuente 71 Ahora debemos compilar nuestra clase para generar el fichero .class correspondiente, para ello acudimos a la opción de menú Proyecto|Ejecutar Make de "HolaMundo.java", al seleccionar esta opción se compilará nuestra clase HolaMundo, sólo nos falta ejecutar nuestra aplicación d ejemplo, para ello pulsaremos en la barra de herramientas el botón verde en forma de "play", en ese momento aparece una pantalla (Figura 18) que nos pregunta las propiedades de ejecución del proyecto. 120 © Grupo EIDOS 8. Aplicaciones Java Figura 18 En esta pantalla debemos indicar la clase principal del proyecto, es decir, la clase que se va a ejecutar, para ello pulsaremos el botón Asignar y seleccionaremos la clase HolaMundo y pulsamos Aceptar. El resultado de la ejecución de la aplicación lo veremos en el panel inferior, este panel es denominado panel de mensajes y se puede observar en la Figura 19. Figura 19 Si observamos el directorio en el que se encuentra el proyecto vemos los ficheros que se han generado durante nuestro proceso de creación de la aplicación. Por un lado tenemos un fichero .jpr que es el fichero de proyecto y que contiene todas las referencias del proyecto. También parece una página HTML que es la página de documentación del proyecto, que ya hemos comentado con anterioridad. Además de estos ficheros aparecen dos directorios, el directorio classes y el directorio scr. El directorio classes contiene todos los ficheros de bytecode, es decir, los ficheros de clases .class y el directorio src contiene los ficheros de código fuente, es decir, los ficheros .java. Al igual que sucedía con Visual J++ podemos ejecutar las clases generadas por JBuilder también desde la línea de comandos. JBuilder contiene la versión 1.2 del JDK (Java Development Kit), por lo tanto para ejecutar las clases utilizaremos la herramienta java que posee el JDK 1.2. El JDK se encuentra en el directorio c:\JBuilder35\jdk1.2.2. 121 Programación en Java © Grupo EIDOS El método main() Una clase por sí sola no es nada, es decir, es un conjunto de definiciones de atributos, constantes y métodos, pero hasta que no se instancia un objeto de la clase, no tiene una existencia real. Es similar al tipo de datos entero, con el tipo de datos no podemos hacer operaciones, pero cuando creamos una variable de ese tipo ya si que podremos operar con ella. La aplicación que construyamos finalmente tendrá una clase principal que será la que utilizaremos como parámetro cuando llamemos al intérprete de Java que interpretará y ejecutará nuestra aplicación. Ahora bien, como ya hemos comentado una clase sin instanciar en muchos casos nos sirve de bien poco, por lo tanto debe existir un mecanismo que de forma automática cuando ejecutemos una aplicación cree una instancia de su clase principal. Todas las aplicaciones Java deben tener un método main(), este es el método de arranque de la clase, este método debe a parecer en la clase principal de la aplicación. De esta forma al iniciar la ejecución de la aplicación lo primero que se hace es iniciar la ejecución del método main() y es en este método el lugar en el que debemos crear una instancia de la clase principal de la aplicación o bien de otras clases que va a utilizar la aplicación. Por ejemplo, si la clase principal de nuestra aplicación se llama Mensajes, esta clase debe implementar el método main(), en el que además de realizar las tareas que se estimen necesarias, se deberá crear una instancia de la clase Mensajes. También es posible realizar instancias de otras clases secundarias de la aplicación que vayamos a utilizar. Para que quede más clara la finalidad el método main() vamos a retomar el ejemplo de los los apartados anteriores y lo vamos a rescribir para que nuestra clase defina el método saludo() y tengamos que crear una instancia de la misma para poder utilizarla. El nuevo código sería el Código fuente 72. public class Class1{ public void saludo(){ System.out.println("Hola Mundo"); } public static void main (String[] args){ Class1 objClass1=new Class1(); objClass1.saludo(); } } Código fuente 72 En este caso además del método main(), nuestra clase ofrece el método saludo(). En la primera línea del método main() creamos una instancia de nuestra clase Class1, es decir, un objeto de la clase Class1. Como se puede observar aunque no definamos un constructor, lo podemos utilizar, lo que utilizamos es el constructor por defecto, que lo poseen todas las clases. Ahora si que estamos utilizando la POO de forma más clara, creando un objeto de nuestra propia clase y lanzándole un método que hemos definido. Vamos a fijarnos ahora en la declaración del método main(). El método main() siempre debe declararse como público, estático y que no devuelve parámetros, es decir, posee los modificadores public static void. 122 © Grupo EIDOS 8. Aplicaciones Java El método main() se debe declarar público para que pueda ser llamado siempre desde cualquier lugar, de esta forma es llamado por el intérprete. Es estático porque no se lanza sobre una instancia de la clase, es decir, no hace falta crear un objeto de la clase para lanzar el método main() sobre el, de hecho, si pudiéramos ver la llamada que realiza el intérprete con el método main() para que arranque la ejecución de una clase sería algo así: Clase.main(). El método main() recibe como parámetro un array de cadenas de caracteres, llamado args normalmente (aunque se puede utilizar cualquier nombre de objeto válido), que representan los argumentos de la línea de comandos que le podemos pasar a la clase a la hora de ejecutarse. El primer argumento, que se encuentra lógicamente en el índice cero, hace referencia primer parámetro que le siga al nombre de la clase que estamos ejecutando con el intérprete, y así sucesivamente, cada parámetro en la línea de comandos se separa con un espacio en blanco. Para manejar los parámetros de entrada, vamos a modificar el ejemplo anterior y vamos a sobrecargar el método saludo(), es decir, vamos a seguir utilizando el mismo nombre de método pero pasándole un parámetro. Este parámetro va a ser un objeto de la clase String (cadena de caracteres) que va a ser el nombre de la persona a la que queremos saludar. El nombre lo pasamos a través de la línea de comandos como un argumento, que lo recuperaremos en nuestra clase a través del primer elemento del parámetro args del método main(). El código resultante es el Código fuente 73. public class Class1{ public void saludo(){ System.out.println("Hola Mundo"); } public void saludo(String nombre){ System.out.println("Hola "+nombre+" que tal..."); } public static void main (String[] args){ Class1 objClass1=new Class1(); objClass1.saludo(args[0]); } } Código fuente 73 Y a continuación, en la Figura 20, se ven tres ejemplos de ejecución de nuestra aplicación, uno de ellos nos da un error, ya que no le hemos indicado el parámetro del nombre y nuestro método está intentando acceder a él sin que exista, nos da una excepción de array fuera de índices. Visual J++ nos dará entonces la oportunidad de depurar nuestra clase, cosa que de momento no vamos a hacer. Para evitar el error anterior, podemos verificar en nuestra clase que la longitud del array de cadenas args es mayor que cero. Si es mayor que cero, utilizamos saludo() con el nombre recuperándolo del argumento número cero, y en caso contrario utilizamos saludo() sin parámetros. El código sería algo como lo que muestra el Código fuente 74. 123 Programación en Java © Grupo EIDOS Figura 20 public class Class1{ public void saludo(){ System.out.println("Hola Mundo"); } public void saludo(String nombre){ System.out.println("Hola "+nombre+" que tal..."); } public static void main (String[] args){ Class1 objClass1=new Class1(); if(args.length!=0) objClass1.saludo(args[0]); else objClass1.saludo(); } } Código fuente 74 O también tenemos la posibilidad de atrapar la excepción ArrayIndexOutOfBoundsException, es decir, la excepción que se produce al intentar acceder al índice de un elemento que no existe dentro de un array. En el bloque try intentaríamos llamar a saludo() con el parámetro nombre, y en el catch, que se ejecuta si se produce la excepción, llamamos a saludo() sin parámetros. Este nuevo código se observa en el Código fuente 75. public class Class1{ public void saludo(){ System.out.println("Hola Mundo"); } public void saludo(String nombre){ System.out.println("Hola "+nombre+" que tal..."); } public static void main (String[] args){ Class1 objClass1=new Class1(); try{ objClass1.saludo(args[0]); } catch(ArrayIndexOutOfBoundsException ex){ 124 © Grupo EIDOS 8. Aplicaciones Java objClass1.saludo(); } } } Código fuente 75 Las aplicaciones Java son más flexibles que el otro tipo de programas Java, es decir, los applets, ya que no ofrecen restricciones de seguridad, como veremos en el capítulo correspondiente, y además permiten acceder al sistema en el que se están ejecutando manteniendo la portabilidad gracias a la clase System, que es la que vamos a comentar en el apartado siguiente. La clase System A la clase System sólo podemos acceder si el programa Java que estamos realizando es una aplicación, los applets Java no pueden acceder a esta clase. La clases System nos permite acceder a una serie de información y de recursos pertenecientes al sistema sobre el cual se está ejecutando nuestra aplicación. Se pueden realizar llamadas directas al sistema, aunque estas se desaconsejan, ya que entonces perderíamos la característica de independencia de la plataforma del lenguaje Java. Para conservar la independencia de la plataforma se ofrece la clase System, es como un intermediario entre el sistema operativo sobre el que se está ejecutando nuestra aplicación, y la propia aplicación Java. La clase System ya la hemos utilizado en algunos ejemplos de nuestro curso, sobre todo para mostrar información en la pantalla, a través de su atributo out. Pero para utilizar la clase System no hemos tenido que instanciar ningún objeto de la clase, esto es así debido a que sus atributos y métodos son estáticos, es decir, los podemos utilizar sin tener que instanciar un objeto de la clase. Posiblemente unos de los elementos más utilizados de la clase System dentro de las aplicaciones Java sean los canales de entrada/salida estándar. La clase System ofrece tres canales de entrada/salida estándar a través de tres atributos estáticos, que son los siguientes: • System.in: representa el canal de entrada estándar, normalmente se utiliza para leer la entrada del usuario realizada desde el teclado. • System.err: representa el canal de error estándar. Si se produce un error se muestra el mensaje en este canal. Este canal suele ser la pantalla. Si retomamos la última versión del ejemplo del apartado anterior, podremos utilizar el atributo err de la clase System para información de la excepción que se ha producido en el bloque try. Para ello podemos añadir al principio del bloque catch el Código fuente 76. System.err.println("Se ha producido la excepción: "+ex); Código fuente 76 • System.out: representa el canal de salida estándar, ya lo hemos utilizado en ejemplos anteriores y suele ser la pantalla. 125 Programación en Java © Grupo EIDOS A través de la clase System tenemos acceso a un conjunto de propiedades del sistema, que están definidas como pares nombre/valor. Cuando se inicia el entorno de ejecución de una aplicación las propiedades del sistema se inicializan, para reflejar los valores actuales de todas ellas. Las propiedades que se ofrecen por defecto son las que se muestran en la Tabla 20. Propiedad Explicación "file.separator" Separador de ficheros "java.class.path" Ruta de clases "java.class.version" Versión de las clases de Java "java.home" Directorio de instalación de Java "java.vendor" Vendedor de Java "java.vendor.url" URL del vendedor "java.version" Versión de Java "line.separator" Separador de líneas "os.arch" Arquitectura del sistema operativo "os.name" Nombre del sistema operativo "os.version" Versión del sistema operativo "path.separator" Separador de rutas "user.dir" Directorio de trabajo actual del usuario "user.home" Directorio de trabajo del usuario "user.name" Nombre de la cuenta del usuario Tabla 20 Las aplicaciones Java tienen acceso a todas ellas, pueden leer o escribir en ellas, mientras que el acceso de los applets Java es mucho más limitado como veremos en el capítulo dedicado a los mismos. Para leer las propiedades del sistema se dispone de dos métodos de la clase System, getProperty() y getProperties(). Con el primer método obtenemos el valor de una propiedad determinada, indicando como parámetro el nombre de la propiedad de la cual se quiere recuperar el valor. Si la propiedad no existe se devuelve null. El método getProperties(), no tiene parámetros, y nos devuelve todas las propiedades del sistema a través de un objeto de la clase Properties. La clase Properties que nos permite manipular las propiedades del sistema, se encuentra formando parte del paquete java.util. Para mostrar las todas las propiedades del sistema, se ha implementado un método cuyo código es el Código fuente 77. 126 © Grupo EIDOS 8. Aplicaciones Java public void propiedades(){ System.getProperties().list(System.out); } Código fuente 77 El objeto que se devuelve de la clase Properties posee un método llamado list() que escribe todas las propiedades en el canal de salida que se le indique por parámetro, es este caso se ha elegido la salida estándar, es decir, la pantalla, y el resultado que he obtenido en mi equipo de pruebas es el siguiente: -- listing properties -user.dir=C:\Work\Proyecto7 line.separator= java.vendor.url=http://www.microsoft.com/ path.separator=; user.home=C:\WINNT\Java java.version=1.1 java.class.path=C:\WINNT\java\trustlib\;C:\WINNT\java... java.home=C:\WINNT\Java com.ms.applet.enable.serversockets=false com.ms.windir=C:\WINNT os.version=4.0 com.ms.sysdir=C:\WINNT\System32 java.class.version=45.3 os.name=Windows NT os.arch=x86 awt.toolkit=com.ms.awt.Wtoolkit user.language=es user.timezone=ECT user.name=aesteban awt.appletWarning=Advertencia: ventana de subprograma user.region=ES browser=ActiveX Scripting Host java.vendor=Microsoft Corp. file.encoding.pkg=sun.io file.separator=\ file.encoding=Cp1252 http.agent=Mozilla/4.0 (compatible; MSIE 4.01; W... Como se puede comprobar hay muchas propiedades que no aparecen en la tabla anterior, esto es debido a que aquí se muestran propiedades específicas de la plataforma Windows, que es la plataforma sobre la que he realizado los ejemplos. Por lo tanto, para que la aplicación sea completamente portable, es recomendable utilizar únicamente las propiedades del sistema especificadas en la tabla. Para crear y añadir nuestras propias propiedades del sistema utilizaremos el método setProperties() de la clase System. A este método se le debe pasar como parámetro un objeto Properties que se ha inicializado para contener las diferentes propiedades. Aunque estos cambios en las propiedades no son persistentes entre diferentes aplicaciones Java. En el capítulo dedicado a la descripción del lenguaje Java, comentábamos que Java realizaba las tareas de liberación de memoria de forma automática a través de un hilo de ejecución paralelo denominado recolector de basura. Sin embargo a través de la clase System podemos forzar la terminación de nuestros objetos. 127 Programación en Java © Grupo EIDOS Antes de que un objeto sea tratado por el recolector de basura, el entorno de ejecución de Java le da la oportunidad al objeto de que libere él sus propios recursos a través del método finalize() del objeto. Para forzar la llamada a este método se lanza el método runFinalization() de la clase System, al lanzar este método se llamará a todos los métodos finalize() de los objetos que estén pendientes de destruirse. El recolector de basura se ejecutará siempre que el entorno de ejecución considere necesario y le sea posible, pero si queremos forzar la ejecución del recolector de basura lanzaremos el método gc() (garbage collector) de la clase System. La clase System ofrece una serie de métodos que se pueden utilizar para diversas tareas, uno de estos métodos es arraycopy(), este método permite copiar el contenido de un array en otro, la sintaxis de este método es: arraycopy(arrayOrigen, indiceOrigen, arrayDestino, indiceDestino, numeroElementos) Como se puede ver debemos especificar los dos arrays, el de destino y el de origen, y las posiciones en la que se inicia la copia en cada uno de ellos, y por último el número de elementos que se desea copiar de un array a otro. Otro método que englobado bajo el epígrafe de funcionalidades varias que ofrece la clase System es currentTimeMillis(), que devuelve la hora y fecha actual en milisegundos desde el 1 de Enero de 1970. El último método que vamos a comentar de la clase System es el método exit(). Al lanzar este método terminamos con la ejecución de la aplicación actual. A este método se le pasa un parámetro que será un entero indicando el estado de finalización, por convenio si se ha producido una finalización errónea se le pasa –1, en caso contrario 0. La clase Runtime Todas las aplicaciones Java poseen una instancia de la clase Runtime que permite interactuar a la aplicación con el entorno en el que se está ejecutando. Esta clase representa el entorno de ejecución de nuestra aplicación que comprende la Máquina Virtual de Java y el sistema operativo sobre el que se está ejecutando. De forma general esta clase ofrece dos funcionalidades, por un lado la comunicación con los componentes del entorno de ejecución, es decir, invocar funciones y obtener información, y por otro lado la interacción con capacidades dependientes del sistema operativo. Se debe tener cuidado al utilizar la clase Runtime, ya que podemos perder la portabilidad entre diferentes sistemas. Por lo general no se suele utilizar esta clase. Para obtener el entorno de ejecución actual se utiliza el método getRuntime() de la clase Runtime. Vamos a ver un ejemplo que permite lanzar la ejecución de la calculadora de Windows. Para lanzar la ejecución de un proceso externo dentro de una aplicación Java se lanza el método exec() de la clase Runtime sobre el objeto devuelto por el método getRuntime(). Al método exec() se le pasa como parámetros el nombre de la aplicación que se desea ejecutar. El método exec() devuelve un objeto de la clase Process, que representa un proceso externo en tiempo de ejecución y a través de este objeto se puede controlar la ejecución del proceso externo. Para esperar a que el proceso termine su ejecución se lanza el método waitFor() de la clase Process, al lanzar este 128 © Grupo EIDOS 8. Aplicaciones Java método se parará la ejecución de la aplicación Java principal hasta que finalice su ejecución el proceso externo. En nuestro caso no nos interesa esperar a que el proceso que lanzamos finalice, así que el código de nuestra aplicación Java que ejecuta la calculadora de Windows sería como se muestra en el Código fuente 78. public class Class1{ public void calculadora(){ try{ Runtime.getRuntime().exec("calc.exe"); } catch(java.io.IOException ex){} } public static void main (String[] args){ Class1 objClass1=new Class1(); objClass1.calculadora(); } } Código fuente 78 A la vista de este código se deben hacer algunos comentarios. Primero, el método exec() de la clase Runtime lanza una excepción de la clase IOException que debemos atrapar en un bloque try-catch o bien lanzar con la cláusula throws, en este caso se ha elegido atraparla. Segundo, la clase IOException se encuentra dentro del paquete java.io (paquete con las clases especializadas en la entrada/salida) y como no lo hemos importado debemos hacer referencia a esta clase a través de su nombre completo. Al ejecutar esta aplicación, una vez que se ha lanzado la ejecución de la calculadora, la aplicación se sigue ejecutando y finaliza la ejecución de la misma, destruyendo todos los procesos asociados, en este caso se incluye también la calculadora, es decir, al final no podemos utilizarla. Una solución al problema anterior es la de utilizar el método waitFor() de la clase Process, para que la aplicación Java no finalice hasta que no se cierre la calculadora. El método calculadora() quedaría como indica el Código fuente 79. public void calculadora(){ try{ Process proceso=Runtime.getRuntime().exec("calc.exe"); proceso.waitFor(); } catch(InterruptedException ex){} catch(java.io.IOException ex){} } Código fuente 79 A la vista de este código se deben realizar una serie de comentarios. Para poder tener una referencia al proceso que representa a la calculadora debemos asignárselo a un objeto de la clase Process. Aquí no creamos ningún objeto Process, sino que recuperamos uno ya existente que es creado por el método exec() de la clase Runtime. 129 Programación en Java © Grupo EIDOS Como se puede observar en el código además de atrapar la excepción IOException se debe atrapar la excepción InterruptedException, esta nueva excepción es lanzada por el método waitFor() de la clase Process. Algunas consideraciones sobre Visual J++ 6 En este apartado se van a comentar una serie de puntos interesantes acerca de la herramienta de desarrollo que estamos utilizando para programar en Java. Como ya hemos repetido, y no nos cansaremos de hacerlo, vamos a utilizar Visual J++ de forma que se genere código 100% Java, sin ningún añadido de Microsoft, es decir, nuestro código fuente compilará de la misma forma en Visual J++ que en la herramienta de Sun JDK (Java Development Kit). Antes de nada vamos a comentar los elementos más importantes que podemos observar en el entorno de desarrollo de Visual J++. En la parte derecha de la pantalla parece el Explorador de proyectos. Desde esta ventana podemos ver la estructura de nuestra solución, es decir, cuantos proyectos contiene y que ficheros contiene cada uno de los proyectos de la solución. En esta ventana podemos crear carpetas, borrar ficheros, cambiar el nombre a los proyectos, cortar, pegar, etc., todo al estilo del explorador de Windows. Debajo de la ventana de proyectos aparece la ventana de Propiedades, que en ningún momento vamos a utilizar, ya que se encarga de mostrar las propiedades de los componentes WFC (Windows Foundation Classes). Así que esta ventana la podemos cerrar. Al lado izquierdo nos encontramos con otra ventana que posee tres pestañas, la más interesante y la que vamos a poder utilizar es la pestaña de Esquema de clases. En esta ventana se muestra de forma gráfica toda la jerarquía y la estructura de la clase seleccionada en el Explorador de proyectos. La estructura de la clase seleccionada se muestra como un árbol con distintos nodos (Figura 21). En un primer nivel se muestra un nodo con el nombre de la clase, otro nodo con el nombre del paquete en el que nos encontramos y otro llamado Importaciones, que contiene todos los paquetes que importa nuestra clase. Si abrimos el nodo que posee el nombre de la clase, parecerá toda la información sobre la estructura de la clase: las clases de las que hereda, directa o indirectamente, los atributos y métodos que se heredan, y los atributos y métodos propios. Cada elemento que aparece en el Esquema de clases tiene un icono para distinguir el tipo de acceso (público, privado, paquete) y el tipo de elemento. En la Figura 21 aparece un ejemplo de clase en la que se muestra su esquema. El Esquema de clases es una utilidad muy potente, ya que nos permite ver de una simple pasada toda la estructura de la clase seleccionada, además la representación gráfica elegida ayuda mucho a la hora de identificar cada elemento. Otra utilidad que se ofrece dentro de Visual J++ y que permite ver la estructura de las clases es el Examinador de objetos. Para acceder a la ventana del Examinador de objetos seleccionaremos la opción de menú: Ver|Otras ventanas|Examinador de objetos. El aspecto de esta ventana se puede ver en la Figura 22. 130 © Grupo EIDOS 8. Aplicaciones Java Figura 21 Desde el Examinador de objetos se ofrece toda la estructura de las clases de Java, desde la vista de paquete hasta la primera clase en la jerarquía de herencia. También nos ofrece las clases propias de Microsoft y extensiones de Sun, es nuestro caso sólo nos van a interesar las clases que se encuentren dentro de los paquetes que comiencen por java. Dejamos al lector que investigue esta útil herramienta, ya que nos proporciona una visión general de la estructura de clases de Java. En la zona central del entorno de desarrollo nos encontramos la ventana de código, que como su nombre indica es dónde escribimos el código fuente de nuestras clases. El editor de código nos ofrece muchas facilidades que ya hemos comentado: intellisense, verificación de sintaxis, resaltar palabras reservadas, etc. Debajo del editor de código encontramos la ventana Lista de tareas, que ya hemos comentado anteriormente y que utilizaremos para mostrar las tareas pendientes y desde ella también se nos indicarán los errores de compilación de nuestra clase. A modo de resumen de lo visto hasta ahora de Visual J++, en la Figura 23 se muestra el entorno de forma general y las diferentes partes del mismo. 131 Programación en Java © Grupo EIDOS Figura 22 Figura 23 El entorno que nos ofrece Visual J++ es bastante amigable si lo comparamos con el ofrecido por el JDK de Sun. Una de las bonanzas que ofrece la herramienta de Microsoft es el entorno de depuración, que es precisamente la característica de la que nos vamos a ocupar ahora mismo. Dentro de la barra de herramientas estándar existe un menú llamado Depuración, que tiene una gran cantidad de opciones: paso a paso por instrucciones, paso a paso por procedimientos, establecimiento de puntos de ruptura, ejecutar hasta el cursor, etc., es decir, todo deseable para un entorno de depuración completo. 132 © Grupo EIDOS 8. Aplicaciones Java Se ofrece también la posibilidad de Agregar inspección, esta opción la vamos a utilizar para ir visualizando en la ventana de inspección los valores de la expresión que indiquemos. Por ejemplo si deseamos utilizar esta opción en la clase que veíamos en un apartado anterior, que consistía en mostrar un saludo por la pantalla, saludando al nombre que nos pasaban por parámetro, debemos situar un punto de interrupción al comienzo del método main() y pulsando con el botón derecho sobre la ventana de código fuente seleccionamos la opción del menú contextual llamada Agregar inspección. En este caso vamos a agregar a la inspección la expresión args[0], al seleccionar agregar inspección aparece la ventana de inspección para poder escribir la expresión. Si ejecutamos nuestra clase desde Visual J++, se detendrá la ejecución de nuestra aplicación en el punto de interrupción y podremos consultar en la ventana Inspección el valor de nuestra expresión que en este caso es, como aparece en la Figura 24, Error:array access out of range, ya que no nos han pasado el argumento. Para detener la depuración pulsaremos el botón de Stop de la barra de herramientas. Figura 24 Si deseamos repetir la depuración pero esta vez pasando un argumento a nuestra aplicación lo haremos de la siguiente forma: accedemos a la opción de menú Proyecto|Propiedades de proyecto, y podremos observar las hojas de propiedades que parecen en la Figura 25. En nuestro caso vamos a seleccionar la opción Personalizar y vamos a añadir al final de los argumentos que utiliza Visual J++ para lanzar nuestra aplicación, el nombre al que queremos saludar, es decir, la caja de texto argumentos quedaría así /p /cp:p "<JAVAPACKAGES>" Class1 Pepe. Si volvemos a ejecutar nuestra aplicación la ventana de inspección mostrará el valor "Pepe". Figura 25 133 Programación en Java © Grupo EIDOS Si al depurar nuestra aplicación Java se produce algún error, la herramienta Visual J++ entrará en modo de interrupción. Aparecerá una flecha verde que nos indicará dónde se ha producido el error, y tendremos acceso a tres ventanas, dos de ellas llamadas Automático y Locales, nos permiten consultar los valores de los atributos de los diferentes objetos de la aplicación. Y una tercera ventana llamada Inmediato nos permite ejecutar expresiones válidas. Para abandonar el modo de interrupción pulsaremos el botón de Play o el de Stop, según creamos conveniente. En la Figura 26 se puede observar el aspecto de la ventana Locales. Y en la Figura 27 se puede observar el aspecto de la ventana Inmediato en la que se ha consultado algunos atributos de la clase actual, en este caso el título y anchura de la ventana, y también si el tamaño de la ventana es modificable. Figura 26 Figura 27 Como vemos la herramienta Visual J++ es bastante amigable y fácil de utilizar, pero ofrece un par de deficiencias bastante importantes. La primera de ellas y la más grave es que soporta únicamente la versión 1.1 del lenguaje Java, es decir, es compatible con el JDK 1.1 pero no con el JDK 1.3, por lo tanto se puede considerar que es una herramienta un poco desfasada y además no contempla la posibilidad de utilizar nuevas versiones del JDK, característica que si presenta la herramienta de desarrollo JBuilder 3.5. De esta forma, podemos utilizar Visual J++ 6 si no vamos a utilizar las características ofrecidas por la plataforma Java 2. La segunda deficiencia es que no ofrece una herramienta que permita construir un interfaz de usuario gráfico (GUI Graphical User Interface) de forma sencilla al estilo de Visual Basic o Delphi, es decir, arrastrando y soltando componentes. 134 © Grupo EIDOS 8. Aplicaciones Java Consideraciones sobre JBuilder Este apartado tiene la misma función que el anterior, pero aplicado a JBuilder, es decir, vamos a realizar un comentario de las características más interesantes de la herramienta así como una descripción general del entorno. Este entorno es más complejo que Visual J++ ya que ofrece un mayor número de funciones. El entorno de desarrollo JBuilder proporciona una visión única en la que se permite realizar la gestión de ficheros y proyectos, diseño visual y compilación y depuración de aplicaciones. El entorno de JBuilder consta de una ventana general que contiene varios paneles que permiten realizar todas estas funciones, esta ventana recibe el nombre de visualizador de aplicaciones, en la Figura 28 se puede observar cada uno de los paneles y elementos de del visualizador de aplicaciones. Figura 28 Vamos a comentar brevemente cada uno de los elementos señalados en la Figura 28. • Panel de proyectos: muestra el contenido del proyecto seleccionado en la lista desplegable de proyectos. • Panel de estructura: contiene iconos, opciones de clasificación y mensajes de error. El panel de estructura muestra la estructura del archivo seleccionado en el panel de contenido. Para un archivo Java esta estructura aparece en forma de un árbol que muestra todos los métodos y atributos definidos en el archivo de código fuente. A través de este panel podemos ver las clases padre de forma muy sencilla. En el caso de ser el fichero una página HTML vemos la estructura del documento HTML en el que se muestran las etiquetas. • Panel de contenido: en este panel se muestran los ficheros abiertos, cada fichero abierto dispone de una pestaña en la parte superior que muestra el nombre del fichero, así como pestañas en la parte inferior para las distintas vistas disponibles. 135 Programación en Java © Grupo EIDOS • Panel de mensajes: en este panel se muestran los mensajes de los subsistemas, tales como diseñadores, resultados de búsquedas, procesos de compilación, depuración o ejecución. • Barra de estado: ofrece información actualizada de cualquier proceso y sus resultados. Existen dos barras de estado, la principal y la de archivo. La principal se muestra en la parte inferior de la ventana del visualizador de aplicaciones, y la de archivo aparece en la parte inferior de cada fichero abierto en la ventana de código fuente del panel de contenido y muestra información específica del fichero actual. JBuilder presenta un gran número de asistentes. Si seleccionamos la opción de menú Archivo|Nuevo, aparece la galería de objetos, cada uno de estos objetos tiene su asistente correspondiente, el asistente de creación de nuevas clases y de nuevos proyectos ya los vimos en el anterior apartado dedicado a JBuilder. En la Figura 29 se puede observar la galería de objetos. Figura 29 Si seleccionamos por ejemplo el objeto Aplicación, se lanzará el asistente para aplicaciones. Este asistente nos va a ayudar a construir una aplicación que muestra una ventana. En la primera pantalla de este asistente indicamos el paquete al que pertenece la aplicación y el nombre de la clase que va a representar la aplicación (Figura 30). También podemos indicar que se generen comentarios de forma automática. Si pulsamos el botón Siguiente podemos especificar una serie de opciones (Figura 31) que se aplican a una clase denominada clase de marco. Esta clase va a heredar de la clase JFrame (componente Swing que representa una ventana), ya que se va a tratar de una aplicación representada por una ventana. 136 © Grupo EIDOS 8. Aplicaciones Java Figura 30 Figura 31 Las opciones que se aplican a la clase de marco, estas opciones son: la posibilidad de añadir una barra de herramientas, una barra de menú y una barra de estado. También se permite generar la típica ventana Acerca de y centrar la ventana en la pantalla. También podemos indicar el nombre de la clase y el título que va aparecer en la ventana. Si pulsamos el botón Finalizar comprobaremos que el asistente ha añadido tres nuevas clases a nuestro proyecto. La clase principal que va a representar a la aplicación, una clase que hereda de la clase JFrame que se corresponde con la ventana principal de la aplicación y otra clase más que hereda dela clase JDialog (un componente Swing que representa una ventana de diálogo) y que se corresponde con el diálogo Acerca de. En el panel de proyectos también se puede observar que se han añadido varias imágenes (fichero .gif) que se corresponden con las utilizadas para la barra de herramientas de nuestra aplicación. Ahora si pulsamos el botón de ejecución se compilarán todas las clases y podremos ver nuestra aplicación que presentará un aspecto similar al de la Figura 32. 137 Programación en Java © Grupo EIDOS Figura 32 Para cada proyecto contenido en JBuilder podemos definir una serie de propiedades a través de la opción Proyecto|Propiedades de proyecto. Las propiedades del proyecto controlan y definen los siguientes aspectos: vías de acceso, ejecución, depuración, compilación, generación y estilo de código. Vamos a comentar cada unas de las pestañas que nos permiten configurar el proyecto. • Vías de acceso: podemos indicar las rutas a los ficheros fuente que se utilizan y a las los ficheros de clase que se generan, también a la documentación y a las rutas de las clases adicionales que son necesarias para el proyecto y que no pertenecen a las estándar de la plataforma Java 2, como pueden ser clases de terceras partes o clases desarrolladas por nosotros mismos. En esta pestaña podemos indicar la versión del JDK de Sun que vamos a utilizar, siendo esta característica muy interesante ya que permite actualizar JBuilder con futuras versiones del lenguaje de Sun. El aspecto de esta pantalla se puede ver en la Figura 33. Figura 33 138 © Grupo EIDOS • 8. Aplicaciones Java Ejecutar: permite especificar el entorno de ejecución del proyecto actual. Esta pantalla a su vez contiene varias pestañas que permiten configurar las aplicaciones, applets y páginas JSP (Java Server Pages). Para la aplicación podemos indicar que parámetros queremos utilizar, estos parámetros los recogeremos a través del parámetro args[] del método main() (como ya hemos visto anteriormente), también se pueden indicar los parámetros que necesitemos pasar al intérprete de Java o Máquina Virtual (MV). Y para los applets podemos definir también los parámetros correspondientes. La forma en que utilizan los parámetros los applets lo veremos con detenimiento en el capítulo correspondiente. Y la opción de configuración de las páginas JSP (similares a las páginas ASP de Microsoft pero implementadas en Java) se sale del contenido de este curso, simplemente comentaremos que permite configurar los parámetros del servidor que va a alojar las páginas JSP. De forma general podemos indicar si queremos compilar el proyecto antes de ejecutarlo o compilarlo antes de depurarlo. Figura 34 • Depurar (Figura 35): en esta pestaña podemos definir una serie de parámetros que se tendrán en cuenta a la hora de depurar el proyecto. Para ejecutar un proyecto en modo de depuración pulsaremos el botón de la barra de herramientas que se encuentra al lado del play de ejecución. Para introducir un punto de interrupción en nuestro código fuente simplemente pulsamos con el botón derecho del ratón sobre la línea correspondiente y seleccionamos la opción Conmutar punto de interrupción. Una vez establecido el punto de interrupción podemos configurar sus propiedades e indicar si queremos detener la ejecución de la aplicación o no. Al iniciar la depuración de un proyecto determinado, el depurador aparecerá en el panel inferior, el que se había denominado panel de mensajes. • Compilador(Figura 36): en este apartado se configuran las opciones por defecto del compilador, como puede ser mostrar advertencias, excluir una clase determinada, mostrar métodos que ya se han desaconsejado, etc. Como opciones generales podemos indicar si queremos guardar en disco los ficheros antes de compilar. 139 Programación en Java © Grupo EIDOS Figura 35 Figura 36 • 140 Generar (Figura 37): permite configurar el compilador IDL utilizado para generar los esqueletos de las clases necesarias para CORBA. Este es un tema avanzado de Java que no vamos a tratar en nuestro curso. © Grupo EIDOS 8. Aplicaciones Java Figura 37 • Estilo de código (Figura 38): en esta opción podemos indicar a JBuilder como queremos que genere el código automático. Podemos indicarle la forma de utilizar las llaves de bloques de instrucciones, como crear las clases adaptadoras para los eventos (los eventos y las clases adaptadoras los trataremos en el tema de interfaces de usuario dedicado a los eventos) y la visibilidad de los atributos de las clases que crea. Figura 38 141 Programación en Java © Grupo EIDOS Una de las ventajas más interesantes que tiene JBuilder es que ofrece la posibilidad de crear los interfaces de usuario en modo de diseño arrastrando y soltando los componentes. En este aspecto JBuilder demuestra ser una herramienta mucho más potente que Visual J++ o el JDK. Vamos a comentar brevemente el diseñador que nos ofrece esta herramienta. Con la ayuda de las herramientas de diseño visual de JBuilder podemos crear de forma rápida y sencilla el interfaz de usuario de una aplicación o de un applet de Java. Para construir el interfaz de usuario utilizaremos distintos componentes que seleccionaremos de la paleta de componentes, una vez seleccionado el elemento deseado podemos definir los valores sus propiedades y también el código necesario para los eventos. En la Figura 39 se muestra un esquema de la vista de diseño dentro del entorno de JBuilder. Figura 39 142 • Diseñador: es la superficie de diseño utilizada para colocar y modificar los paneles y otros componentes del interfaz de usuario como pueden ser botones, etiquetas, listas, cajas de texto, áreas de texto, etc., para acceder al diseñador únicamente se debe seleccionar la pestaña Diseño de la parte inferior del panel de contenido. Este diseñador se puede utilizar para clases que hereden de la clase Continer, como puede ser JFrame, JPanel, JDialog o JApplet (las clases utilizadas en la construcción de interfaces de usuario las veremos con detenimiento en los capítulos dedicados al tema). JBuilder mantiene la sincronización entre el diseño visual y el código fuente de Java, al cambiar el diseño en diseñador de interfaces se actualiza automáticamente el código fuente, y viceversa al cambiar el código fuente el cambio se refleja en el diseñador de interfaces. • Paleta de componentes: está situada encima del diseñador de interfaces y contiene componentes visuales y no visuales, estos componentes varían según la edición de JBuilder, en este caso se trata de la versión empresarial (Enterprise). • Árbol de componentes: se localiza en el panel de estructura y ofrece una vista estructurada de todos los componentes que contiene el fichero fuente y de las relaciones que existen entre ellos. • Inspector: esta ventana se utiliza para inspeccionar y definir los valores de las propiedades (atributos) de los componentes y los métodos para el tratamiento de eventos. Los cambios © Grupo EIDOS 8. Aplicaciones Java realizados en el inspector se reflejan en el diseño. Para ver el inspector de un componente basta con seleccionar el componente deseado. Para ver el diseñador de interfaces de JBuilder en acción simplemente podemos crear una nueva aplicación con el asistente que ya habíamos visto anteriormente, ya que la clase generada hereda de la clase JFrame, que es un contenedor. Además de todas estas facilidades comentadas para la creación de interfaces, JBuilder ofrece un diseñador de menús. Para utilizar el diseñador de menús tenemos que acceder a la vista de diseño (la aplicación creada por el asistente puede servir) y añadir un componente menú (JMenuBar) desde la paleta de componentes. El menú no se verá en el diseñador, pero haciendo doble clic sobre el componente de menú en el árbol de componentes, aparecerá el diseñador de menús, como se puede observar en la Figura 40. Figura 40 El diseñador de menús es muy fácil de utilizar y muy intuitivo, podemos ir añadiendo elementos de menú de manera sencilla. De momento no vamos a añadir más comentarios ni explicaciones sobre JBuilder, a lo largo del curso la volveremos a retomar al igual que haremos con Visual J++ y el JDK, para terminar este capítulo se deben destacar los aspectos positivos y negativos de JBuilder. Los dos aspectos o características más positivas de JBuilder es la posibilidad de diseñar interfaces de usuario de forma visual y que implementa la última versión del lenguaje Java, permitiendo la posibilidad de actualizar la versión del lenguaje de forma rápida y sencilla. Y el aspecto más negativo es la excesiva memoria que requiere el producto así como los requerimientos de sistema operativo (Windows NT Server o superior) y los numerosos (por lo menos en mi caso) "cuelgues" de la herramienta, así como los diversos problemas de refresco de las ventanas y de los interfaces en general de la herramienta. 143 Interfaces de usuario en Java: componentes AWT Introducción A lo largo de este capítulo y los siguientes, vamos a abordar la construcción de interfaces de usuario en el lenguaje Java, se ha querido crear varios capítulos para tratar este tema debido a su relevancia. El interfaz de usuario tiene una gran importancia ya que es lo que realmente va a ver y utilizar el usuario. Un interfaz de usuario debe ser coherente, fácil de utilizar, intuitivo, atractivo, amigable, rápido, etc., pero sobretodo lo más importante es que el usuario pueda utilizarlo correctamente y se sienta dentro de un entorno amigable. No vamos a teorizar sobre la construcción de interfaces de usuario, sino que vamos a explicar simplemente como crear sencillos interfaces dentro del lenguaje Java. Trataremos las bases de la creación de interfaces de usuario comentando las clases más relevantes que Java pone a nuestra disposición. Este capítulo está dedicado a la utilización de componentes AWT. El AWT (Abstract Window Toolkit) El AWT es un conjunto de clases que Java pone a nuestra disposición para la creación de interfaces de usuario, como pueden ser botones, listas, cajas de texto, ventanas, etc. Todas estas clases se Programación en Java © Grupo EIDOS encuentran en el paquete JAVA.AWT. El otro conjunto de clases que ofrece Java para la construcción de interfaces de usuario se denomina Swing y lo veremos en próximos capítulos. El AWT está presente desde la primera versión del lenguaje Java, y todavía se sigue utilizando, tanto en la versión 2 del lenguaje como en la 1.1, debido a esto lo hemos incluido en nuestro curso. Los componentes Swing no se pueden utilizar dentro de Visual J++ pero sí con JBuilder. Pero el AWT además de proporcionar clases que permiten la construcción de interfaces de usuario, también ofrece un subpaquete llamado java.awt.event que contiene las clases especializadas en el tratamiento de eventos, como puede ser la pulsación de un botón, la selección de un elemento de una lista, cerrar una ventana etc. En este capítulo sólo vamos a tratar la utilización de componentes AWT, en el siguiente veremos con detenimiento el tratamiento de eventos que realiza Java, y también los gestores de diseño que nos permiten tener un mayor control sobre la posición de los distintos componentes y elementos del interfaz. Mediante los programas Java se pueden construir interfaces de usuario muy completos, similares a los que podríamos construir con otros lenguajes de programación como Visual Basic, aunque ya avisamos, no de forma tan sencilla. Para poder realizar interfaces que dispongan de botones, menús, etiquetas, campos de texto, listas y otros componentes se diseñó el Abstract Window Toolkit (AWT). El AWT ofrece lo siguiente: • Un conjunto completo de accesorios de interfaz de usuario y otros componentes como ventanas, menús, botones, casillas de verificación, campos de texto, barras de desplazamiento y listas desplegables. • Soporte para contenedores de interfaces de usuario para que tengan a otros contenedores dentro de él o accesorios como pueden ser botones, listas, cajas de texto, etc. • Un sistema de eventos para administrar los eventos del sistema y los del usuario cuando interacciona con componentes del AWT. Lo que vamos a ver de eventos y gestores de diseño en el próximo capítulo se aplica tanto a componentes AWT como a componentes Swing. • Mecanismos para distribuir los componentes de forma que permitan un diseño de interfaz de usuario independiente de la plataforma. La idea fundamental que sostiene el AWT es que una ventana Java es un conjunto de componentes anidados que empiezan desde la ventana exterior hasta el más pequeño de los componentes que forman parte del interfaz. Esta anidación de componentes dentro de contenedores crea una jerarquía de componentes desde la casilla de verificación más pequeña hasta la ventana general de la pantalla. Esta jerarquía determina la distribución de elementos en la pantalla y dentro de otros elementos además del orden en el que se muestran. Los componentes principales con los que puede trabajar el AWT son los siguientes: • 146 Contenedores (Container): componentes AWT genéricos que incluyen a otros componentes. La forma más común de éstos es la clase Panel, que representa a un contenedor que puede desplegarse en pantalla, y también la clase Window que representa de forma genérica las ventanas . Los applets, como veremos en el capítulo correspondiente, son un tipo de panel, de hecho, la clase Applet es una subclase o clase hija de la clase Panel. © Grupo EIDOS 9. Interfaces de usuario en Java: componentes AWT • Lienzos (Canvas): es una superficie de dibujo que permite dibujar imágenes y realizar otras operaciones gráficas. • Componentes de interfaz de usuario: incluyen botones, listas, menús de aparición súbita, casillas de verificación, campos de texto y otros elementos comunes de un interfaz de usuario. • Componentes de construcción de ventana: incluyen ventanas, cuadros, barras de menú y diálogos. Como ya habíamos comentado, las clases del AWT se encuentran organizadas en el paquete JAVA.AWT, para recordar un poco el término diremos que los paquetes Java son una manera de agrupar clases e interfaces relacionadas, permiten que grupos modulares de clases estén disponibles sólo cuando se necesiten. Volviendo al AWT, sus diferentes clases conforman una jerarquía y la raíz para la mayoría de los componentes AWT es la clase Component, que ofrece características comunes para todos los componentes. La muestra una jerarquía parcial de las clases AWT. Uno de los principales problemas con los que nos podemos encontrar es la portabilidad del interfaz de usuario, es decir, conseguir que la misma información (ventanas, controles, imágenes y otros objetos) guarden sus proporciones en todas las plataformas en las que el sistema tiene que ejecutarse. Dicho de otro modo, que la apariencia de lo que se ve sea la misma en distintos entornos. La solución, sin ser nada fácil, es muy similar a la que se utiliza para escribir código portable. Para hacer que las aplicaciones funcionen bajo distintas plataformas, en lugar de compilar el código fuente de las mismas al código máquina de un microprocesador concreto, se compila a un pseudo-código, y se ejecuta en una máquina virtual, es necesario escribir una máquina virtual para cada entorno, pero una vez hecho esto, todos los programas escritos para ese compilador podrán ser ejecutados en todas aquellas plataformas para las que exista máquina virtual. Del mismo modo, es necesario crear una interfaz independiente del medio para poder portar las aplicaciones sin perder el aspecto que les imprimió su creador. Figura 41 147 Programación en Java © Grupo EIDOS Java resuelve este problema utilizando lo sus creadores han dado en llamar "Gestores de diseño" (Layout Managers), que trataremos con detenimiento en el capítulo correspondiente. Utilizando los componentes del AWT En este apartado vamos a comentar algunos de los componentes básicos que podemos encontrar dentro del AWT. Vamos a realizar una sencilla aplicación a la que vamos a ir añadiendo diferentes componentes. Frame Lo primero que vamos a añadir a nuestra aplicación es una ventana, una ventana se representa mediante la clase Frame. La clase Frame es un tipo de contenedor, por lo que es un descendiente de la clase Container, y además es una ventana, por lo que hereda directamente de la clase Window. Para que nuestra aplicación posea una ventana tenemos dos opciones, creamos un atributo que sea un objeto de la clase Frame o bien nuestra aplicación hereda de la clase Frame. Vamos a elegir la segunda opción, ahora nuestra aplicación va a heredar todo el comportamiento de la clase Frame. El código con el que vamos a empezar es el que muestra el Código fuente 80. import java.awt.*; public class Ventana extends Frame{ public static void main (String[] args){ } } Código fuente 80 El Código fuente 80 merece el siguiente comentario. Para utilizar las clases de creación de interfaces de usuario con los componentes del AWT debemos importar el paquete JAVA.AWT, como hacemos en este caso. Vamos a añadir un constructor a nuestra clase para que al crear un objeto se instancie de una forma determinada, es decir, con un título y dimensiones determinadas. El constructor podría ser como indica el Código fuente 81. public Ventana(String titulo,int x, int y){ super(titulo); setSize(x,y); show(); } Código fuente 81 148 © Grupo EIDOS 9. Interfaces de usuario en Java: componentes AWT En el constructor de nuestra clase Ventana, llamamos con super() al constructor de la clase padre, en este caso la clase Frame. El constructor de la clase Frame se encuentra sobrecargado, y en una de sus versiones acepta como parámetro el título de la ventana. Una vez lanzado el constructor se utiliza el método setSize() de la clase Frame para establecer las dimensiones de la ventana, y por último se muestra dicha ventana con el método show() de la clase Frame. Una vez que ya tenemos constructor, podemos utilizar nuestra clase dentro del método main() de nuestra aplicación como muestra el Código fuente 82. public static void main (String[] args){ Ventana miVentana=new Ventana("Una ventana",300,150); } Código fuente 82 Si ejecutamos la aplicación podremos ver un objeto de nuestra clase Ventana, más o menos debe ser como el que aparece en la Figura 42. Figura 42 Como el lector habrá comprobado, la ventana no se puede cerrar, más tarde en el siguiente capítulo veremos como implementar este comportamiento ala hora de tratar los eventos en el lenguaje Java. Cursor Si queremos modificar el aspecto del cursor del ratón en nuestra ventana utilizaremos el método setCursor(). Este método establece el aspecto que va a tener el cursor del ratón al moverse en el área de la ventana. Como parámetro recibe un objeto de la clase Cursor. Esta clase representa al cursor del ratón. En el Código fuente 83 se muestra como utilizar este método, el constructor de la clase Cursor recibe como parámetro el tipo de cursor que se quiere crear. Los tipos de cursores se encuentran definidos como constantes de la clase Cursor. miVentana.setCursor(new Cursor(Cursor.HAND_CURSOR)); Código fuente 83 149 Programación en Java © Grupo EIDOS MenuBar Para añadir una barra de menú a nuestra ventana utilizaremos el método setMenuBar() de la clase Frame. Este método recibe como parámetro un objeto de la clase MenuBar, que representa una barra de menú. Menu Una vez creado el objeto MenuBar vamos a ir añadiéndole los menús que sean necesarios, para ello utilizaremos el método add() de la clase MenuBar. Este método recibe como parámetro objetos de la clase Menu. MenuItem A su vez cada menú (objeto de la clase Menu) va a tener elementos de menú, que se van añadiendo con el método add() de la clase Menu. A este método se le debe pasar como parámetro objetos de la clase MenuItem, que representarán cada uno de los elementos de menú. CheckboxMenuItem En nuestro ejemplo vamos a crear un nuevo método llamado añadeMenu(), que va a añadir a nuestra clase Ventana una barra de menú con dos menús, y uno de los elementos de menús va a ser una casilla de verificación. Este tipo de elemento de menú especial se representa mediante la clase CheckboxMenuItem. Vamos a modificar el constructor de nuestra clase Ventana para que llame al método añadeMenu(). Finalmente el código quedaría como aparece en el Código fuente 84. import java.awt.*; public class Ventana extends Frame{ public Ventana(String titulo,int x, int y){ super(titulo); setSize(x,y); añadeMenu(); show(); } public void añadeMenu(){ //barra de menú MenuBar mb; //menús Menu m1, m2; //elementos de menú MenuItem mi1_1, mi1_2; CheckboxMenuItem mi2_1; //se crea y establece la barra de menú mb = new MenuBar(); setMenuBar(mb); //se crea y añade el primer menú m1 = new Menu("Menú 1", true); mb.add(m1); //se añade los elementos de menú al primer menú mi1_1 = new MenuItem("Menú item 1_1"); m1.add(mi1_1); 150 © Grupo EIDOS 9. Interfaces de usuario en Java: componentes AWT mi1_2 = new MenuItem("Menú item 1_2"); m1.add(mi1_2); //se crea y añade el segundo menú m2 = new Menu("Menú 2"); mb.add(m2); //se crea y añade el elemento de menú del segundo menú mi2_1 = new CheckboxMenuItem("Menú item 2_1"); m2.add(mi2_1); } public static void main (String[] args){ Ventana miVentana=new Ventana("Una ventana",300,150); } } Código fuente 84 Y el resultado de la ejecución de la aplicación es el que aparece en la Figura 43. Figura 43 Todas las clases adicionales utilizadas en este ejemplo: MenuBar, Menu, MenuItem y CheckboxMenuItem pertenecen todas ellas al paquete JAVA.AWT. Estas clases presentan una jerarquía separada del resto de las clases del AWT, todas ellas heredan de MenuComponent, su jerarquía se muestra en la Figura 44. Figura 44 151 Programación en Java © Grupo EIDOS Otro tipo de ventanas que podemos crear con el AWT son los diálogos, este tipo de ventanas se distinguen del resto en que son dependientes de otra ventana, al destruir la ventana principal se destruyen todos los diálogos que dependan de ella y cuando se minimiza la ventana principal, todos los diálogos dependientes desaparecen de la pantalla. Dialog Los diálogos se representan mediante la clase Dialog, que ofrece además una subclase llamada FileDialog que permite al usuario abrir y guardar ficheros. Un diálogo puede ser modal, es decir, hasta que no se cierre no se permite interactuar al usuario con la ventana de la que depende. Vamos a añadir a nuestra aplicación un método que muestre un diálogo modal que dependa de la ventana creada anteriormente en este mismo apartado. Este método se llama muestraDialogo() y posee el Código fuente 85. public void muestraDialogo(String titulo,int x,int y){ Dialog dialogo=new Dialog(this,titulo,true); dialogo.setSize(x,y); dialogo.show(); } Código fuente 85 Como se puede observar el método muestraDialogo() recibe tres parámetros, el título de la ventana de diálogo y las dimensiones de la misma. En la primera línea creamos un objeto de la clase Dialog, el constructor de esta clase, en una de sus versiones, recibe tres parámetros, la referencia a la ventana a la que va a estar relacionada, el título y un valor booleano que indica si el diálogo es de tipo modal o no. La ventana del que depende el diálogo es nuestra propia clase, debido a esto utilizamos la referencia this. La llamada al método muestraDialogo() la podemos situar dentro del método main(), una vez creado un objeto de la clase Ventana. El resultado de la ejecución de la aplicación se puede observar en la Figura 45. Figura 45 Como se puede observar, al ser un diálogo modal, no podemos acceder al objeto de la clase Ventana. 152 © Grupo EIDOS 9. Interfaces de usuario en Java: componentes AWT FileDialog Si queremos mostrar un diálogo para la elección de un fichero utilizaremos la clase FileDialog. Esta clase hereda de la clase vista anteriormente, Dialog. Para mostrar un diálogo de este nuevo tipo se va a utilizar el método muestraDialogoFichero() cuyo código es el Código fuente 86. public void muestraDialogoFichero(int x, int y){ FileDialog dialogo=new FileDialog(this); dialogo.setSize(x,y); dialogo.show(); } Código fuente 86 El constructor de la clase FileDialog que se ha utilizado, recibe como parámetro la referencia a la ventana a la que se encuentra asociado este diálogo, que sigue siendo una instancia de nuestra clase Ventana. El aspecto de la ejecución de nuestra aplicación es el que aparece en la Figura 46. Figura 46 Como se puede comprobar, este tipo de diálogo va a ser siempre modal, y al pulsar cualquiera de los botones se cierra el diálogo. Una vez comentadas algunas clases del AWT vamos a definir el concepto de contenedor de una forma un poco más extensa y relacionándolo con lo que hemos visto hasta ahora. Container Un objeto de la clase Container (que dicho sea de paso, es abstracta) es un componente que puede contener otros componentes. Permite almacenar elementos GUI (controles, otros contenedores, etc.), y es sobre estos elementos contenidos sobre los que actúa el gestor de diseño asociado al objeto Container, para distribuirlos sobre la superficie abarcada por el Container según las indicaciones del programador. 153 Programación en Java © Grupo EIDOS Pero además, los gestores de diseño permiten que esta distribución, o mejor dicho, la apariencia de la distribución de los componentes, se mantenga similar a través de los distintos entornos gráficos en los que se va a ejecutar la aplicación Java. En el siguiente capítulo, dentro del apartado dedicado a los gestores de diseño veremos los diferentes tipos y como utilizarlos en cada caso. De la clase Container derivan las siguientes: • Panel: No es más que una particularización de la clase Container. Tampoco tiene representación gráfica y generalmente se utiliza para gestionar una agrupación lógica de elementos. El caso más típico es el del teclado de una calculadora. • Window: Esta clase permite la definición de "ventanas" como áreas independientes capaces de contener otros elementos, no tienen título, ni barra de menú, ni botones de tamaño, cierre, etc. De este modo esta clase puede utilizarse para construir ventanas que se adapten a las peculiaridades de cada uno de entornos para los que se va a construir una máquina Java (JVM). De esta clase, heredan las siguientes. • Frame: Esta es clase que utilizamos para construir lo que normalmente entendemos por ventanas o "forms" en algunos lenguajes. • Dialog: Esta clase se utiliza para crear cajas de diálogo. • FileDialog: Esta es un caso especial de la clase Dialog, de la cual hereda. Puesto que las tareas de manejo de ficheros (guardar, guardar como y abrir) son muy comunes en todos los entornos, esta clase proporciona cajas de diálogo para realizar estas y otras tareas relacionadas. Button Ya hemos visto algunos contenedores y también la creación de menús, ahora vamos a añadir un botón a nuestra ventana. Los botones se encuentran representados por la clase Button. Para añadir el botón vamos a definir un atributo llamado boton dentro de nuestra clase Ventana, es decir, en la declaración de atributos escribiremos algo como lo que aparece en el Código fuente 87. public Button boton; Código fuente 87 El botón lo vamos a instanciar y añadir en el constructor de la clase Ventana, añadiendo para ello las el Código fuente 88. boton=new Button("Un botón"); this.add(boton); Código fuente 88 Como se puede ver en el código, el constructor de la clase Button, en una de sus dos versiones, recibe como parámetro la etiqueta que va a mostrar el botón, pero además de instanciar el botón, se debe añadir a la ventana. 154 © Grupo EIDOS 9. Interfaces de usuario en Java: componentes AWT Se debe recordar que la clase Frame es un tipo de contenedor, y para añadir elementos a un componente contenedor se debe utilizar el método add() de la clase Container. Se utiliza la referencia this para indicar que el botón se va a añadir a nuestra ventana, aunque en realidad no es necesario, se ha puesto para una mayor claridad. El método add() en esta versión (está sobrecargado) recibe como parámetro la instancia del componente que se quiere añadir al contenedor. Si probamos nuestra aplicación, vemos que se ha añadido el botón, pero es un botón enorme que ocupa toda la superficie de nuestra ventana. Para solucionar este problema vamos a utilizar un gestor de diseño. Los gestores de diseño (LayoutManagers) los trataremos más adelante en el siguiente capítulo, de momento simplemente vamos a añadir una línea de código para evitar el problema anterior. El nuevo código del constructor es el Código fuente 89. public Ventana(String titulo,int x, int y){ super(titulo); setSize(x,y); añadeMenu(); setLayout(new FlowLayout()); boton=new Button("Un botón"); this.add(boton); show(); } Código fuente 89 La nueva línea la vamos a comentar de forma somera, como ya hemos dicho más adelante explicaremos los gestores de diseño. La línea de código que hemos añadido permite establecer un tipo de gestor de diseño para evitar el efecto de la extensión del botón en toda la superficie del contenedor. El aspecto final de nuestra ventana es el de la Figura 47. Figura 47 Un objeto botón genera un evento cada vez que lo pulsamos, generalmente lo vamos a utilizar para que el usuario con su pulsación nos indique que desea que se realice una acción determinada. Los eventos los veremos más en detalle en el próximo capítulo. Ahora si pulsamos el botón no pasará nada, el evento se lanza pero no hay nadie que lo trate. La clase Button ofrece el método setLabel() para asignar la cadena que se le pasa como argumento a la etiqueta del botón. Al igual que añadimos botones a nuestra aplicación podemos añadir etiquetas, cajas de texto, áreas de texto, listas, etc. Vamos a ir viendo cada uno de estos elementos. 155 Programación en Java © Grupo EIDOS Label Las etiquetas son texto estático que se suele utilizar para mostrar información y dar indicaciones al usuario. En Java las etiquetas se representan con la clase Label. En nuestro ejemplo vamos a añadir tres etiquetas, pero en lugar de añadirlas directamente a nuestra ventana las vamos a añadir a un objeto de la clase Panel, para agrupar todas ellas bajo un gestor de diseño diferente al establecido para la ventana. Para añadir las etiquetas vamos a crear un nuevo método llamado añadeEtiquetas(), cuyo código se puede ver en el Código fuente 90. public void añadeEtiquetas(){ Panel panel=new Panel(); panel.setLayout(new GridLayout(0,1,1,1)); Label label1=new Label(); label1.setText("Etiqueta 1"); Label label2=new Label("Etiqueta 2"); label2.setAlignment(Label.CENTER); Label label3=new Label("Etiqueta 3"); panel.add(label1); panel.add(label2); panel.add(label3); add(panel); validate(); } Código fuente 90 En la primera línea instanciamos el objeto de la clase Panel que va a contener las etiquetas, y a continuación le asignamos un gestor de diseño. Luego se van creando las etiquetas, como se puede ver en el constructor utilizado, se le pasa como parámetro el texto que van a mostrar las diferentes etiquetas. Utilizamos el método setAligment() de la clase Label para alinear el texto de la etiqueta, utilizando como parámetro una constante definida por la clase Label, por defecto el texto de las etiquetas se encuentra alineado a la izquierda. Después añadimos las etiquetas al objeto de clase Panel creado, ya que es un tipo de contenedor, utilizamos el método add(). Una vez añadidas las etiquetas al panel, debemos añadir el panel a nuestra ventana, al igual que hacíamos con el botón. El método validate() que aparece al final de nuestro código es utilizado para que la ventana muestre los cambios que se han realizado sobre ella, ya que el panel que contiene las etiquetas se ha añadido después de mostrar la ventana. El método validate() pertenece a la clase Container, y su función es la de actualizar el aspecto de los contenedores. El aspecto que debe tener nuestra aplicación es el de la Figura 48. Figura 48 156 © Grupo EIDOS 9. Interfaces de usuario en Java: componentes AWT Los métodos más relevantes de la clase Label son setText() y getText() que asignan y devuelven el texto de la etiqueta, respectivamente. List, Choice Otro elemento del AWT son las listas y las listas desplegables, en ambos casos se trata de un conjunto de elementos que podemos seleccionar de entre varias opciones. Las listas se encuentran representadas por la clase List y las listas desplegables se encuentran representadas por la clase Choice. Como viene siendo costumbre vamos a crear un método que añada los dos tipos de lista a nuestra aplicación, este método se denomina añadeListas(). Y lo implementamos como indica el Código fuente 91. public void añadeListas(){ List lista1=new List(3, true); Choice lista2=new Choice(); lista1.add("Elemento 1"); lista1.add("Elemento 2"); lista1.add("Elemento 3"); lista1.add("Elemento 4"); lista1.add("Elemento 5"); add(lista1); lista2.addItem("Elemento 1"); lista2.addItem("Elemento 2"); lista2.addItem("Elemento 3"); lista2.addItem("Elemento 4"); lista2.addItem("Elemento 5"); add(lista2); validate(); } Código fuente 91 Lo que procede a continuación es comentar el código anterior. El constructor de la clase List recibe dos parámetros en la versión del método utilizada, por un lado le indicamos el número de elementos que queremos que sean visibles, y por otro lado le indicamos si se permite la selección múltiple, es decir, si se tiene la posibilidad de seleccionar varios elementos a la vez. El constructor de la clase Choice, no está sobrecargado y no recibe ningún parámetro. Una vez instanciado un objeto de la clase List vamos a ir añadiendo cada uno de los elementos que va a contener con el método add() de la clase List, y a continuación añadimos la lista a la ventana con el método add(). Con el objeto Choice realizamos la misma tarea. Al igual que en el ejemplo de las etiquetas, es necesario lanzar el método validate() de la clase Container. El método añadeListas() lo llamaremos, al igual que ocurría en los ejemplos anteriores, en el método main() de nuestra clase. Para simplificar el aspecto de nuestra aplicación, vamos a eliminar de nuestro constructor la creación del botón. En la Figura 49 se puede ver el nuevo aspecto de la aplicación. Las diferencias entre los objetos List y Choice son evidentes, el objeto List permite seleccionar varios elementos y definir el número de elementos que se desea que permanezcan visibles, sin embargo el objeto Choice, sólo permite seleccionar un objeto y tener uno sólo visible. 157 Programación en Java © Grupo EIDOS Figura 49 De los métodos que ofrece la clase List se destacan los siguientes: • int getItemCount(): devuelve el número de elementos de una lista. • replaceItem(String,int): reemplaza el elemento cuyo índice se especifica en el segundo parámetro por el texto indicado en el primer parámetro. El primer elemento de la lista posee el índice cero. • removeAll(): elimina todos los elementos de la lista. • remove(int): elimina el elemento de la lista cuyo índice coincida con el especificado. • String getSelectedItem(): devuelve el elemento que se encuentra seleccionado. • String[] getSelectedItems(): devuelve los elementos que se encuentran seleccionados. • boolean isIndexSelected(int): indica si el elemento cuyo índice se pasa por parámetro se encuentra seleccionado o no. Y de la clase Choice podemos destacar los siguientes: • int getItemCount(): devuelve el número de elementos de una lista. • String getItem(int): devuelve el elemento cuyo índice coincide con el se pasa por parámetro. • select(int): selecciona el elemento cuyo índice coincide con el que se pasa por parámetro. TextField, TextArea Ahora vamos a pasar a ver las cajas de texto y las áreas de texto. Normalmente estos dos tipos de controles se utilizan para obtener una entrada del usuario. La diferencia principal entre ambos es que las cajas de texto poseen una única línea, y las áreas de texto permiten tener más de una, reconociendo los saltos de línea, además muestra barras de scroll si el texto no es visible completamente en el área de texto. El AWT representa las áreas de texto mediante la clase TextArea y las cajas de texto mediante la clase TextField. Estas dos clases que heredan de la clase TextComponent las vamos a ver a continuación. 158 © Grupo EIDOS 9. Interfaces de usuario en Java: componentes AWT Al igual que en el caso de las listas, al ser dos componentes muy parecidos vamos a implementar un método llamado añadeTexto() que va a añadir a nuestra ya conocida aplicación una caja de texto y un área de texto. El método añadeTexto() es como se muestra en el Código fuente 92. public void añadeTexto(){ TextField caja=new TextField("Texto",10); TextArea area=new TextArea(5,10); area.setText("Primera línea.\n"); area.append("Segunda línea.\n"); area.append("Tercera línea que es más larga que las anteriores.\n"); add(caja); add(area); validate(); } Código fuente 92 El constructor de la clase TextField recibe como argumentos en una de sus versiones, ya que se encuentra sobrecargado, el texto que va a parecer en la caja de texto y el tamaño de la caja de texto. El constructor utilizado para instanciar el objeto TextArea recibe como parámetros dos enteros que indican el número de filas y de columnas del área de texto. Para asignarle texto al objeto TextArea y al objeto TextField se utiliza el método setText() pasando como parámetro un objeto de la clase String que va a representar el texto que va a contener el área o campo de texto. La clase TextArea ofrece el método append() para ir añadiendo al final del texto existente en el área de texto el nuevo texto que se le pasa por parámetro. Se puede observar que para indicar un salto de línea utilizamos el carácter de escape especial \n. Por último añadimos la caja de texto y el área de texto a nuestra ventana y llamamos al método validate(), al igual que en los casos anteriores. Si llamamos al método añadeTexto() de nuestra clase Ventana, se obtendrá el resultado mostrado en la Figura 50. Figura 50 A continuación vamos a comentar los métodos más interesantes de la clase TextComponent, y que por lo tanto heredan las clases TextArea y TextField: • String getSelectedText(): devuelve el texto que se encuentra seleccionado. • boolean isEditable(): devuelve verdadero si es posible editar el texto. 159 Programación en Java © Grupo EIDOS • selectAll(): selecciona todo el texto. • select(int,int): selecciona el texto comprendido entre los índices indicados como parámetros. Cero es el primer carácter. • setCaretPosition(int): indica cual va a ser la posición del cursor (el caret) dentro del texto. • int getCaretPosition(): devuelve la posición en la que se encuentra el cursor dentro del texto. • setEditable(boolean): indicamos si el texto se va a poder modificar o no, dependiendo del argumento booleano que le facilitemos. • String getText(): devuelve el texto que posea el componente. La clase TextField añade una serie de métodos a los que hereda de la clase TextComponent, los más interesantes son: • setEchoChar(char): establece en la caja de texto un carácter de eco, que ocultará el contenido real del texto de la caja, se suele utilizar el carácter ‘*’. • char getEchoChar(): devuelve el carácter de eco que tiene asignada la caja de texto. Por su parte la clase TextArea añade los siguientes métodos: • insertText(String, int): inserta el texto especificado, en la posición indicada • replaceText(String, int, int): reemplaza el texto comprendido entre las posiciones indicadas por el texto especificado. Checkbox, CheckboxGroup Los siguientes componentes del AWT que vamos a tratar son las opciones o casillas de verificación. Estos componentes poseen dos estados: activado o desactivado (verdadero o falso) y se utilizan para que el usuario elija entre una serie de opciones disponibles. La clase que representa a las opciones es Checkbox. Hay dos tipos de opciones, las que son independientes y se pueden activar y desactivar sin tener en cuenta al resto, y las que forman parte de un grupo de opciones y sólo puede estar activada una de las opciones agrupadas. Para agrupar las opciones vamos a hacer uso de la clase CheckboxGroup. Esta clase nos asegura que de todos los objetos Checkbox que contenga, sólo uno de ellos puede estar activado en un mismo momento. Vamos a añadir seis de opciones, tres de ellas independientes y otras tres agrupadas y por lo tanto dependientes y excluyentes entre sí. Los dos conjuntos de opciones las vamos a añadir en dos objetos de la clase Panel, y a su vez añadiremos estos dos objetos a nuestra ventana. Al igual que en todos los casos anteriores vamos a mostrar el código del método (Código fuente 93) que añade a nuestra aplicación los nuevos componentes y acto seguido comentaremos dicho código. public void añadeOpciones(){ Panel p1, p2; //casillas de verificación 160 © Grupo EIDOS 9. Interfaces de usuario en Java: componentes AWT Checkbox cb1, cb2, cb3; //opciones excluyentes Checkbox cb4, cb5, cb6; //objeto que va a contener al grupo de opciones CheckboxGroup cbg; //Primer panel con las casillas independientes //se crean las opciones cb1 = new Checkbox(); cb1.setLabel("Opción 1"); cb2 = new Checkbox("Opción 2",true); cb3 = new Checkbox("Opción 3"); cb3.setState(true); p1 = new Panel(); p1.setLayout(new FlowLayout()); p1.add(cb1); p1.add(cb2); p1.add(cb3); //Segundo panel con las opciones agrupadas cbg = new CheckboxGroup(); cb4 = new Checkbox("Opción 4", cbg, true); cb5 = new Checkbox("Opción 5", cbg, false); cb6 = new Checkbox("Opción 6", cbg, false); p2 = new Panel(); p2.setLayout(new FlowLayout()); p2.add(cb4); p2.add(cb5); p2.add(cb6); //utilizamos otro gestor de diseño para la ventana setLayout(new GridLayout(0, 2)); //se añaden los paneles add(p1); add(p2); validate(); } Código fuente 93 En este código se han utilizado varias versiones del constructor de la clase Checkbox para instanciar los objetos correspondientes, vamos a ver cada una de ellas. El primer constructor es el más sencillo y no posee ningún tipo de argumentos, el segundo constructor ofrece un parámetro para indicar mediante un objeto de la clase String la etiqueta que va a acompañar a la opción y otro parámetro para indicar el estado de la opción activada o desactivada (true o false), y el tercer constructor permite asignar la etiqueta de la opción correspondiente. Para activar una opción utilizamos el método setState() de la clase Checkbox, a este método le pasamos un valor booleano para indicar si va a estar activada o no la opción. Una vez creadas las opciones y configuradas de forma correcta, creamos el panel que las va a contener instanciando un objeto de la clase Panel al que aplicaremos un gestor de diseño con el método setLayout(), y a continuación añadimos los objetos Checkbox mediante el método add() de la clase Container. Para crear el segundo grupo de opciones y para que permanezcan agrupadas de forma excluyente es necesario instanciar un objeto de la clase CheckboxGroup. Este objeto se utilizará como parámetro en otra nueva versión del constructor de la clase Checkbox. En este nuevo constructor vamos a indicar la etiqueta que acompaña a la opción, el grupo al que pertenece y su estado. Sólo uno de estos objetos Checkbox puede crearse como activado, ya que pertenecen todos al mismo objeto CheckboxGroup. Creamos el nuevo panel y añadimos los objetos Checkbox correspondientes, se aplica un gestor de diseño a nuestra ventana y se añaden los dos paneles que contienen las opciones. El aspecto de la aplicación es el de la Figura 51. 161 Programación en Java © Grupo EIDOS Figura 51 ScrollPane El último de los componentes que vamos a tratar del AWT es la clase ScrollPane. Esta clase hereda de Container, y por lo tanto es un tipo de contenedor, y como todo contenedor se va a utilizar para albergar otros componentes. La particularidad que ofrece este tipo de contenedor es que muestra barras de desplazamiento horizontal y vertical que nos permiten desplazar el contenido de este contenedor. Vamos a crear un objeto de la clase ScrollPane en nuestra ventana, para que contenga a su vez un objeto de la clase Panel que contiene cuatro botones en una fila, de esta forma podremos ver las barras de desplazamiento horizontal. El código que nos permite hacer esto es el Código fuente 94. public void añadePanelScroll(){ ScrollPane panel=new ScrollPane(ScrollPane.SCROLLBARS_AS_NEEDED); Panel otroPanel=new Panel(); otroPanel.setLayout(new GridLayout(1,4)); otroPanel.add(new Button("botón 1")); otroPanel.add(new Button("botón 2")); otroPanel.add(new Button("botón 3")); otroPanel.add(new Button("botón 4")); panel.add(otroPanel); add(panel); validate(); } Código fuente 94 El constructor de la clase ScrollPane recibe como argumento una constante de la clase ScrollPane que indica cuando se van a mostrar las barras de scroll o de desplazamiento, en este caso indicamos que se muestren sólo cuando sea necesario. El resto del código es bastante claro, creamos un panel que va a contener los botones y vanos añadiendo los distintos botones. Como se puede observar, al crear los botones no los guardamos en ninguna variable, esto lo hacemos así porque no vamos a utilizarlos más, podemos crear así los componentes del AWT a los que después no es necesario hacer referencia. Si lanzamos sobre una instancia de nuestra clase Ventana el método añadePanelScroll() tenemos el resultado de la Figura 52. 162 © Grupo EIDOS 9. Interfaces de usuario en Java: componentes AWT Figura 52 163 Interfaces de usuario en Java: gestores de diseño y eventos Introducción A lo largo de este capítulo, vamos a continuar con la creación de interfaces de usuario en Java, pero en este caso no vamos a hablar de componentes como en el capítulo anterior, sino que vamos a tratar dos características del interfaz de usuario en los programas Java, los gestores de diseño y los eventos. Lo que vamos a ver en este capítulo es aplicable tanto a componentes AWT como a componentes Swing (que los veremos en el siguiente capítulo), aunque muchas de las clases que implementan los gestores de diseño y el tratamiento de eventos se encuentran dentro del paquete JAVA.AWT y del subpaquete java.awt.events. Los eventos y gestores de diseño específicos de los componentes Swing los veremos en los próximos capítulos en los que se abordará Swing, en éste veremos lo común a los dos tipos de componentes. Gestores de diseño Los gestores de diseño (Layout Managers) tienen una gran importancia en el leguaje Java, para poder incorporar controles en una ventana (ya sea de una aplicación o de un applet), es necesario que estos estén contenidos en un gestor de diseño, de otro modo no funcionarán correctamente. Comencemos por lo tanto, viendo qué es un gestor de diseño desde tres puntos de vista diferentes: Programación en Java © Grupo EIDOS • Desde el punto de vista del lenguaje, como ya hemos comentado, un gestor de diseño es un "estilo de visualización", es decir, es el mecanismo que el lenguaje incorpora para conseguir crear aplicaciones que tengan la misma apariencia a través de las diferentes plataformas en las que éstas pueden ejecutarse. • Desde el punto de vista de la jerarquía de clases de Java, un gestor de diseño es un interfaz, y deriva directamente de la clase Object, que es la que se encuentra en la raíz de la jerarquía de clases. Sin embargo, es a través de la clase Container como podemos establecer el o los gestores de diseño que la ventana va a utilizar para implementar los componentes que ésta manejará. El gestor de diseño es parte del Container, de hecho, siempre que se crea un Container, se puede especificar con cual o cuales de los gestores de diseño disponibles en Java se quiere trabajar, o dicho de otro modo, qué gestor de diseño se asignará al contenedor. • Desde el punto de vista operativo, un Gestor de diseño es una propiedad del contenedor, que puede almacenar cualquier tipo de objeto que derive de la clase Container (normalmente bien controles o bien otros gestores de diseño). El Container actúa de intermediario entre la ventana y los componentes que él almacena pasándoles a estos los mensajes que a él le llegan desde la ventana. El modo de interactuar con un gestor de diseño es muy simple: basta con especificar con cual de los disponibles queremos trabajar y añadirle componentes. Veamos esto de una forma más detenida. Puesto que existen diferentes gestores de diseño incluidos en el lenguaje Java y puesto que además podemos construir nuestros propios gestores de diseño, se hace necesario indicar con cual de los disponibles deseamos trabajar. Lo que se hace pasándole al método setLayout() de la clase Container un objeto del tipo deseado de gestor de diseño con el que se desea trabajar. Veamos, por ejemplo, la línea de código que muestra el Código fuente 95. this.setLayout(new FlowLayout()); Código fuente 95 El operador new devuelve un gestor de diseño del tipo FlowLayout, éste objeto es recogido por el método setLayout() de la clase Container, quien a partir de ahora ya sabe con qué gestor deseamos trabajar. La clase Container dispone del método add(), que permite añadir componentes. Como ya hemos comentado, a un objeto de la clase Container le podemos añadir bien controles o bien otros gestores de diseño. Para ello algo tan simple como lo que aparece en el Código fuente 96. this.add( new Button( "Uno" ) ); Código fuente 96 A continuación vamos a comentar los distintos gestores de diseño que podemos encontrar dentro del paquete JAVA.AWT. En los distintos ejemplos se han utilizado botones para poner de manifiesto las diferentes distribuciones que los distintos gestores de diseño hacen del espacio ya que el gestor es invisible. Los cinco gestores de diseño son los que se comentan a continuación. 166 © Grupo EIDOS 10. Interfaces de usuario en Java: gestores de diseño y eventos FlowLayout El gestor de diseño FlowLayout, además de ser el gestor de diseño por defecto de los applets (que los trataremos en el capítulo correspondiente), es también el más simple de todos. Este gestor, distribuye los controles de izquierda a derecha llenando el ancho del contenedor de arriba a abajo. Es decir, cuando acaba con una fila de controles, sigue con la siguiente, actuando con los controles como lo hace un editor de textos con las palabras. Es decir, los controles fluyen (Flow). Para ver cada uno de los ejemplos de los distintos tipos de gestores de diseño vamos a volver a utilizar la aplicación de ejemplo del capítulo anterior, es decir, nuestra clase Ventana. Implementaremos un método para cada tipo de gestor de diseño. En la Figura 53 se puede ver el ejemplo del gestor FlowLayout. Figura 53 Y el método, que será llamado desde el método main() y que permite mostrar la ventana de la Figura 53 es el que se muestra en el Código fuente 97. public void gestorFlowLayout(){ setLayout( new FlowLayout() ); for( int n = 0; n <= 15; n++ ) add( new Button( Integer.toString(n) ) ); validate(); } Código fuente 97 Como se puede observar, se utiliza primero el método setLayout() pasándole por parámetro una instancia del gestor de diseño que se va a utilizar en nuestra ventana. En el bucle se van añadiendo botones a la ventana, en este caso se utiliza el método add() de la clase Container, en la versión que ya conocemos, es decir pasándole por parámetro la instancia del componente AWT que queremos añadir. Como se ha puede comprobar, los componentes se van incorporando al contenedor atendiendo al orden en el que los vamos añadiendo. BorderLayout Este gestor, distribuye los componentes en cinco zonas: norte, sur, este, oeste y deja una amplia zona central para el resto de los componentes. Si a alguna de estas áreas no se le asigna un componente, el área vacía será ocupada por alguna de las áreas restantes. 167 Programación en Java © Grupo EIDOS Al igual que con el gestor anterior, vamos a ver en la Figura 54 el aspecto que muestra y a continuación veremos y comentaremos el código de nuestra clase Ventana que nos permite crear este gestor de diseño. Figura 54 Y el método correspondiente de la clase Ventana es el que muestra el Código fuente 98. public void gestorBorderLayout(){ setLayout( new BorderLayout() ); add("North",new Button("Norte")); add("South", new Button("Sur")); add(BorderLayout.EAST,new Button("Este")); add(BorderLayout.WEST,new Button("Oeste")); add("Center",new Button("Centro")); validate(); } Código fuente 98 Aquí se puede observar que el método setLayout() se sigue utilizando de la misma forma que para el gestor anterior, pero vemos que el método add() antepone un parámetro al componente que vamos añadir, este parámetro es para indicar la posición del componente dentro del contenedor. También se puede observar que hay dos formas de indicar la posición del componente, o con una cadena indicando la situación (North, Center, South...) o con una constante definida por la clase BorderLayout. CardLayout Este tercer gestor permite manejar varias "fichas intercambiables", de forma tal que sólo una esté visible a la vez y ocupando todo el área del contenedor. Es similar a los controles de pestaña o "tabs" de Windows. El problema que este gestor de diseño tiene es que la implementación que en Java se ha hecho de él, no es completa, y las fichas (o tarjetas si se prefiere) no tienen la correspondiente pestaña asociada, por lo que no existe una forma preestablecida de interactuar con los controles para cambiar de unas fichas a otras; siendo necesario crear este mecanismo programáticamente con los inconvenientes de pérdida de tiempo y esfuerzo que esto supone. En el capítulo siguiente veremos que los componentes Swing ofrecen una clase llamada JTabbedPane que representa un componente en el que se pueden elegir las distintas fichas (paneles) a través de los paneles que presenta. En la Figura 55 se puede observar el aspecto de nuestra aplicación. 168 © Grupo EIDOS 10. Interfaces de usuario en Java: gestores de diseño y eventos Figura 55 Y el código correspondiente se puede ver en el Código fuente 99. public void gestorCardLayout(){ CardLayout gestor=new CardLayout(); setLayout(gestor); add("1",new Button("Card 1")); add("2",new Button("Card 2")); add("3",new Button("Card 3")); add("4",new Button("Card 4")); add("5",new Button("Card 5")); validate(); } Código fuente 99 El lector puede que esté un poco confundido, debido a que en el código se añaden cinco botones, pero sin embargo al ejecutar la aplicación sólo podemos ver el primero, esto es así porque las demás fichas con los botones correspondientes se encuentran debajo de la primera. La clase CardLayout ofrece una serie de métodos para mostrar las distintas fichas que conforman el gestor de diseño. Estos métodos son: • first(Container): nos movemos a la primera ficha. A este método le pasamos por parámetro el contenedor sobre el que se ha aplicado el gestor de diseño. En el ejemplo anterior deberíamos añadir la siguiente línea: gestor.first(this). • next(Container): nos movemos a la siguiente ficha, si estamos en la última, volveremos a la primera, es decir, la disposición del orden de las fichas es circular. Al igual que en el método anterior, debemos pasar por parámetro el contenedor que al que se le ha aplicado el gestor de diseño. • previous(Container): nos movemos a la ficha anterior, si estamos en la primera ficha, nos moveremos a la última. • last(Container): nos movemos a la última ficha. • show(Container, String): mostramos una ficha específica, la ficha se indica mediante un parámetro que va a ser un objeto de la clase String, y que coincidirá con la cadena asignada a la ficha que queremos mostrar. Esta cadena se asigna a la ficha a la hora de añadirla al gestor de diseño, como se puede observar en el método add(). Por ejemplo, si queremos mostrar la tercera ficha, teniendo en cuenta el ejemplo anterior, se escribiría: gestor.show(this,"3"). 169 Programación en Java © Grupo EIDOS Si observamos el método gestorCardLayout(), comprobamos que el gestor de diseño se ha asignado de una forma un poco diferente, en este caso se ha creado un variable llamada gestor que va a contener la referencia al gestor de diseño de la clase CardLayout. Esto se ha hecho así para poder tener una referencia del gestor de diseño sobre la que podemos lanzar los métodos de la clase CardLayout vistos anteriormente. Los componentes se añaden a las fichas utilizando una versión del método add() que tiene como primer parámetro el objeto String que identifica la ficha a la que se va añadir el elemento, según se van añadiendo los diferentes componentes se van añadiendo fichas al gestor de diseño, no es obligatorio especificar el nombre que va a identificar a la ficha, es decir podemos utilizar al método add() con un único parámetro que va a ser el componente a añadir. GridLayout Este gestor permite distribuir componentes basándose en una rejilla (grid) bidimensional, haciendo que cada componente utilice una celda de la rejilla. Para este gestor de diseño debemos indicar el número de filas y columnas que va a presentar. Sin embargo, los componentes que se añaden al gestor no pueden especificarse bidimensionalmente, sino que es obligatorio añadirlos ordinalmente, es decir, primero el primero, después el segundo en la rejilla, y así sucesivamente. Veamos la figura que muestra este gestor de diseño (Figura 56). Figura 56 Como se puede apreciar en el ejemplo aquí expuesto, el flujo de los componentes ha ido de izquierda a derecha y de arriba a abajo hasta completarse la rejilla (grid). Y el código del método correspondiente se puede ver en el Código fuente 100. public void gestorGridLayout(){ setLayout(new GridLayout(4,3)); add(new Button("Primero")); add(new Button("Segundo")); add(new Button("Tercero")); add(new Button("Cuarto")); add(new Button("Quinto")); add(new Button("Sexto")); add(new Button("Séptimo")); add(new Button("Octavo")); add(new Button("Noveno")); add(new Button("Décimo")); 170 © Grupo EIDOS 10. Interfaces de usuario en Java: gestores de diseño y eventos validate(); } Código fuente 100 El constructor de la clase GridLayout es diferente del resto de gestores, se debe especificar dos parámetros que indican el número de filas y columnas, respectivamente, que va a presentar el gestor de diseño. GridBagLayout El quinto gestor de diseño es el más potente, y es también el más complicado de manejar. Esta clase, trabaja en conjunción con otra llamada GridBagConstraints, que se encarga de almacenar todos los parámetros de visualización necesarios para que los componentes puedan presentarse de una forma versátil y flexible. Quizás una de las mayores ventajas que incorpora este gestor es la posibilidad de que un componente abarque más de una celda, lo que le da una gran potencia. Pero esta es sólo una de las muchas ventajas que este gestor incorpora. En la Figura 57 se puede ver el aspecto de un gestor de diseño GridBagLayout. Figura 57 Y como viene siendo ya costumbre, se muestra el código del método correspondiente, en el Código fuente 101. public void gestorGridBagLayout(){ GridBagLayout gestor=new GridBagLayout(); GridBagConstraints constantes=new GridBagConstraints(); Button boton; setLayout(gestor); //primer botón en la posición (0,0) (columna,fila) //se extiende hasta la última fila boton=new Button("Primero"); constantes.gridwidth=1; constantes.gridheight=GridBagConstraints.REMAINDER; constantes.fill=GridBagConstraints.BOTH; constantes.gridx=0; constantes.gridy=0; gestor.setConstraints(boton,constantes); add(boton); 171 Programación en Java © Grupo EIDOS //segundo botón en la posición (1,0) boton=new Button("Segundo"); constantes.gridx= GridBagConstraints.RELATIVE; constantes.gridheight=1; gestor.setConstraints(boton,constantes); add(boton); //tercer botón en la posición (2,0), se extiende dos columnas boton = new Button("Tercero"); constantes.gridx=2; constantes.gridwidth=2; gestor.setConstraints(boton,constantes); add(boton); //cuarto botón en la posición (4,0) boton=new Button("Cuarto"); constantes.gridx=4; constantes.gridwidth=1 gestor.setConstraints(boton,constantes); add(boton); //quinto botón en la posición (5,0), se extiende 3 filas boton = new Button("Quinto"); constantes.gridx=5; constantes.gridheight=3; gestor.setConstraints(boton,constantes); add(boton); //sexto botón, en la posición (1,1), se extiende 4 columnas boton=new Button("Sexto"); constantes.gridx=1; constantes.gridy++; constantes.gridheight=1; constantes.gridwidth=4; gestor.setConstraints(boton,constantes); add(boton); //séptimo botón en la posición (1,2) boton=new Button("Séptimo"); constantes.gridy++; constantes.gridx=1; constantes.gridwidth=1; gestor.setConstraints(boton,constantes); add(boton); //octavo botón situado en la posición (2,2) se extiende 3 columnas boton = new Button("Octavo"); constantes.gridx=2; constantes.gridwidth=3; gestor.setConstraints(boton, constantes ); add(boton); //noveno botón en la posición (1,3) se extiende hasta la última //columna boton = new Button("Noveno"); constantes.gridx=1; constantes.gridy++; constantes.gridwidth=GridBagConstraints.REMAINDER; gestor.setConstraints(boton,constantes); add(boton); validate(); } Código fuente 101 Creamos el gestor de diseño GridBagLayout y también un objeto GridBagConstraints, el objeto GirdBagConstraints, que en el código hemos llamado constantes, indica al gestor de diseño GridBagLayout cómo se debe comportar. Al gestor de diseño le indicamos el objeto GridBagConstraints que tiene asociado a través del método setConstraints() de la clase GridBagLayout. A este método se le pasa como parámetros el componente 172 © Grupo EIDOS 10. Interfaces de usuario en Java: gestores de diseño y eventos que se va añadir y se le va a aplicar lo definido por el objeto GridBagConstraints, y el propio objeto GridBagConstraints. El objeto GridBagConstraints lo podemos utilizar para ir añadiendo distintos componentes al contenedor al que se le ha asignado el gestor de diseño, en nuestro ejemplo hemos ido reutilizando y modificando los aspectos que nos convenían en cada caso. Para definir el comportamiento que debe tener cada control al añadirse al contenedor, la clase GridBagConstraints tiene una serie de atributos que permiten un gran control sobre cómo se sitúa cada componente en el contenedor al que se le ha asignado el gestor de diseño GridBagLayout. Los atributos más relevantes de la clase GridBagConstraints son los siguientes: • gridx, gridy: especifica las coordenadas en las que se va a situar el componente, de esta forma la primera columna sería gridx=0 y la primera fila gridy=0. Para indicar que el componente se va a situar a la derecha (para gridx) o debajo (para gridy) del último componente que se ha añadido al contenedor utilizaremos la constante RELATIVE de la clase GridBagConstraints, además, este es el valor por defecto. • gridwidth, gridheight: a través de estos dos atributos indicamos cuantas columnas y cuantas filas, respectivamente, va a ocupar el componente. El valor por defecto en ambos casos es uno. Para especificar que el componente se extienda hasta la última columna (para gridwidth) o hasta la última fila (para gridheight) se utiliza la constante REMAINDER de la clase GridBagConstraints. • fill: cuando el área en la que se va a mostrar el componente es mayor que el tamaño especificado del componente, podemos determinar mediante este atributo si se va a redimensionar el componente. Este atributo acepta como valores válidos varias constantes de la clase GridBagConstraints. Por defecto el valor de esta propiedad es NONE, es decir, no se redimiensionan los componentes, si queremos que se redimensione de forma horizontal o de forma vertical utilizaremos las constantes HORIZONTAL y VERTICAL, respectivamente. Y si queremos que se redimensione el componente de forma completa utilizaremos la constante BOTH. En el Código fuente 101 se puede observar la utilización de todos estos atributos, para seguir el código se deben tener en cuenta los comentarios y fijarnos en el aspecto que ofrece al final nuestro contenedor. Con este gestor de diseño finalizamos el apartado dedicado a los mismos, ahora vamos a pasar a un apartado que tiene una gran importancia dentro del tema de la construcción de interfaces de usuario, es el apartado dedicado al tratamiento de eventos en Java. Veremos como reaccionar ante eventos generados por la interacción del usuario con el interfaz de usuario, como pueden ser pulsaciones de botón, selección de elementos de menú, selección de listas, cambios en cajas de texto, cierre de una ventana, movimientos del ratón, etc. Tratamiento de eventos en Java En apartados y capítulos anteriores hemos estado viendo como construir interfaces de usuario a partir de los componentes ofrecidos por el paquete JAVA.AWT, pero estos componentes por sí solos no hacen mucho, para tener un interfaz de usuario completo es necesario que el usuario se comunique con nuestra aplicación, y esta comunicación se produce a través del interfaz de usuario mediante el tratamiento de eventos. 173 Programación en Java © Grupo EIDOS Como veremos a lo largo de este apartado, el lenguaje Java dentro de su paquete java.awt.event nos ofrece una completa jerarquía de clases cuya función es la de representar los distintos tipos de eventos que podemos tratar desde un interfaz de usuario. El modelo de eventos que ofrece Java es denominado modelo de delegación de eventos (Delegation Event Model). Los diferentes tipos de eventos se encuentran encapsulados dentro de una jerarquía de clases que tienen como raíz a la clase java.util.EventObject. La idea fundamental del modelo de eventos de Java es que un evento se propaga desde un objeto "fuente" (source) hacia un objeto "oyente" (listener) invocando un método en el oyente al que se le pasa como parámetro una instancia de la clase del evento que representa el tipo de evento generado. Este método será el que trate al evento y actúe en consecuencia. Los eventos son representados por toda una jerarquía de clases de eventos. Esta jerarquía de eventos se muestra en la Figura 58, cada una de las líneas significa que la clase que se encuentra en la parte inferior de la línea hereda de la clase que se encuentra en la parte superior. Se debe aclarar que las clases en las que no aparece el nombre de su paquete pertenecen todas al paquete dedicado a los eventos, java.awt.event. Figura 58 Como ya hemos adelantado, en el tratamiento de eventos dentro del lenguaje Java hay dos entidades bien diferenciadas y que son las necesarias para que se produzca el tratamiento de eventos en Java: la fuente del evento y el oyente del mismo. La fuente va a ser el componente que genere el evento y el oyente el componente o clase que lo trate. Desde el punto de vista de la jerarquía de clases de Java, un oyente es un objeto que implementa un interfaz java.util.EventListener específico, de esta clase raíz es de la que heredan todos los interfaces del tipo <Evento>Listener. Implementará el interfaz que se corresponda con el tipo de evento que se desea tratar en el oyente. Un interfaz EventListener define uno o más métodos que serán invocados por la fuente del evento en respuesta a un tipo de evento específico. 174 © Grupo EIDOS 10. Interfaces de usuario en Java: gestores de diseño y eventos Las características que debe tener un oyente es que debe implementar el interfaz que se corresponda con el evento que desea tratar y debe registrarse como oyente de la fuente de eventos que va a disparar los eventos a tratar. Cualquier tipo de objeto se puede registrar como un "oyente" de eventos. Estos oyentes sólo reciben notificaciones de los eventos en los que están interesados, es decir, los eventos que provienen de la fuente para cual se han registrado. Una fuente de eventos es un objeto que origina o "dispara" eventos. La fuente define los eventos que van a se emitidos ofreciendo una serie de métodos de la forma add<Tipo de Evento>(), que son usados para registrar oyentes específicos para esos eventos. A los métodos add<Tipo de Evento>() se les pasa por parámetro una instancia del oyente que va a tratar los eventos que genere la fuente. Todos estos conceptos que estamos introduciendo acerca del tratamiento de eventos en Java, se verá mucho más claro cuando se muestren algunos ejemplos más adelante. Así por ejemplo, si tenemos el siguiente GUI: un botón y una caja de texto dentro de una ventana, y queremos que al pulsar el botón aparezca un mensaje en la caja de texto, el oyente será la ventana que contiene al botón y a la caja de texto, y la fuente será el botón que lanzará el evento correspondiente. Este modelo de eventos ofrece las siguientes características: • Ofrece un estructura robusta para soportar programas Java complejos. • Es simple y fácil de aprender. • Ofrece una clara separación entre el código de la aplicación y del interfaz de usuario, en lo que al tratamiento de eventos se refiere. • Facilita la creación de un código de tratamiento de eventos robusto y menos propenso a errores. Un oyente en lugar de implementar un interfaz, puede utilizar una clase que herede de una clase adaptadora de eventos del paquete java.awt.event. Las clases adaptadoras permiten sobrescribir solamente los métodos del interfaz en los que se esté interesado. Así por ejemplo, si queremos atrapar un click del ratón, en lugar de implementar el interfaz MouseListener, heredamos de la clase adaptadora de los eventos de ratón, es decir de la clase MouseAdapter, solamente deberemos sobrescribir el método mouseClicked(). Esto es posible debido a que las clases adaptadoras implementan el interfaz correspondiente y simplemente tienen implementaciones vacías de todos los métodos del interfaz EventListener. De esta forma se consigue un código más claro y limpio. Estas clases se suelen utilizar cuando se quiere hacer uso de un interfaz muy complejo del que sólo interesan un par de métodos. Dentro de la jerarquía de clases de Java hay una serie de clases para representar todos los eventos y otra serie de interfaces que definen una serie de métodos que deben implementar las clases que van a tratar los eventos, es decir, lo que hemos llamado oyentes. Otras clases que también hemos comentado y que se utilizan dentro del tratamiento de eventos en Java son las clases adaptadoras. Las clases adaptadoras las utilizaremos para simplificar nuestro código, ya que implementan de forma vacía todos los métodos de un interfaz de tratamiento de eventos determinado. Cada conjunto de eventos tiene asociado un interfaz, y como ya hemos dicho cada uno de estos interfaces declara una serie de métodos para cada uno de los eventos lógicos asociados al tipo de evento de que se trate. 175 Programación en Java © Grupo EIDOS Cada componente del AWT genera un tipo de eventos distinto, que está representado mediante una clase en el paquete java.awt.event. En la Tabla 21 y en la Tabla 22 se indica que eventos lanza cada clase perteneciente a los componentes del AWT. En las filas aparecen las clases de los componentes y en las columnas las clases de los eventos que pueden lanzar. ActionEvent Adjustment Component ContainerEvent Event Event FocusEvent X X X Canvas X X Checkbox X X Choice X X Component X X Container X X X Dialog X X X Frame X X X Label X X X X Button CheckBoxMenuItem List X MenuItem X X Panel X Scrollbar X X X Scrollpane X TextArea X X TextComponent X X X X TextField X X Window Tabla 21 176 X X X X X © Grupo EIDOS 10. Interfaces de usuario en Java: gestores de diseño y eventos ItemEvent KeyEvent MouseEvent TextEvent WindowsEvent Button X X Canvas X X X X X X Component X X Container X X Dialog X X X Frame X X X Label X X X X Panel X X Scrollbar X X Scrollpane X X TextArea X X X TextComponent X X X TextField X X X Window X X Checkbox X CheckBoxMenuItem X Choice X List X MenuItem X Tabla 22 Los interfaces que definen los métodos de cada evento van a ser utilizados por los oyentes, o bien implementándolos directamente o bien a través de las clases adaptadoras. Y las clases de los eventos se van a utilizar para obtener información del evento que se ha producido, el cual se pasará como parámetro a los métodos del interfaz correspondiente. En la Tabla 23 se muestra una relación de interfaces con sus métodos correspondientes y la clase adaptadora que le corresponde. 177 Programación en Java © Grupo EIDOS Interfaz Métodos Clase Adaptadora ActionListener AdjustmentListener ComponentListener actionPerformed(ActionEvent) ----ComponentAdapter adjustmentValueChanged(AdjustmentEvent) componentHidden(ComponentEvent) componentMoved(ComponentEvent) componentResized(ComponentEvent) componentShown(ComponentEvent) ContainerListener componentAdded(ContainerEvent) componentRemoved(ContainerEvent) ContainerAdapter FocusListener focusGained(FocusEvent) focusLost(FocusEvent) FocusAdapter ItemListener KeyListener ItemStateChanged(ItemEvent) KeyPressed(KeyEvent) keyReleased(KeyEvent) keyTyped(KeyEvent) --KeyAdapter MouseListener mouseClicked(MouseEvent) mouseEntered(MouseEvent) mouseExited(MouseEvent) mousePressed(MouseEvent) mouseReleased(MouseEvent) MouseAdapter MouseMotionListener mouseDragged(MouseEvent) mouseMoved(MouseEvent) MouseMotionAdapter TextListener WindowListener --WindowAdapter textValueChanged(TextEvent) windowActivated(WindowEvent) windowClosed(WindowEvent) windowClosing(WindowEvent) windowDeactivated(WindowEvent) windowDeiconified(WindowEvent) windowIconified(WindowEvent) windowOpened(WindowEvent) Tabla 23 Como se puede apreciar en la tabla anterior, los interfaces que únicamente poseen un método no tienen clase adaptadora correspondiente, ya que no tiene ningún sentido, siempre que utilizamos un interfaz con un único método vamos a implementarlo. A continuación vamos a comentar los pasos genéricos que se deben seguir a la hora de realizar el tratamiento de eventos en Java. Aunque los pasos son genéricos, para poder hacer referencia a un interfaz concreto vamos a suponer que queremos realizar el tratamiento de eventos que se corresponde con la pulsación de un botón. Lo primero es importar el paquete java.awt.event: import java.awt.event.*; Código fuente 102 178 © Grupo EIDOS 10. Interfaces de usuario en Java: gestores de diseño y eventos A continuación escribiremos la declaración de la clase para que implemente el interfaz adecuado (listener interface). Por ejemplo si se está tratando de atrapar un evento ActionEvent (es decir, una pulsación de un botón) generado por un botón, será necesario implementar el interfaz ActionListener. public class MiClase extends ClasePadre implements ActionListener{ Código fuente 103 Debemos determinar que componentes van a generar los eventos. Se registra cada uno de ellos con el tipo adecuado de oyente, si tenemos en cuenta el ejemplo anterior del botón, se debería escribir lo que indica el Código fuente 104. objetoBoton.addActionListener(this); Código fuente 104 Una vez hecho esto debemos crear las implementaciones de todos los métodos del interfaz que la clase debe implementar. public void actionPerformed(ActionEvent evento){ //cuerpo del método } Código fuente 105 Una vez comentado el tratamiento de eventos en Java de forma más o menos teórica vamos a comentar una serie de ejemplos para aplicar la teoría a la práctica. Estos ejemplos además nos van a servir para repasar distintos puntos del lenguaje Java comentados hasta ahora. Vamos a realizar una aplicación que vamos a ir modificando y complicando para mostrar las diferentes facetas del tratamiento de eventos en Java. No vamos a tratar todos los interfaces, sólo vamos a tratar tres de los más representativos, el que se encarga de las pulsaciones de botones, ActionListener, el que se encarga del ratón, MouseListener, y el que se encarga de las ventanas, WindowListener, estos tres nos ofrecen ejemplos bastante prácticos. También veremos los adaptadores MouseAdapter y WindowAdapter. Primero vamos a comenzar creando una aplicación sencilla que va a consistir simplemente en una ventana que nos va a indicar si se encuentra minimizada o no, y cuando pulsemos sobre el aspa de cerrar la ventana esta se cierre y finalice la ejecución de la aplicación. En la Figura 59 se puede ver el aspecto que tendría esta aplicación. 179 Programación en Java © Grupo EIDOS Figura 59 El código de esta aplicación de ejemplo es el que aparece en el Código fuente 106. import java.awt.*; //paquete necesario para el tratamiento de eventos import java.awt.event.*; public class Ventana extends Frame implements WindowListener{ //constructor de nuestra clase public Ventana(String titulo){ //constructor de la clase padre super(titulo); //tamaño de la ventana setSize(150,150); //se muestra la ventana show(); //se registra nuestra clase como oyente addWindowListener(this); } public void windowClosed(WindowEvent evento){ System.out.println("La ventana se ha cerrado"); //finaliza la aplicación System.exit(0); } public void windowIconified(WindowEvent evento){ System.out.println("Estoy minimizada"); } public void windowDeiconified(WindowEvent evento){ System.out.println("No estoy minimizada"); } public void windowClosing(WindowEvent evento){ //se cierra la ventana dispose(); } public void windowDeactivated(WindowEvent evento){} public void windowActivated(WindowEvent evento){} public void windowOpened(WindowEvent evento){} public static void main (String[] args){ Ventana miVentana=new Ventana("Eventos"); } } Código fuente 106 Vamos a comentar algunos puntos importantes acerca del código anterior. Vamos a comentar algunas consideraciones de carácter general, en primer lugar, al ser una aplicación debe tener un método 180 © Grupo EIDOS 10. Interfaces de usuario en Java: gestores de diseño y eventos main() de arranque, y como hemos dicho que es una ventana, hereda de la clase Frame. Como se puede observar hemos creado también un método constructor para nuestra clase Ventana. En lo concerniente al tratamiento de eventos, como se puede ver importamos el paquete correspondiente e indicamos que nuestra clase implementa el interfaz WindowListener. En el constructor de nuestra clase indicamos quien va a ser el oyente de nuestra clase, es decir, quien va a tratar los eventos, en este caso el oyente es nuestra misma clase. Para ello utilizamos la línea de código que aparece en el Código fuente 107. addWindowListener(this); Código fuente 107 Es decir, indicamos que el oyente de los eventos de la ventana va a ser nuestra propia clase, es decir, estamos registrando nuestra clase como oyente mediante la referencia this. Al ser nuestra clase fuente y oyente de los eventos, también debe implementar el interfaz que define los métodos que se van a ejecutar atendiendo al evento que se produzca. En nuestro caso hay tres métodos que no nos interesan, pero como implementamos el interfaz WindowListener debemos implementarlos aunque no tengan contenido. Los métodos windowIconified() y windowDeiconified() se ejecutarán cuando se minimice la ventana y cuando se restaure, respectivamente, el método windowClosing() se ejecuta en el momento de cerrar la ventana y el método windowClosed() cuando ya se ha cerrado. El resto del código es bastante sencillo y considero que no necesita una mayor explicación. Como ya hemos comentado hay tres métodos del interfaz WindowListener que no nos interesan y que por lo tanto nos gustaría suprimir, en este momento entran en juego las clases adaptadoras. Como ya debe saber el alumno, las clases adaptadoras realizan una implementación vacía de todos los métodos del interfaz con el que se corresponden, y al heredar de ellas utilizaremos únicamente los métodos del interfaz correspondiente que nos interese. En nuestro caso no podemos heredar de una clase adaptadora, ya que heredamos de la clase Frame y la herencia múltiple no se permite en Java. Por lo tanto deberemos crear y definir una nueva clase que herede de la clase adaptadora del interfaz WindowListener, es decir, que herede de la clase WindowAdapter. Por lo tanto si queremos hacer uso de la clase adaptadora WindowAdapter, el código de nuestra aplicación se debe modificar como indica el Código fuente 108. import java.awt.*; import java.awt.event.*; //nuestra clase ya no implementa el interfaz WindowListener public class Ventana extends Frame{ public Ventana(String titulo){ super(titulo); setSize(150,150); show(); //se registra como oyente la clase que hereda la clase adaptadora addWindowListener(new AdaptadorVentana(this)); } 181 Programación en Java © Grupo EIDOS public static void main (String[] args){ Ventana miVentana=new Ventana("Eventos"); } } //clase que hereda de la clase adaptadora class AdaptadorVentana extends WindowAdapter{ //atributo que se utiliza como referencia a la clase //que es la fuente del evento que queremos tratar private Ventana fuente; //constructor de nuestra clase adaptadora //recibe como parámetro la clase fuente del evento public AdaptadorVentana(Ventana fuente){ this.fuente=fuente; } //contiene la implementación de los métodos que nos intersan //del interfaz WindowListener public void windowClosed(WindowEvent evento){ System.out.println("La ventana se ha cerrado"); System.exit(0); } public void windowIconified(WindowEvent evento){ System.out.println("Estoy minimizada"); } public void windowDeiconified(WindowEvent evento){ System.out.println("No estoy minimizada"); } public void windowClosing(WindowEvent evento){ fuente.dispose(); } } Código fuente 108 Los mayores cambios se aprecian en que existe una nueva clase que va a ser la encargada de tratar los eventos, en este caso si que existe una separación clara entre clase fuente y clase oyente. El oyente sería la clase AdaptadorVentana y la clase fuente sería la clase principal Ventana. Nuestra clase Ventana, al ser sólo fuente de eventos no va a implementar el interfaz WindowListener y por lo tanto únicamente va a tener en su cuerpo el método constructor y el método main(). Otra cosa que cambia también en el código de nuestra aplicación es la forma en la que se registra el oyente. Se sigue utilizando el método addWindowListener() pero en este caso se pasa por parámetro una instancia de la clase adaptadora que hemos creado nosotros. addWindowListener(new AdaptadorVentana(this)); Código fuente 109 Ahora vamos a detenernos en la clase AdaptadorVentana. Esta clase hereda de la clase WindowAdapter y posee un atributo denominado fuente que es de la clase Ventana. Este atributo es necesario para tener una referencia a la clase fuente del evento. Esta referencia es necesaria ,en este caso, dentro del método windowClosing(), a la hora de cerrar la ventana. 182 © Grupo EIDOS 10. Interfaces de usuario en Java: gestores de diseño y eventos public void windowClosing(WindowEvent evento){ fuente.dispose(); } Código fuente 110 Para conseguir la referencia a la fuente del evento se pasa una instancia de la clase Ventana actual al constructor de la clase adaptadora. De esta forma la clase adaptadora puede manipular y acceder al objeto de clase Ventana. public AdaptadorVentana(Ventana fuente){ this.fuente=fuente; } Código fuente 111 Como se puede comprobar esta nueva clase contiene todos los métodos que nos interesan del interfaz WindowListener. Si queremos tratar distintos eventos mediante clases adaptadoras, deberemos crear una clase que herede de la clase adaptadora de cada tipo de evento, siempre que exista una clase adaptadora para el evento en cuestión. También existe la posibilidad de utilizar la clases adaptadoras como clases internas (inner classes). Una clase interna es una clase definida dentro de otra. El beneficio que podemos obtener de estas clases adaptadoras, es que no tenemos porque llevar la referencia de la clase fuente. La clase interna va a tener acceso a todos los métodos y atributos de la clase en la que está declarada, aunque estos sean privados. Realmente una clase interna se sale fuera de los principios de la POO, pero puede ser bastante práctica. El nuevo código tendría el aspecto que muestra el Código fuente 112. import java.awt.*; import java.awt.event.*; public class Ventana extends Frame{ public Ventana(String titulo){ super(titulo); setSize(150,150); show(); //ya no se indica la referencia de la clase fuente addWindowListener(new AdaptadorVentana()); } public static void main (String[] args){ Ventana miVentana=new Ventana("Eventos"); } //clase adaptadora interna class AdaptadorVentana extends WindowAdapter{ //Ya no es necesario el atributo fuente public void windowClosed(WindowEvent evento){ System.out.println("La ventana se ha cerrado"); System.exit(0); } 183 Programación en Java © Grupo EIDOS public void windowIconified(WindowEvent evento){ System.out.println("Estoy minimizada"); } public void windowDeiconified(WindowEvent evento){ System.out.println("No estoy minimizada"); } public void windowClosing(WindowEvent evento){ //llamamos directamente al método dispose() dispose(); } }//se cierra la clase interna } Código fuente 112 Y para rizar el rizo, vamos a ofrecer una posibilidad distinta a la hora de tratar los eventos en Java, se puede utilizar una clase interna anónima, el código sería el Código fuente 113. import java.awt.*; import java.awt.event.*; public class Ventana extends Frame{ public Ventana(String titulo){ super(titulo); setSize(150,150) show(); //Utilizamos una clase interna anónima addWindowListener(new WindowAdapter(){ //métodos de la clase anónima public void windowClosed(WindowEvent evento){ System.out.println("La ventana se ha cerrado"); System.exit(0); } public void windowIconified(WindowEvent evento){ System.out.println("Estoy minimizada"); } public void windowDeiconified(WindowEvent evento){ System.out.println("No estoy minimizada"); } public void windowClosing(WindowEvent evento){ dispose(); } });//se cierra la clase interna anónima } public static void main (String[] args){ Ventana miVentana=new Ventana("Eventos"); } } Código fuente 113 A la vista del código se puede ver que se trata de instanciar una clase sin indicar un objeto que contenga una referencia a la misma, ya que en realidad esta clase sólo se va a utilizar para el tratamiento de eventos y no se va a querer hacer una referencia a ella. El mecanismo es muy sencillo, se utiliza el nombre de la clase adaptadora correspondiente, WindowAdapter en nuestro caso, y se implementan los métodos que se consideren necesarios, por lo demás funciona exactamente igual a una clase interna. 184 © Grupo EIDOS 10. Interfaces de usuario en Java: gestores de diseño y eventos Una vez comentados las distintas opciones que tenemos a la hora de tratar los eventos, ahora vamos a añadir a nuestra aplicación un botón para que al pulsarlo escriba un mensaje en la pantalla. En este caso deberemos implementar el interfaz ActionListener, ya que este interfaz no posee una clase adaptadora, al tener un único método llamado actionPerformed(). El nuevo aspecto de nuestra aplicación de ejemplo se puede observar en la Figura 60. Figura 60 Y el nuevo código es el Código fuente 114. import java.awt.*; import java.awt.event.*; //implementamos el interfaz para tratar la pulsación del botón public class Ventana extends Frame implements ActionListener{ private Button boton; public Ventana(String titulo){ super(titulo); setSize(150,150); setLayout(new FlowLayout()); boton=new Button("Púlsame"); add(boton); show(); addWindowListener(new AdaptadorVentana()); //oyente del botón, en este caso la clase Ventana boton.addActionListener(this); } public void actionPerformed(ActionEvent evento){ System.out.println("Buenas tardes"); } public static void main (String[] args){ Ventana miVentana=new Ventana("Eventos"); } class AdaptadorVentana extends WindowAdapter{ public void windowClosed(WindowEvent evento){ System.out.println("La ventana se ha cerrado"); System.exit(0); } public void windowIconified(WindowEvent evento){ System.out.println("Estoy minimizada"); } public void windowDeiconified(WindowEvent evento){ System.out.println("No estoy minimizada"); } public void windowClosing(WindowEvent evento){ dispose(); } } } Código fuente 114 185 Programación en Java © Grupo EIDOS Como se puede observar la fuente del evento en este nuevo caso va a ser por un lado, el botón y su oyente la clase Ventana, y por otro lado la fuente de eventos va a ser la clase Ventana y el oyente la clase interna AdaptadorVentana. Las líneas utilizadas para añadir el botón se encuentran en el constructor, la única línea digna de mención es la que registra el oyente del botón (Código fuente 115). boton.addActionListener(this); Código fuente 115 La clase Ventana al implementar el interfaz ActionListener debe facilitar el método actionPerformed(), que se ejecutará al pulsar el botón. Al pulsar el botón veremos en pantalla el saludo "Buenas tardes". public void actionPerformed(ActionEvent evento){ System.out.println("Buenas tardes"); } Código fuente 116 Para finalizar el presente capítulo modificaremos de nuevo nuestra aplicación, añadiendo un área de texto, en la que se escribirá si el puntero del ratón se encuentra en el área de texto, o por el contrario ha salido. Aquí vamos a tratar un nuevo tipo de evento, en este caso los eventos del ratón, y lo vamos a realizar a través de la clase adaptadora MouseAdapter. La clase MouseAdapter es la clase adaptadora del interfaz MouseListener. Utilizamos la clase adaptadora, porque sólo nos van a interesar un par de métodos del interfaz MouseListener. El nuevo aspecto de la aplicación de ejemplo es el de la Figura 61. Y el código actualizado de la aplicación es el que aparece en el Código fuente 117. Figura 61 import java.awt.*; import java.awt.event.*; 186 © Grupo EIDOS 10. Interfaces de usuario en Java: gestores de diseño y eventos public class Ventana extends Frame implements ActionListener{ private Button boton; private TextArea area; public Ventana(String titulo){ super(titulo); setLayout(new FlowLayout()); boton=new Button("Púlsame"); area=new TextArea(); add(boton); add(area); show(); pack(); addWindowListener(new AdaptadorVentana()); boton.addActionListener(this); //El oyente del ratón es una nueva clase interna area.addMouseListener(new AdaptadorRaton()); } public void actionPerformed(ActionEvent evento){ System.out.println("Buenas tardes"); } public static void main (String[] args){ Ventana miVentana=new Ventana("Eventos"); } //clase adaptadora para los eventos del ratón class AdaptadorRaton extends MouseAdapter{ public void mouseEntered(MouseEvent evento){ area.setText("El ratón ha entrado"); } public void mouseExited(MouseEvent evento){ area.setText("El ratón ha salido"); } } class AdaptadorVentana extends WindowAdapter{ public void windowClosed(WindowEvent evento){ System.out.println("La ventana se ha cerrado"); System.exit(0); } public void windowIconified(WindowEvent evento){ System.out.println("Estoy minimizada"); } public void windowDeiconified(WindowEvent evento){ System.out.println("No estoy minimizada"); } public void windowClosing(WindowEvent evento){ dispose(); } } } Código fuente 117 En esta última versión de la aplicación de prueba tenemos una nueva fuente de eventos, que va a ser el área de texto, y un nuevo oyente que es la clase adaptadora AdaptadorRaton. La clase Ventana ofrece un nuevo atributo que va a representar el área de texto. Para registrar el oyente del área de texto se utiliza el método addMouseListener(), a este método se le pasa una instancia de la clase adaptadora que va a tratar los eventos del ratón, en este caso se trata de la clase AdaptadorRaton. Esta clase implementa los métodos mouseEntered() y mouseExited() que se lanzarán cuando en ratón entre en el área de texto o salga del mismo, respectivamente. Como se puede apreciar cada oyente se encarga de los eventos para los cuales se ha registrado, sin tener en cuenta el resto de los que se produzca y sin interferir entre sí. 187 Interfaces de usuario en Java: componentes Swing / contenedores Introducción A lo largo de los siguientes tres capítulos vamos a tratar el segundo tipo de componentes que ofrece Java para construir interfaces de usuario, es decir, los componentes Swing incluidos en el paquete javax.swing. Muchos de los conceptos vistos en capítulos anteriores, como pueden ser los eventos o los gestores de diseño siguen siendo válidos para los componentes Swing, así por ejemplo, los gestores de diseño que utilizan los componentes Swing son los mismos que los vistos en los componentes AWT, de hecho se debe importar el paquete JAVA.AWT para utilizarlos, pero Swing aporta un nuevo gestor de diseño (que veremos en el siguiente capítulo, también dedicado por completo al extenso mundo de los componentes Swing) que se encuentra en el paquete javax.swing. Lo mismo sucede con los eventos, los componentes Swing utilizan las clases de los eventos incluidas en el paquete java.awt.event, y además añaden nuevos eventos que se pueden encontrar en el paquete javax.swing.event. Como hemos dicho vamos a tener tres capítulos dedicados a Swing. Swing es bastante extenso y aun utilizando tres capítulos no lo vamos a ver completamente, sería necesario un curso de la misma extensión de éste (o más) para tratar Swing de forma completa. En este primer capítulo vamos a introducir una serie de conceptos de Swing y también se va a tratar un tipo de componentes Swing, los contenedores Swing. Programación en Java © Grupo EIDOS En el segundo capítulo trataremos el otro gran grupo de componentes Swing, los componentes atómicos. Y en el último de esta serie de capítulos trataremos otras características de Swing como son el nuevo gestor de diseño que incorpora (implementado por la clase BoxLayout) y la característica Pluggable Look & Feel, es decir, aspecto y comportamiento configurables. JFC y Swing JFC (Java Foundation Classes) es el nombre que recibe el conjunto de un gran número de características cuya función primordial es la de permitir la construcción de complejos interfaces de usuario. El primer anuncio de JFC se realizó en la conferencia de desarrolladores JavaOne y se definió mediante las características que iba a ofrecer: • Componentes Swing: comprenden todos los componentes utilizados para interfaces de usuario desde botones, barras de menú, diálogos y ventanas hasta cajas de texto, barras de progreso, paneles con pestañas y listas. A lo largo de este capítulo describiremos algunos de los componentes Swing, no todos ya que Swing ofrece un gran número de clases agrupadas en 15 paquetes distintos. • Soporte para Pluggable Look and Feel: es decir, soporte para una apariencia y comportamiento configurables. Permite a cualquier programa que utilice componentes Swing definir un tipo de apariencia y comportamiento (Look and Feel). Así por ejemplo una misma aplicación puede presentar una apariencia y comportamiento al estilo de Java o bien tipo Windows. En el apartado correspondiente volveremos a retomar esta característica y la trataremos en detalle. • API de accesibilidad: permite a tecnologías de rehabilitación tales como lectores de pantalla y displays Braille obtener información acerca del interfaz de usuario. El API de accesibilidad se encuentra formando parte de las características avanzadas del lenguaje Java y por lo tanto no lo vamos a tratar en este curso. • API Java 2D: al igual que ocurría con la característica o funcionalidad anterior, se trata de un conjunto de clases especializadas, en este caso, en el tratamiento de gráficos en dos dimensiones, imágenes y texto. El API Java 2D se escapa también del alcance y pretensiones de este curso. • Soporte para arrastrar y soltar (Drag and Drop): permite la posibilidad de arrastrar y soltar componentes entra aplicaciones Java y aplicaciones en otros lenguajes. En muchos casos se utilizan indistintamente los términos JFC y Swing sin hacer ningún tipo de distinción, Swing fue el nombre en clave que recibió el proyecto de Sun encargado de desarrollar los nuevos componentes para la construcción de interfaces de usuario. La denominación swing aparece también en los paquetes correspondientes. Los componentes Swing se encuentran disponibles de dos formas distintas, como parte de la plataforma Java 2, tanto en la herramienta JDK 1.2 o como en el JDK 1.3, y como una extensión del JDK 1.1 (versión de Java 1.1) denominada JFC 1.1. En nuestro caso vamos a utilizar la versión que se encuentran integrada con la plataforma Java 2. 190 © Grupo EIDOS 11. Interfaces de usuario en Java: componentes Swing / contenedores Componentes Swing frente a componentes AWT En este apartado vamos a comentar las diferencias entre los dos grupos de componentes que ofrece Java para la creación de interfaces de usuario, y el porqué de la existencia de dos grupos distintos de componentes. Los componentes AWT aparecieron en la versión 1.0 del JDK y eran los únicos componentes que se encontraban disponibles para desarrollar interfaces de usuario. Los componentes AWT se utilizan principalmente en las versiones 1.0 y 1.1 del lenguaje (que coincidían con las versiones de la herramienta JDK), aunque la plataforma Java 2 soporta perfectamente los componentes AWT como hemos podido comprobar en capítulos anteriores. Desde Sun nos recomiendan que utilicemos si es posible los componentes Swing en lugar de los componentes AWT. Los componentes Swing los podemos identificar porque los nombres de sus clases suelen ir precedidos por la letra J. Así por ejemplo, la clase Button del AWT, tiene su clase correspondiente en Swing denominada JButton. Los componentes AWT se encuentran en el paquete JAVA.AWT y los Swing en el paquete javax.swing. La mayor diferencia entre los componentes AWT y los componentes Swing, es que los componentes Swing se encuentran implementados sin utilizar ningún tipo de código nativo. Los componentes Swing se denominan componentes ligeros (lightweight) y los componentes AWT componentes pesados (heavyweight). Otra diferencia importante es la gran potencia y funcionalidad que ofrecen los componentes Swing. incluso los componentes Swing más sencillos ofrecen una serie de posibilidades que los componentes AWT no recogen, algunas de estas ventajas de los componentes Swing frente a los AWT son las que se enumeran a continuación: • Los botones Swing pueden mostrar imágenes y/o texto. • Es posible situar bordes en torno a los componentes Swing. • Los componentes Swing no tienen porque ser rectangulares, por ejemplo, los botones pueden ser redondos. • Las tecnologías de rehabilitación pueden obtener información de los componentes Swing de forma sencilla. Swing nos permite especificar el aspecto y comportamiento (look and feel) del interfaz de usuario de nuestra aplicación (más adelante veremos como), sin embargo los componentes AWT tienen el aspecto y comportamiento de la plataforma nativa sobre la que se ejecutan. Vamos a comentar brevemente algunas de las razones por las que nos puede interesar utilizar componentes Swing en lugar de componentes AWT. • El rico conjunto de componentes que ofrece Swing: botones con imágenes, barras de herramientas, imágenes, elementos de menú, selectores de color, etc. • La arquitectura Pluggable Look & Feel, es decir, aspecto y comportamiento configurable y seleccionable para los elementos del interfaz de usuario. • Posiblemente en un futuro se ampliarán los componentes Swing disponibles. 191 Programación en Java © Grupo EIDOS Parece hasta ahora que con los componentes Swing todo son ventajas, e incluso que ni siquiera debemos plantearnos que tipo de componentes debemos utilizar para construir interfaces de usuario, siempre elegiremos componentes Swing. Pero este razonamiento no es correcto, ya que en la práctica no resulta tan sencillo. Se debe señalar que, desgraciadamente, todavía no existen navegadores Web que soporten Swing, más claro todavía, los navegadores Web actuales, incluso en sus últimas versiones, no implementan la máquina virtual correspondiente a la plataforma Java 2. Por lo tanto si queremos construir applets que puedan ejecutarse en cualquier navegador deberemos construir su interfaz gráfica mediante componentes AWT, aunque en uno de los temas dedicados a los applets veremos un pequeño parche que utilizaremos para que los navegadores soporten componentes Swing. Contenedores de alto nivel Bajo esta nomenclatura se agrupan una serie de clases que se corresponden con componentes Swing que realizan la función de contenedores de otros componentes y que constituyen la raíz en la jerarquía de contenedores. Los componentes de alto nivel se basan en los siguientes principios: • Swing ofrece tres clases que representan a componentes de alto nivel: JFrame (ventana), JDialog (diálogo) y JApplet. También dentro de los componentes Swing encontramos una cuarta clase denominada JWindow que representa una ventana sin controles ni título y que se encuentra siempre por encima de cualquier ventana, es el equivalente a la clase Window de los componentes AWT. A lo largo de este capítulo iremos viendo como en los componentes Swing encontramos equivalentes de los componentes AWT. • Para aparecer en pantalla todo componente de interfaz de usuario debe formar parte de una jerarquía de contenedores. Cada jerarquía de contenedores posee un contenedor de alto nivel como raíz. • Cada contenedor de alto nivel posee un panel de contenido (content pane) que contiene los componentes visibles del contenedor de alto nivel del interfaz de usuario correspondiente. • Opcionalmente se puede añadir una barra de menú a un contenedor de alto nivel. La barra de menú se sitúa en el contenedor de alto nivel, pero fuera del panel de contenido. Veamos un sencillo ejemplo que muestra un contenedor de alto nivel. Se trata de un objeto de la clase JFrame que contiene una barra de menú (JMenuBar) de color azul y una etiqueta (JLabel) de color amarillo. En la Figura 62 se puede ver el aspecto del contenedor. Figura 62 192 © Grupo EIDOS 11. Interfaces de usuario en Java: componentes Swing / contenedores Y el código fuente completo de este ejemplo es el que aparece en el Código fuente 118. import java.awt.*; import javax.swing.*; public class ContenedorAltoNivel { public static void main(String s[]) { //se crea la ventana raíz de la jerarquía de contenedores JFrame ventana = new JFrame("Contenedor de alto nivel"); //se crea la etiqueta JLabel etiquetaAmarilla = new JLabel(""); etiquetaAmarilla.setOpaque(true); etiquetaAmarilla.setBackground(Color.yellow); etiquetaAmarilla.setPreferredSize(new Dimension(200, 180)); //se crea la barra de menú JMenuBar barraMenuCyan = new JMenuBar(); barraMenuCyan.setOpaque(true); barraMenuCyan.setBackground(Color.cyan); barraMenuCyan.setPreferredSize(new Dimension(200, 20)); //se añaden la etiqueta y la barra de menú a la ventana ventana.setJMenuBar(barraMenuCyan); ventana.getContentPane().add(etiquetaAmarilla, BorderLayout.CENTER); //se muestra la ventana ventana.pack(); ventana.setVisible(true); } } Código fuente 118 A la vista de este código podemos realizar los siguientes comentarios. Se han importado los paquetes JAVA.AWT y javax.swing. El paquete JAVA.AWT es necesario para utilizar las clases Dimension, Color y BorderLayout, como se puede observar aunque utilicemos componentes Swing vamos a necesitar de los componentes AWT. También podemos comprobar la forma en la que se añade un componente a un panel de contenido de un contenedor de alto nivel como puede ser un objeto de la clase JFrame, y también como añadir una barra de menú, en este mismo apartado veremos en más detalle el código que realiza estas funciones. La jerarquía de contenedores del ejemplo queda como muestra el esquema de la Figura 63 Figura 63 193 Programación en Java © Grupo EIDOS Veamos un ejemplo más para ilustrar las jerarquías de contenedores. Para ello nos debemos fijar en la Figura 64. Figura 64 En este ejemplo se han utilizado los siguientes componentes Swing: • Una ventana principal (JFrame). • Un panel (JPanel). • Un botón (JButton). • Una etiqueta (JLabel). Al panel se le ha añadido un borde mediante la clase BorderFactory para mostrar el agrupamiento de forma más clara entre el botón y la etiqueta. El código fuente de este ejemplo es el que aparece en el Código fuente 119. import javax.swing.*; import java.awt.*; public class Ventana{ public static void main(String[] args) { //se establece el Look & Feel try { UIManager.setLookAndFeel( UIManager.getCrossPlatformLookAndFeelClassName()); } catch (Exception e) { } //se crean los componentes JFrame ventana = new JFrame("Ventana"); JLabel etiqueta = new JLabel(" Soy una etiqueta"); JButton boton = new JButton("Soy un botón"); JPanel panel = new JPanel(); //se asigna un borde y un gestor de diseño al panel panel.setBorder(BorderFactory.createLineBorder(Color.red,5)); panel.setLayout(new GridLayout(0, 1)); //se añaden al panel el botón y la etiqueta panel.add(boton); panel.add(etiqueta); //se añade el panel al panel de contenido de la ventana ventana.getContentPane().add(panel, BorderLayout.CENTER); //se muestra la ventana ventana.pack(); ventana.setVisible(true); } } Código fuente 119 194 © Grupo EIDOS 11. Interfaces de usuario en Java: componentes Swing / contenedores Este código adelanta algo que veremos en detalle en el apartado correspondiente, cómo asignar un aspecto y comportamiento (Look & Feel) determinado a nuestro interfaz de usuario, esto aparece en las primeras líneas del método main() y en este caso se establece el aspecto y comportamiento de Java. Vamos a pasar a describir las funciones que presentan cada uno de los componentes Swing utilizados en el ejemplo. En este ejemplo la ventana representada mediante la clase JFrame es el contenedor de alto nivel, igualmente podría tratarse de otro componente de alto nivel como podría ser un objeto de la clase JApplet o JDialog. El panel es un contenedor intermedio, su propósito es el de simplificar la situación del botón y la etiqueta, para que sea visible se le ha asignado un borde de color rojo. Otros contenedores intermedios pueden ser paneles con pestañas (JTabbedPane) y paneles de scroll (JScrollPane), que juegan un papel más visible e interactivo dentro del interfaz de usuario. Y por último y el botón y la etiqueta se denominan componentes atómicos, son componentes que no contienen otros componentes Swing, como ocurría con los anteriores, sino que tienen entidad suficiente para presentar por sí mismos información al usuario. A menudo los componentes atómicos tienen como función obtener información del usuario. Swing ofrece un gran número de componentes atómicos (en este capítulo veremos algunos de ellos) como pueden ser listas desplegables (JComboBox), cajas de texto (JTextField) o tablas (JTable). En la Figura 65 se muestra el diagrama con la jerarquía de contenedores que presenta este ejemplo. Figura 65 Como se puede observar en la Figura 65, incluso el programa Swing más sencillo presenta múltiples niveles en su jerarquía de contenedores, además, como ya hemos dicho la raíz de esta jerarquía siempre es un contenedor de alto nivel. Cada contenedor de alto nivel indirectamente contiene un contenedor intermedio llamado panel de contenido (content pane), y normalmente el panel de contenido contiene, directa o indirectamente, todos los componentes visibles de la ventana del interfaz de usuario. La excepción a esta regla son las barras de menú, que se sitúan en un lugar especial fuera del panel de contenido, como vimos en el primer ejemplo de este apartado. 195 Programación en Java © Grupo EIDOS Para añadir un componente a un contenedor se utiliza una de las distintas formas del método add(), como ya vimos en el tema dedicado a los componentes AWT. Como regla general, una aplicación Java con un interfaz de usuario basado en componentes Swing, tiene al menos una jerarquía de contenedores con un objeto de la clase JFrame como raíz de la misma. Por ejemplo, si una aplicación tiene una ventana principal y dos diálogos, la aplicación presentará tres jerarquías de contenedores, y por lo tanto tres contenedores de alto nivel. Una de las jerarquías de contenedores tiene a un objeto de la clase JFrame como raíz, y cada una de las otras dos jerarquías un objeto de la clase JDialog como raíz. Un applet basado en Swing tiene al menos una jerarquía de contenedores y su raíz es un objeto de la clase JApplet. Por ejemplo un applet que muestra un diálogo tiene dos jerarquías de contenedores, los componentes en la ventana del navegador tienen como raíz un objeto JApplet, y los que se encuentran en el diálogo tienen como raíz de su jerarquía de componentes un objeto de la clase JDialog. Hasta ahora hemos visto un par de ejemplos que por un lado nos han mostrado como funcionan las jerarquías de contenedores, y por otro, nos ha mostrado como utilizar algunos de los componentes Swing, aunque más adelante veremos algunos de ellos con más detalle. Para obtener una referencia al panel de contenido de un componente de alto nivel debemos utilizar el método getContentPane(), que devuelve un objeto de la clase Container. Por defecto el panel de contenido es un contenedor intermedio que hereda de la clase JComponent y que tiene como gestor de diseño un BorderLayout. Una vez que tenemos una referencia al panel de contenido podremos añadir los componentes que consideremos necesarios utilizando una sentencia similar a la que muestra el Código fuente 120. ventana.getContentPane().add(componente,BorderLayout.CENTER); Código fuente 120 Todos los contenedores de alto nivel pueden tener, en teoría, una barra de menú, sin embargo en la práctica las barras de menú se utilizan únicamente en ventanas y en raras ocasiones en applets. Para añadir una barra de menú a un contenedor de alto nivel, crearemos un objeto de la clase JMenuBar, añadiremos los elementos de menú que se consideren necesarios y por último se lanzará sobre el contenedor de alto nivel el método setMenuBar(), pasándole por parámetro el objeto JMenuBar correspondiente. ventana.setMenuBar(objMenuBar); Código fuente 121 Todo contenedor de alto nivel además de poseer un panel de contenido, poseen otro panel llamado panel raíz (root pane). Normalmente este panel intermedio no se suele utilizar, su función es la de gestionar el panel de contenido y la barra de menú junto con otros dos contenedores. Estos dos contenedores son el panel de capas (layered pane) y el panel de cristal (glass pane). El panel de capas contiene directamente la barra de menú y el panel de contenido, y además permite ordenar los componentes que se vayan añadir con detenimiento (Z-order), es decir, permite organizar los 196 © Grupo EIDOS 11. Interfaces de usuario en Java: componentes Swing / contenedores componentes del interfaz de usuario en capas. Esto puede ser útil para mostrar menús de aparición súbita (popup menus) por encima de otros componentes. El panel de cristal suele permanecer oculto y se encuentra por encima de todos los elementos del panel raíz. Este panel es útil para interceptar eventos o pintar sobre un área que ya contiene otros componentes, se utiliza sobre todo para interceptar eventos de entrada que suceden sobre el contenedor de alto nivel. En la Figura 66 se puede ver un esquema que muestra la disposición de los distintos paneles que podemos encontrar en un componente de alto nivel. Figura 66 A continuación vamos a comentar los distintos componentes de alto nivel: JFrame, JDialog y JApplet. JFrame Ya hemos visto este componente Swing realizando las labores de contenedor de alto nivel, un objeto de la clase JFrame representa a una ventana con bordes, título y botones que permiten cerrar y maximizar la ventana. Las aplicaciones Java que poseen interfaz de usuario al menos utilizan un JFrame, y también lo hacen a veces los applets. Veamos la utilización de la clase JFrame mediante un sencillo ejemplo. Se trata de una ventana que contiene una etiqueta y que al pulsar el cierre de la misma se finalizará la ejecución de la aplicación. Su código es el Código fuente 122. import java.awt.*; import java.awt.event.*; import javax.swing.*; public class Ventana{ public static void main(String s[]) { JFrame ventana = new JFrame("Ventana Sencilla"); ventana.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); JLabel etiqueta = new JLabel("Soy una etiqueta"); etiqueta.setPreferredSize(new Dimension(175, 100)); 197 Programación en Java © Grupo EIDOS ventana.getContentPane().add(etiqueta, BorderLayout.CENTER); ventana.pack(); ventana.setVisible(true); } } Código fuente 122 Y el aspecto de la ventana es el que vemos en la Figura 67. Figura 67 En la primera línea del método main() se utiliza el constructor de la clase JFrame que nos permite indicar el título de la ventana mediante una cadena que pasamos como parámetro, otra versión de este constructor es sin argumentos. A continuación se añade un oyente para los eventos de la ventana, en este caso se implementa únicamente el método windowClosing() para finalizar la ejecución de la aplicación. En las siguientes líneas se crea y añade una etiqueta (JLabel) al panel de contenido de la ventana. Por último se lanzan los métodos pack() y setVisible() sobre la instancia de la clase JFrame. El método pack() de un tamaño a la ventana de forma que todos sus contenidos tengan el tamaño especificado o superior, una alternativa al método pack() es le método setSize() en el que se puede especificar de forma explícita las dimensiones de la ventana. Al método setVisible() se le pasa el parámetro true para que muestre la ventana en la pantalla. Por defecto cuando se pulsa el botón de cierre de la ventana, la ventana se oculta, sin la necesidad de utilizar ningún tratamiento de eventos, para cambiar este comportamiento se puede utilizar el método setDefaultCloseOperation() o bien implementar un tratamiento de eventos similar al del ejemplo. El parámetro que se le pasa al método setDefaultCloseOperation() debe ser una de las siguientes constantes: 198 • DO_NOTHING_ON_CLOSE: en este caso cuando se pulsa el botón de cierre de la ventana no ocurre nada, la ventana sigue visible. • HIDE_ON_CLOSE: es el valor por defecto, cuando se pulsa el botón de cierre la ventana se oculta, pero sigue existiendo, teniendo la posibilidad de mostrarse la ventana de nuevo si así lo indicamos en el programa. • DISPOSE_ON_CLOSE: al cerrar la ventana se elimina de la memoria y de la pantalla, es decir, en este caso se liberan todos los recursos que estaban siendo utilizados por la ventana. © Grupo EIDOS 11. Interfaces de usuario en Java: componentes Swing / contenedores La clase JFrame hereda de la clase java.awt.Frame y por lo tanto hereda todos los métodos de la clase Frame, como pueden ser setSize(), pack(), setTitle(), setVisible() y getTitle(). Además de los métodos de la clase java.awt.Frame, la clase JFrame tiene los siguientes métodos propios: • void setDefaultCloseOperation(int): asigna el tipo de operación que va a realizar la ventana cuando el usuario pulse el botón de cierre de la ventana. Ya hemos visto las posibles acciones. • int getDefaultCloseOperation(): devuelve la operación de cierre por defecto asignada a la ventana. • void setContentPane(Container): asigna a la ventana un panel de contenido, que va a ser el que contenga todos los componentes visibles del interfaz de usuario de la ventana. Este método permite utilizar nuestro propio panel de contenido, lo normal es utilizar un objeto de la clase JPanel para crear el panel de contenido, en el Código fuente 123 se puede observar como se crea un objeto JPanel que luego va a ser el panel de contenido de una ventana. JPanel panelContenido=new JPanel(); panelContenido.setLayout(new BorderLayout()); panelContenido.add(componente,BorderLayout.CENTER); panelContenido.add(otroComponente,BorderLayout.SOUTH); ventana.setContentPane(panelContenido); Código fuente 123 • Container getContentPane(): devuelve el panel de contenido de la ventana, este método se suele utilizar para una vez que tenemos una referencia al panel de contenido de la ventana añadir componentes a la misma. • void setMenuBar(MenuBar): asigna a la ventana una barra de menú determinada representada por un objeto de la clase MenuBar. • JMenuBar getMenuBar(): devuelve la barra de menú que tiene asignada la ventana. • void setGlassPane(Component): permite asignar un panel de cristal a la ventana. • Component getGlassPane(): devuelve el panel de cristal de la ventana. JDialog, JOptionPane Vamos a pasar ahora a comentar el segundo tipo de contenedores de alto nivel, los diálogos, que son un tipo de ventana más limitada que las ventanas representadas por la clase JFrame. Hay varias clases que ofrecen diálogos: • JOptionPane: permiten crear sencillos diálogos estándar. • ProgressMonitor: muestra un diálogo que muestra el progreso de una operación. • JColorChooser: ofrece un diálogo estándar para la selección de colores. • JFileChooser: ofrece un diálogo estándar para la selección de un fichero. 199 Programación en Java • © Grupo EIDOS JDialog: permite crear directamente diálogos completamente personalizados. Como se indica al principio de esta sección nosotros únicamente nos vamos a encargar de las clase JOptionPane y JDialog. Todo diálogo es dependiente de una ventana determinada, si la ventana se destruye también sus lo harán sus diálogos asociados. Cuando la ventana se transforma en icono sus diálogos desaparecen de la pantalla, cuando se maximiza la ventana los diálogos vuelven a aparecer. Los diálogos pueden ser modales, cuando un diálogo modal se encuentra visible se bloquea la entrada del usuario en todas las demás ventanas del programa. Los diálogos que ofrece la clase JOptionPane son modales, para crear un diálogo no modal se debe hacer uso de la clase JDialog que permitirá indicar si el diálogo que se crea va a ser modal o no. La clase JDialog hereda de a clase AWT java.awt.Dialog, añade a la clase Dialog un panel raíz (root pane) y soporte para la operación por defecto de cierre. Como se puede observar son las mismas características que ofrecía la clase JFrame sobre la clase Frame del AWT. Cuando se utiliza la clase JOptionPane en realidad también estamos haciendo uso de la clase JDialog de forma indirecta, ya que la clase JOptionPane es un contenedor que puede crear de forma automática una instancia de la clase JDialog y añadirse al panel de contenido de ese diálogo. A continuación vamos a comentar las distintas características de la clase JOptionPane. Mediante la clase JOptionPane podemos crear distintos tipos de diálogos, JOptionPane ofrece soporte para mostrar diálogos estándar, indicar los iconos, especificar el título y texto del diálogo y los textos de los botones que aparecen. En cuanto a los iconos que se muestran en el diálogo facilitado por JOptionPane, podemos utilizar iconos personalizados, no utilizar ningún tipo de icono, o utilizar uno de los cuatro iconos estándar que ofrece la clase JOptionPane, estos iconos son los de pregunta, información, advertencia y error. El aspecto de estos iconos variará según el Look & Feel (aspecto y comportamiento) que se aplique. Para mostrar diálogos modales sencillos se utilizará directamente uno de los métodos showXXXDialog() de la clase JOptionPane. Este conjunto de métodos son métodos estáticos y por lo tanto se lanzarán sobre una instancia concreta de la clase, sino que se lanzaran de forma directa sobre la clase. Si lo que necesitamos es controlar el comportamiento del diálogo cuando se cierre o si el diálogo no debe ser modal entonces instanciaremos un objeto de la clase JOptionPane y lo añadiremos a un objeto de la clase JDialog. Veamos a continuación los principales métodos showXXXDialog() de la clase JOptionPane. El primero de estos métodos es el método showMessageDialog(), que muestra un diálogo modal con un botón de aceptar. Se puede especificar el mensaje, el icono y el título que muestra el diálogo. Este método se encuentra sobrecargado y ofrece tres versiones distintas: 200 • void showMessageDialog(Component componentePadre, Object mensaje): el primer parámetro indica el componente padre del que depende, puede ser una ventana, un componente dentro de una ventana o nulo y el segundo el mensaje que se muestra, suele ser una cadena de texto que podemos dividir en varias líneas utilizando el carácter de nueva línea (\n). • void showMessageDialog(Component componentePadre, Object mensaje, String título, int tipoMensaje): en este caso se especifica también el título, que será una cadena, y el tipo de © Grupo EIDOS 11. Interfaces de usuario en Java: componentes Swing / contenedores mensaje a mostrar. Este último parámetro determinará el icono que se va a mostrar en el diálogo, para ello se utilizan una serie de constantes definidas en la clase JOptionPane, estas constantes son: PLAIN_MESSAGE (sin icono), ERROR_MESSAGE (icono de error), INFORMATION_MESSAGE (icono de información), WARNING_MESSAGE (icono de advertencia) y QUESTION_MESSAGE (icono de pregunta). • void showMessageDialog(Component componentePadre, Object mensaje, String título, int tipoMensaje, Icon icono): en esta última versión del método podemos indicar un icono personalizado para que se muestre en el diálogo. Veamos algunos ejemplos con el método showMessageDialog(). Podemos añadir las distintas sentencias de creación de diálogos que vamos a ver ahora al ejemplo de la sección anterior en la que tratábamos la clase JFrame, la instancia de la clase JFrame, llamada ventana va a ser el componente padre de los diálogos. Si añadimos la línea que muestra el Código fuente 124, a nuestra clase Ventana de la sección anterior, obtendremos el resultado que aparece en la Figura 68. JOptionPane.showMessageDialog(ventana,"Esto es un mensaje", "Título",JOptionPane.WARNING_MESSAGE); Código fuente 124 Figura 68 Otras variaciones sobre la sentencia anterior se muestran a continuación con sus correspondientes resultados. JOptionPane.showMessageDialog(ventana,"Esto es un mensaje"); Código fuente 125 En este caso se muestra el título y tipo de mensaje por defecto. Figura 69 201 Programación en Java © Grupo EIDOS ImageIcon icono = new ImageIcon("icono.gif"); JOptionPane.showMessageDialog(ventana,"Esto es un mensaje","Título", JOptionPane.INFORMATION_MESSAGE,icono); Código fuente 126 En este otro caso indicamos un icono personalizado para que se muestre en el diálogo. Figura 70 JOptionPane.showMessageDialog(ventana,"Esto es un mensaje","Título", JOptionPane.ERROR_MESSAGE); Código fuente 127 Figura 71 Como se puede comprobar en todos los ejemplos, si pulsamos el botón etiquetado como OK, el diálogo se cerrará. Otro método de la clase JOptionPane que muestra un diálogo es el método showConfirmaDialog(). En este caso este método muestra un diálogo de confirmación, para que el usuario seleccione entre los botones correspondientes. Este método, al igual que el anterior, se encuentra sobrecargado y por lo tanto ofrece tres versiones distintas, que pasamos a comentar a continuación. 202 • int showConfirmDialog(Component componentePadre, Object mensaje): este método muestra un diálogo modal con los botones, que representan las opciones disponibles, si, no y cancelar, y además con el título por defecto. • int showConfirmDialog(Component componentePadre, Object mensaje, String título, int tipoOpción): en este caso podemos especificar el título del diálogo de confirmación y el tipo de opciones que se van a mostrar, para esto último utilizaremos las siguientes constantes ofrecidas por la clase JOptionPane: DEFAULT_OPTION, YES_NO_OPTION, YES_NO_CANCEL_OPTION y OK_CANCEL_OPTION. © Grupo EIDOS 11. Interfaces de usuario en Java: componentes Swing / contenedores • int showConfirmDialog(Component componentePadre, Object mensaje, String título, int tipoOpción, int tipoMensaje): en esta versión del método podemos indicar además el tipo de mensaje que se muestra en el diálogo, de la misma forma que lo hacíamos en el método showMessageDialog(). • int showConfirmDialog(Component componentePadre, Object mensaje, String título, int tipoOpción, int tipoMensaje, Icon icono): en esta última versión especificamos un icono personalizado que se va a mostrar en el diálogo de conformación correspondiente. Como se puede observar en todas las versiones del método showConfirmDialog() se devuelve un entero (int), este entero va a recoger la selección que ha realizado el usuario, es decir, indicará el botón que ha sido pulsado. El entero que devuelve este método se corresponde con uno de los valores de las siguientes constantes de la clase JOptionPane: YES_OPTION, NO_OPTION, CANCEL_ OPTION, OK_OPTION y CLOSED_OPTION (el usuario cierra el diálogo sin pulsar ninguna de las opciones disponibles). Al igual que ocurría con el método anterior, vamos a ver ejemplos de utilización del método showConfirmDialog. JOptionPane.showConfirmDialog(ventana,"¿Desea formatear el disco?"); Código fuente 128 En este caso se muestra el diálogo de confirmación por defecto. Figura 72 JOptionPane.showConfirmDialog(ventana,"¿Desea formatear el disco?","Confirme operación", JOptionPane.YES_NO_OPTION,JOptionPane.WARNING_MESSAGE); Código fuente 129 Figura 73 203 Programación en Java © Grupo EIDOS El siguiente método que vamos a tratar de la clase JOptionPane va a ser el método showOptionDialog(). Este método es mucho más potente que los vistos anteriormente, ya que permite una mayor personalización del diálogo. La función de este método es la de mostrar un diálogo modal con los botones, iconos, mensaje y títulos especificados para que el usuario seleccione entre las distintas opciones que se le ofrecen, con este método podremos indicar los botones que queremos aparezcan en el diálogo. El método showOptionDialog() se diferencia del método showConfirmDialog() principalmente en que permite especificar las etiquetas de los botones que aparecen y además permite especificar la opción seleccionada por defecto. La sintaxis de este método es la siguiente: int showOptionDialog(Component componentePadre, Object mensaje, String título, int tipoOpción, int tipoMensaje, Icon icono, Object[] opciones, Object valorInicial) Vamos a comentar los distintos parámetros de este nuevo método. Los tres primeros parámetros son los mismos que los vistos en el método showMessageDialog() y además tienen el mismo significado. En el tipo de opción se especifica el conjunto de opciones que se van a presentar al usuario, y por lo tanto se corresponderán con las constantes vistas en el método showConfirmDialog(). Los dos siguientes parámetros, tipo de mensaje y el icono personalizado, tiene el mismo cometido que el método showMessageDialog(). El siguiente parámetro es un array de objetos que se va a corresponder con un array de cadenas que se van a mostrar en cada uno de los botones del diálogo, es decir, podemos utilizar nuestras propias etiquetas pera mostrar en los botones. El último parámetro indica cual es la opción que se encuentra seleccionada por defecto. Como se puede observar en la sintaxis del método showOptionDialog() se devuelve un entero (int), este entero tiene la misma función que el que devolvía el método showConfirDialog(), es decir, va a recoger la selección que ha realizado el usuario, es decir, indicará el botón que ha sido pulsado. Se debe indicar que aunque utilicemos etiquetas personalizadas para nuestros botones, se siguen devolviendo los mismos valores de las constantes, así por ejemplo un diálogo del tipo YES_NO_OPTION siempre devolverá los valores: YES_OPTION, NO_OPTION o CLOSED_OPTION. Ahora se va a mostrar ejemplos de uso del método showOptionDialog(). Estos ejemplos son únicamente unas cuentas sentencias que podemos incluir, como ocurría con los métodos anteriores, en nuestra ya conocida clase Ventana. Object[] opciones={"Vale", "Ni hablar"}; ImageIcon icono = new ImageIcon("icono.gif"); JOptionPane.showOptionDialog(ventana,"¿Desea formatear el disco?","Confirme operación", JOptionPane.YES_NO_OPTION,JOptionPane.WARNING_MESSAGE, icono, opciones, opciones[1]); Código fuente 130 204 © Grupo EIDOS 11. Interfaces de usuario en Java: componentes Swing / contenedores Se muestra un diálogo con las opciones si/no pero con unas etiquetas de botones personalizadas, así como con un icono personalizado, además es el segundo botón el que se encuentra seleccionado por defecto. Figura 74 El último método que vamos a ver de la clase JOptionPane es el método showInputDialog(), este método va a permitir obtener información de entrada del usuario a través del diálogo, ya sea a través de una caja de texto o a través de un una lista de opciones. El diálogo que se muestra va a tener dos botones, uno de aceptar y otro de cancelar, si el usuario pulsa el botón de aceptar indicará que ha introducido una información que podemos recuperar. Este método se encuentra sobrecargado, y ofrece las siguientes versiones: • Object showInputDialog(Component componentePadre, Object mensaje): muestra un diálogo con el mensaje correspondiente que requiere una entrada del usuario a través de una caja de texto. • Object showInputDialog(Component componentePadre, Object mensaje, String título, int tipoMensaje): en esta caso nos permite especificar un título y un tipo de mensaje (ya conocemos las constantes correspondientes). • Object showInputDialog(Component componentePadre, Object mensaje, String título, int tipoMensaje, Icon icono, Object opciones, Object opciónSeleccionada): en este método se permite indicar un icono personalizado y un conjunto de opciones en forma de lista desplegable para que el usuario seleccione una de ellas además se permite seleccionar una opción por defecto. • String showInputDialog(Object mensaje): en este caso no se indica nada más que el mensaje que se va a mostrar al usuario, sin utilizar ningún componente padre, por lo que el diálogo se situará en el centro de la pantalla, ya que en los otros casos se centra siempre con respecto al componente padre, aunque de todas forma sigue bloqueando a la ventana que lo ha generado. Como se puede comprobar siempre se devuelve un objeto de la clase String, que se va a corresponder con una cadena que va a representar la información indicada por el usuario, ya sea a través de una caja de texto una de una lista de opciones disponibles. En los siguientes ejemplos se muestra la utilización del método showInputDialog(). En este caso se va a recoger el dato facilitado por el usuario y mostrarlo en la salida estándar de la aplicación. String respuesta=JOptionPane.showInputDialog(ventana,"¿Cuál es tu nombre?"); System.out.println("El nombre del usuario es: "+respuesta); Código fuente 131 205 Programación en Java © Grupo EIDOS Figura 75 Object[] valores={"Rojo","Verde","Azul","Negro","Amarillo"}; ImageIcon icono = new ImageIcon("icono.gif"); Object respuesta=JOptionPane.showInputDialog(ventana,"¿Cuál es tu color favorito?", "Selección de color",JOptionPane.QUESTION_MESSAGE, icono,valores,valores[2]); System.out.println("El color favorito del usuario es el: "+respuesta); Código fuente 132 En este caso se muestra una lista con las opciones disponibles, para que el usuario seleccione la que desee. También se ha utilizado un icono personalizado. Figura 76 Hasta ahora hemos visto los métodos que nos ofrece la clase JOptionPane para mostrar distintos tipos de diálogo. Estos diálogos tienen en común una serie de características: al pulsar alguno de los botones que contienen se cierran, también se cierran cuando el usuario pulsa el cierre del diálogo y por último son todos modales. En algunos casos estos diálogos nos servirán, ya que su comportamiento se puede adecuar a nuestras necesidades, pero en otros casos esto puede no ser así. En algunos casos nos puede interesar validar la información ofrecida por el usuario en un diálogo, o también nos puede interesar utilizar un diálogo que no sea modal. En estos casos en los que deseamos personalizar al máximo los diálogos utilizaremos la clase JDialog conjuntamente con la clase JOptionPane. Para ello necesitamos crear una instancia de la clase JOptionPane para añadirla al objeto JDialog correspondiente. La clase JOptionPane presenta múltiples constructores que permiten especificar el mensaje que se va a mostrar, el tipo de mensaje, el tipo de opciones, el icono personalizado, las opciones disponibles y la opción seleccionada por defecto. Una vez creado el objeto JOptionPane, deberemos crear el objeto JDialog que lo va a contener, algunos de los constructores ofrecidos por la clase JDialog son: 206 © Grupo EIDOS 11. Interfaces de usuario en Java: componentes Swing / contenedores • JDialog(): crea un diálogo no modal, sin título definido y sin ninguna ventana propietaria. • JDialog(Frame ventanaPropietaria): se indica la ventana a la que se encuentra asociado el diálogo. • JDialog(Frame ventanaPropietaria, boolean modal): se indica si el diálogo va a ser modal o no. • JDialog(Frame ventanaPropietaria, boolean modal, String título): se especifica además el título que va a tener el diálogo. Si ya tenemos instanciados el objeto JOptionPane y el objeto JDialog, únicamente nos queda asignar al diálogo (JDialog) su contenido (JOptionPane), para ello utilizamos el método setContentPane() de la clase JDialog, y que es común, como ya hemos comentado, a todos los contenedores de alto nivel. A continuación se muestra este proceso con un ejemplo que consiste en una sencilla aplicación Java que muestra una ventana y un diálogo asociada a la misma. import java.awt.*; import java.awt.event.*; import javax.swing.*; public class Dialogo{ public static void main(String s[]) { JFrame ventana = new JFrame("Ventana Sencilla"); ventana.setSize(175,100); ventana.setVisible(true); JOptionPane contenido=new JOptionPane("Esto es un mensaje", JOptionPane.INFORMATION_MESSAGE,JOptionPane.YES_NO_CANCEL_OPTION); JDialog dialogo=new JDialog(ventana,"Esto es un diálogo",true); dialogo.setContentPane(contenido); dialogo.setLocationRelativeTo(ventana); dialogo.pack(); dialogo.setVisible(true); } } Código fuente 133 El resultado es un diálogo modal como el de la Figura 77. Figura 77 Como se puede comprobar la pulsación de los botones del diálogo no tiene el efecto que tenían anteriormente, si queremos que realicen alguna operación cuando se pulsen, deberemos hacerlo a través de una gestión de eventos a través del código de nuestra aplicación. Aunque si pulsamos el 207 Programación en Java © Grupo EIDOS cierre del diálogo éste se sigue cerrando, ya que la operación tiene asignada por defecto para el cierre es HIDE_ON_CLOSE, es decir, ocultarse en el cierre, como ocurría con la clase JFrame. En el código se puede observar que se utiliza el método setLocationRealtiveTo(), este método centra el diálogo de forma relativa al componente que le pasamos por parámetro. Otros métodos de la clase JDialog son los siguientes: • Container getContentPane(): devuelve el panel de contenido del diálogo. • int getDefaultCloseOperation(): devuelve la operación de cierre por defecto que tiene asignada el diálogo. • void setDefaultCloseOperation(int operacion): asigna una operación de cierre por defecto al diálogo. JApplet Este es el tercero de los contenedores de alto nivel. Esta clase hereda de la clase java.applet.Applet, es por lo tanto la versión Swing de la clase Applet. La clase JApplet aporta dos características esenciales a los applets, ofrece soporte para tecnologías de rehabilitación y ofrece un panel raíz, con todo lo que ello supone, es decir, añadir componentes al panel de contenido, posibilidad de tener una barra de menú, etc. Todavía no hemos visto con detenimiento la utilización de applets, únicamente los hemos definido de manera sencilla, por lo tanto en el presente capítulo no vamos a comentar nada más de la clase JApplet, será en los capítulos dedicados a los applets dónde se trate la misma. Contenedores intermedios Los contenedores intermedios son contenedores Swing, que aunque no son contenedores de alto nivel, su labor principal es la de contener otros componentes. Estos contenedores se siguen basando en la jerarquía de contenedores de Swing que ya hemos visto anteriormente. Swing ofrece una serie de contenedores de propósito general: 208 • JPanel: es le más flexible y utilizado de todos ellos. Se utiliza normalmente para agrupar componentes, se le puede asignar gestores de diseño y bordes. Los paneles de contenido de los contenedores de alto nivel suelen ser de la clase JPanel. • JScrollPane: ofrece una vista de scroll de un componente, suele contener componentes grandes o que pueden crecer. Se utilizan debido a las limitaciones del tamaño de la pantalla. • JSplitPane: este panel agrupa dos componentes, uno al lado del otro. El usuario puede ajustar la línea divisora que separa ambos componentes arrastrándola. El aspecto es similar al que ofrece, por ejemplo, el explorador de Windows, la vista del árbol de directorios se encuentra separada de la vista de contenidos de un directorio. © Grupo EIDOS 11. Interfaces de usuario en Java: componentes Swing / contenedores • JTabbedPane: contiene muchos componentes pero sólo puede mostrar un conjunto de ellos a la vez, el usuario puede ir cambiando entre los distintos conjuntos de manera sencilla. Un ejemplo podrían ser las distintas pestañas de una hoja de propiedades. • JToolBar: grupa una serie de componentes (normalmente botones) en una fila o en una columna, pueden permitir al usuario arrastrar la barra a distintos lugares. Un ejemplo pueden ser las barras de herramientas de un procesador de textos como MS Word. Además también existen los siguientes contenedores intermedios que se encuentran más especializados: • JInternalFrame: permite mostrar una ventana dentro de otra. Parece una ventana y ofrece toda su funcionalidad pero debe parecer siempre dentro de otra ventana (contenedor de alto nivel) que la contiene. • JLayeredPane: este panel ofrece una tercera dimensión, la profundidad, para poder posicionar componentes de esta forma. Esta tercera dimensión se denomina también Z-order. Al añadir un componente a un panel de este tipo se especifica su profundidad mediante un entero, cuanto mayor sea el entero especificado mayor será la profundidad en la que se sitúa el componente. • JRootPane: esta clase representa el panel raíz de un contenedor de alto nivel, como ya vimos en el apartado anterior el panel raíz esta formado por las siguientes partes: panel de capas, panel de contenido, panel de cristal y barra de menú. A continuación vamos a comentar algunos de estos paneles intermedios, no vamos a tratar todos ya nos excederíamos en la extensión del presente curso, no se debe olvidar que Swing tienen un gran número de componentes. JPanel Esta clase permite construir paneles de propósito general para contener componentes Swing. Por defecto un panel no muestra nada en pantalla a excepción de su fondo, también por defecto los paneles son opacos, aunque se pueden hacer transparentes mediante el método setOpaque() pasándole el valor false por parámetro. La clase JPanel es la versión Swing de la clase Panel de AWT. La clase JPanel permite asignar un gestor de diseño al panel para indicar la forma en la que se van añadiendo los distintos componentes al mismo, por defecto el gestor de diseño de un objeto JPanel es un FlowLayout. Swing utiliza los mismos gestores de diseño que vimos para los componentes AWT, de hecho se debe importar el paquete JAVA.AWT para poder utilizarlos, pero se añade un nuevo gestor mediante la clase BoxLayout, que veremos en el apartado correspondiente. Para añadir componentes lo haremos de la misma forma que veíamos en los paneles AWT, es decir, con las distintas versiones de los métodos add(). En el Código fuente 134 se muestra la forma de utilizar esta clase. import import import public java.awt.*; java.awt.event.*; javax.swing.*; class Panel{ 209 Programación en Java © Grupo EIDOS public static void main(String s[]) { JFrame ventana = new JFrame("Ventana Sencilla"); JPanel panel=new JPanel(); panel.setLayout(new BorderLayout()); JLabel etiqueta1 = new JLabel("Soy una etiqueta",JLabel.CENTER); etiqueta1.setPreferredSize(new Dimension(175, 100)); JLabel etiqueta2 = new JLabel("Soy otra etiqueta",JLabel.CENTER); etiqueta2.setPreferredSize(new Dimension(175, 100)); panel.add(etiqueta1, BorderLayout.NORTH); panel.add(etiqueta2, BorderLayout.SOUTH); ventana.getContentPane().add(panel, BorderLayout.CENTER); ventana.pack(); ventana.setVisible(true); } } Código fuente 134 Se trata simplemente de añadir dos etiquetas a un panel (JPanel) y añadir este panel al panel de contenido de una ventana (JFrame), el resultado se puede apreciar en la Figura 78. Figura 78 La clase JPanel hereda de la clase JComponent, y debido a ello permite utilizar una nueva funcionalidad ofrecida por Swing, los bordes. Cada objeto de la clase JComponent puede tener uno o más bordes. Los bordes no son realmente componentes, sino que se utilizan para delimitar visualmente una serie de componentes de otros. Para asignar un borde a un componente, en este caso un objeto de la clase JPanel, se utiliza el método setBorder(). Para crear los distintos bordes que ofrece Swing se suele utilizar la clase BorderFactory. Esta clase ofrece un gran número de métodos que permiten crear distintos tipos de bordes: de líneas, elevados, hundidos, marcados, con títulos, una combinación de dos bordes, etc. En el Código fuente 135 se puede observar como se asigna distintos bordes a distintos paneles dentro de una ventana. import java.awt.*; import java.awt.event.*; 210 © Grupo EIDOS 11. Interfaces de usuario en Java: componentes Swing / contenedores import javax.swing.*; public class Panel{ public static void main(String s[]) { JFrame ventana = new JFrame("Ventana Sencilla"); ventana.getContentPane().setLayout(new GridLayout(7,1)); ((JPanel)ventana.getContentPane()).setBorder( BorderFactory.createEmptyBorder(10,10,10,10)); JPanel panel1=new JPanel(); JLabel etiqueta1 = new JLabel("Borde tipo línea",JLabel.CENTER); panel1.setBorder(BorderFactory.createLineBorder(Color.black)); panel1.add(etiqueta1); ventana.getContentPane().add(panel1); JPanel panel2=new JPanel(); JLabel etiqueta2 = new JLabel("Borde elevado",JLabel.CENTER); panel2.setBorder(BorderFactory.createRaisedBevelBorder()); panel2.add(etiqueta2); ventana.getContentPane().add(panel2); JPanel panel3=new JPanel(); JLabel etiqueta3 = new JLabel("Borde hundido",JLabel.CENTER); panel3.setBorder(BorderFactory.createLoweredBevelBorder()); panel3.add(etiqueta3); ventana.getContentPane().add(panel3); JPanel panel4=new JPanel(); JLabel etiqueta4 = new JLabel("Borde decorado",JLabel.CENTER); panel4.setBorder(BorderFactory.createMatteBorder(5,5,5,5,Color.red)); panel4.add(etiqueta4); ventana.getContentPane().add(panel4); JPanel panel5=new JPanel(); JLabel etiqueta5 = new JLabel("Borde con título",JLabel.CENTER); panel5.setBorder(BorderFactory.createTitledBorder( BorderFactory.createLineBorder(Color.blue),"Título")); panel5.add(etiqueta5); ventana.getContentPane().add(panel5); JPanel panel6=new JPanel(); JLabel etiqueta6 = new JLabel("Borde grabado",JLabel.CENTER); panel6.setBorder(BorderFactory.createEtchedBorder()); panel6.add(etiqueta6); ventana.getContentPane().add(panel6); JPanel panel7=new JPanel(); JLabel etiqueta7 = new JLabel("Borde compuesto",JLabel.CENTER); panel7.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createLineBorder(Color.yellow), BorderFactory.createRaisedBevelBorder())); panel7.add(etiqueta7); panel7.setPreferredSize(new Dimension(175,50)); ventana.getContentPane().add(panel7); ventana.pack(); ventana.setVisible(true); } } Código fuente 135 Y el resultado se muestra en la Figura 79. El Código fuente 135 merece una serie de comentarios. El primero de ellos es referente al tipo de borde utilizado en el panel de contenido, como se puede observar al recuperar el panel de contenido para asignarle un borde vacío se ha tenido que hacer un "cast" con la clase JPanel, este borde vacío se ha utilizado para crear un margen entre el panel de contenido y el contenedor, en este caso una instancia de la clase JFrame. 211 Programación en Java © Grupo EIDOS Figura 79 Al panel de contenido de la ventana se le ha asignado un gestor de diseño GridLayout con siete filas y una única columna. Algunos de los métodos de la clase BorderFactory se encuentran sobrecargados, para permitir una mayor personalización de los distintos tipos de bordes que queremos asignar. A cada panel se le ha asignado un borde distinto y se le ha añadido una etiqueta (JLabel) con la descripción del tipo de borde correspondiente. JTabbedPane Gracias a este contenedor intermedio podemos tener distintos componentes, normalmente otros paneles, compartiendo un mismo espacio. El usuario puede visualizar los componentes que desea ver seleccionando la pestaña correspondiente. Para crear un contenedor de este tipo primero debemos instanciar el objeto correspondiente de la clase JTabbedPane, luego se van creando los distintos componentes y se van añadiendo al contenedor mediante el método addTab(). Antes de seguir con esta nueva clase vamos a verla en acción mediante un sencillo ejemplo. El ejemplo consiste simplemente en un objeto JTabbedPane al que se le van añadiendo distintos paneles, cada uno con sus componentes. El código se puede ver en el Código fuente 136. import java.awt.*; import java.awt.event.*; import javax.swing.*; 212 © Grupo EIDOS 11. Interfaces de usuario en Java: componentes Swing / contenedores public class PanelTab{ public static void main(String s[]) { JFrame ventana = new JFrame("Ventana Sencilla"); ImageIcon icono = new ImageIcon("icono.gif"); JTabbedPane panelTab = new JTabbedPane(); //tratamiento de eventos para el cierre de la ventana ventana.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); //se van creando paneles con componentes y se añaden al objeto JTabbedPane JPanel panel1=new JPanel(); panel1.add(new JButton("Botón",icono)); //se crea una nueva pestaña con el nuevo componente panelTab.addTab("Uno",icono,panel1,"Soy la primera pestaña"); panelTab.setSelectedIndex(0); JPanel panel2=new JPanel(); panel2.add(new JLabel("Soy una etiqueta",JLabel.CENTER)); panelTab.addTab("Dos",icono,panel2,"Soy la segunda pestaña"); JPanel panel3=new JPanel(); panel3.add(new JLabel("Soy otra etiqueta",JLabel.CENTER)); panel3.setBorder(BorderFactory.createLineBorder(Color.red)); panelTab.addTab("Tres",icono,panel3,"Soy la tercera pestaña"); JPanel panel4=new JPanel(); panel4.add(new JToggleButton("Otro botón",icono)); panelTab.addTab("Cuatro",icono,panel4,"Soy la cuarta pestaña"); JPanel panel5=new JPanel(); panel5.add(new JSlider(JSlider.HORIZONTAL,0,30,10)); panelTab.addTab("Cinco",icono,panel5,"Soy la última pestaña"); //se le da un tamaño al contenedor de pestañas panelTab.setPreferredSize(new Dimension(400,200)); //se añade a la ventana ventana.getContentPane().add(panelTab); ventana.pack(); ventana.setVisible(true); } } Código fuente 136 Y el resultado de este código se puede apreciar en la Figura 80. Figura 80 213 Programación en Java © Grupo EIDOS Si se prueba el ejemplo anterior se puede comprobar que no se ha escrito ningún código para realizar el tratamiento de eventos, la clase JTabbedPane realiza este tratamiento (mostrar los componentes de la pestaña seleccionada) de forma automática. En el ejemplo se ha utilizado un constructor de la clase JTabbedPane que no utiliza ningún parámetro, pero existe otro constructor que permite, mediante una constante que recibe como parámetro, especificar la localización de las pestañas. Estas constantes se encuentran definidas en la clase JTabbedPane y son las siguientes TOP, BOTTOM, LEFT y RIGHT. Así si modificamos el ejemplo anterior cambiando el constructor utilizado para el panel JTabbedPane mediante la línea de código que aparece en el Código fuente 137. JTabbedPane panelTab = new JTabbedPane(JTabbedPane.BOTTOM); Código fuente 137 Obtenemos el resultado que vemos en la Figura 81. Figura 81 Para añadir una nueva pestaña ya sabemos que debemos utilizar el método addTab(), este método presenta las siguientes versiones: • addTab(String texto, Component componente): el primer argumento indica el texto que va a parecer en la pestaña y el componente que va a contener. • addTab(String texto, Icon icono, Component componente): en este caso además se indica el icono que se va a mostrar en la pestaña. • addTab(String texto, Icon icono, Component componente, String ayuda): en la última versión del método addTab() se permite especificar el texto que aparecerá a modo de ayuda (tooltip) cuando situemos el puntero del ratón sobre la pestaña. En el ejemplo se ha utilizado el método setSelectedIndex() para indicar la pestaña que por defecto se encuentra seleccionada, que será la que mostrará sus componentes. Las pestañas comienzan a numerarse en cero. 214 © Grupo EIDOS 11. Interfaces de usuario en Java: componentes Swing / contenedores Para manipular las pestañas la clase JTabbedPane ofrece los siguientes métodos: • insertTab(String texto, Icon icono, Component componente, String ayuda, int índice): inserta una nueva pestaña en la posición indicada por el parámetro índice. • removeTabAt(int índice): elimina la pestaña cuya posición coincida con la indicada. • remove(Component componente): elimina la pestaña que contenga el componente especificado como argumento. • removeAll(): elimina todas las pestañas. • int indexOfTab(String texto): devuelve el índice de la pestaña que posea el texto indicado por parámetro. • int getSelectedIndex(): devuelve el índice de la pestaña seleccionada actualmente. También es posible modificar la apariencia de las pestañas del panel JTabbedPane. Podemos indicar el icono que va a mostrar la pestaña según se encuentre habilitada o deshabilitada, también el color de fondo y el del texto de la pestaña. Así si una vez creado el panel de nuestro ejemplo añadimos las líneas de código que se muestran en el Código fuente 138 y obtenemos el resultado que aparece en la Figura 82. //color de fondo cuando no está seleccionada la pestaña panelTab.setBackgroundAt(0,Color.red); //color del texto panelTab.setForegroundAt(0,Color.green); ImageIcon otroIcono = new ImageIcon("icono2.gif"); //icono que se muestra cuando la pestaña está desactivada panelTab.setDisabledIconAt(0,otroIcono); //se modifica el título panelTab.setTitleAt(2,"Otro texto"); JPanel panelNuevo=new JPanel(); panelNuevo.add(new JTextArea(5,30)); //se cambia el componente que posee la pestaña panelTab.setComponentAt(3,panelNuevo); Código fuente 138 Figura 82 215 Programación en Java © Grupo EIDOS Curioso resultado ya que no se muestra el icono que se utiliza para indicar que la pestaña está deshabilitada. Si utilizamos cualquiera de los métodos anteriores ,que tiene como parámetro el índice de la pestaña, y la pestaña no existe se producirá una excepción. JToolBar Esta clase representa una barra de herramientas, normalmente este tipo de contenedor va a contener botones con iconos dentro de una fila o columna. Estos botones cumplen las mismas funciones que las opciones de menú. Por defecto el usuario puede arrastrar la barra de herramientas y situarla en los diferentes bordes del contenedor o bien como una ventana independiente. Para que este funcionamiento de arrastre de la barra sea correcto, el contenedor en el que se sitúa la barra de herramientas debe tener un gestor de diseño BorderLayout. Normalmente la barra de herramientas se añade en el norte del gestor de diseño y el componente al que afecta en el centro, no debe existir ningún componente más en el centro del contenedor. En el Código fuente 139 se puede observar como se utiliza un objeto de la clase JToolBar. En nuestro caso vamos a tener una ventana (JFrame) que va a tener un panel de contenido al que se va a añadir la barra de herramientas (JToolBar) y un área de texto (JTextArea). Como se puede ver la barra de herramientas puede contener otros tipos de componentes, no sólo botones. import import import public 216 java.awt.*; java.awt.event.*; javax.swing.*; class BarraHerramientas{ public static void main(String s[]) { JFrame ventana = new JFrame("Ventana con barra de herramientas"); //tratamiento de eventos para el cierre de la ventana ventana.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); //iconos ImageIcon icono1=new ImageIcon("icono1.gif"); ImageIcon icono2=new ImageIcon("icono2.gif"); ImageIcon icono3=new ImageIcon("icono3.gif"); //botones JButton boton1=new JButton(icono1); JButton boton2=new JButton(icono2); JButton boton3=new JButton(icono3); //lista con elementos JComboBox combo=new JComboBox(); combo.addItem("uno"); combo.addItem("dos"); combo.addItem("tres"); //caja de texto JTextField caja=new JTextField("caja de texto"); //barra de herramientas JToolBar barra=new JToolBar(); //se añaden los botones barra.add(boton1); barra.add(boton2); barra.add(boton3); © Grupo EIDOS 11. Interfaces de usuario en Java: componentes Swing / contenedores //se añade el separador a la barra de herramientas barra.addSeparator(); barra.add(combo); barra.add(caja); //se añade la barra al panel de contenido ventana.getContentPane().add(barra,BorderLayout.NORTH); //se crea un área de texto JTextArea areaTexto = new JTextArea(5,30); //se añade a un panel de scroll JScrollPane scrollPane = new JScrollPane(areaTexto); //se añade el panel de scroll al panel de contenido ventana.getContentPane().add(scrollPane,BorderLayout.CENTER); //se asigna un tamaño preferido a la vetana ((JPanel)ventana.getContentPane()).setPreferredSize(new Dimension(400, 100)); ventana.pack(); ventana.setVisible(true); } } Código fuente 139 El resultado se muestra en la Figura 83. Figura 83 Como se puede comprobar en el código anterior, para añadir un separador a la barra de herramientas se utiliza el método addSeparator(). Si queremos que la barra de herramientas permanezca fija se puede lanzar sobre el objeto de la clase JToolBar el método setFloatable() pasando como argumento el valor false. Para posicionar los distintos elementos que contiene, la clase JToolBar utiliza el gestor de diseño BoxLayout, que veremos más adelante en el apartado dedicado al mismo. JLayeredPane Este contenedor ofrece una tercera dimensión que permite posicionar los componentes que contiene especificando una profundidad. La profundidad de un determinado componente se especifica mediante un entero, cuanto mayor sea este entero mayor será la profundidad del componente correspondiente. Si los componentes que contiene el panel de capas se superponen los componentes que se encuentran a una mayor profundidad se muestran encima de los de una menor profundidad. Vimos que los contenedores de alto nivel de Swing contienen un panel de raíz que a su vez contiene un panel de capas (layered pane), normalmente no se suele utilizar el JLayeredPane del JRootPane, sino que se crea un objeto JLayeredPane distinto para utilizarlo dentro de otro panel. Esto mismo ocurre en nuestro ejemplo, que se sitúa un objeto de la clase JLayeredPane dentro del panel de contenido de una ventana (JFrame). 217 Programación en Java © Grupo EIDOS Para añadir un componente a un JLayeredPane se utiliza el método add(), en este método se debe indicar la profundidad del componente, es decir, la capa en la que se encuentra. En el siguiente ejemplo se van añadiendo a una instancia de la clase JLayeredPane etiquetas con color de fondo a distintas profundidades. Veamos el Código fuente 140. import import import public java.awt.*; java.awt.event.*; javax.swing.*; class PanelCapas{ static private String[] textosCapa = { "Amarillo(0)", "Magenta (1)", "Azul (2)", "Rojo (3)", "Verde (4)" }; static private Color[] coloresCapa = { Color.yellow, Color.magenta, Color.blue, Color.red, Color.green }; public static void main(String s[]) { JFrame ventana = new JFrame("Ventana con panel de capas"); JLayeredPane panelCapas=new JLayeredPane(); //tratamiento de eventos para el cierre de la ventana ventana.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); //punto de origen Point origen = new Point(10, 20); //separación entre etiquetas int separacion = 35; //se van creando las etiquetas de color a distinta profundidad for (int i = 0; i < textosCapa.length; i++) { JLabel etiqueta = creaEtiqueta(textosCapa[i],coloresCapa[i],origen); //se añade la etiqueta al panel de capas panelCapas.add(etiqueta,new Integer(i)); origen.x += separacion; origen.y += separacion; } //se indica un tamaño para le panel panelCapas.setPreferredSize(new Dimension(300, 310)); //se añade el panel creado al panel de contenido de la ventana ventana.getContentPane().add(panelCapas); ventana.pack(); ventana.setVisible(true); } //método para la creación de etiquetas con un texto y un color en un punto //de origen. private static JLabel creaEtiqueta(String texto,Color color,Point origen) { JLabel etiqueta = new JLabel(texto); //se alinea el texto en la etiqueta etiqueta.setVerticalAlignment(JLabel.TOP); etiqueta.setHorizontalAlignment(JLabel.CENTER); etiqueta.setOpaque(true); etiqueta.setBackground(color); //se asigna un borde a la etiqueta etiqueta.setBorder(BorderFactory.createLineBorder(Color.black)); etiqueta.setBounds(origen.x, origen.y, 140, 140); return etiqueta; } } Código fuente 140 218 © Grupo EIDOS 11. Interfaces de usuario en Java: componentes Swing / contenedores El aspecto de esta ventana es el de la Figura 84. Figura 84 Como se puede comprobar a la vista del código se han creado dos arrays que contienen por un lado los textos de las etiquetas y por otro los colores de las etiquetas. Se ha utilizado un bucle for para ir recorriendo estos arrays y crear las etiquetas mediante el método crearEtiqueta(). Como se puede ver el método y los atributos utilizado son estáticos, ya que los utilizamos directamente en el método main() si lanzarlos sobre una instancia de una clase. Dentro de una capa, es decir, a una profundidad determinada, se puede especificar la posición de un componente. Es posible por lo tanto definir la posición de un componente respecto al resto de componentes dentro de la misma capa, para ello existe una versión del método add() que posee un tercer parámetro para indicar esta posición dentro de la capa. El valor de esta posición va desde -1 hasta n-1, dónde n es el número de componentes dentro de la capa. Al contrario que las capas, cuanto menor es el número mayor es la profundidad del componente dentro de la capa. Utilizar -1 es equivalente a utilizar n-1, indica la posición más en el fondo. Si utilizamos 0 el componente se encontrará por encima del resto. Si al ejemplo anterior le añadimos las líneas que muestra el Código fuente 141, una vez creadas las etiquetas en las distintas capas. ImageIcon icono = new ImageIcon("imagen.gif"); JLabel nuevaEtiqueta=new JLabel(icono); nuevaEtiqueta.setBounds(180,75,icono.getIconWidth(),icono.getIconHeight()); panelCapas.add(nuevaEtiqueta,new Integer(2),0); Código fuente 141 219 Programación en Java © Grupo EIDOS Lo que se hace es añadir una nueva etiqueta con un icono en la capa 2 de modo que quede por encima de la etiqueta de color que ya existía en esa misma capa. El nuevo aspecto del panel de capas es el que aparece en la Figura 85. Figura 85 También es posible mover un componente de una capa a otra, para ello se utiliza el método setLayer(). Así si queremos mover la etiqueta con el icono a la capa 4 escribiremos lo que indica el Código fuente 142. panelCapas.setLayer(nuevaEtiqueta,4,0); Código fuente 142 El último argumento del método setLayer() es la posición del componente dentro de la nueva capa. Para mover un componente dentro de una capa a la primera posición se utiliza el método moveToFront(), y para enviarlo al fondo el método moveToBack(), ambos métodos tiene como parámetro el componente al que se quiere cambiar de posición dentro de una capa. Si utilizamos el método moveToBack() en nuestro código anterior como indica el Código fuente 143, el nuevo aspecto del ejemplo es el de la Figura 86. panelCapas.moveToBack(nuevaEtiqueta); Código fuente 143 220 © Grupo EIDOS 11. Interfaces de usuario en Java: componentes Swing / contenedores Con este último tipo de contenedor intermedio damos por terminado este apartado y este capítulo, en el siguiente capítulo seguimos tratando componentes Swing, en este caso los componentes atómicos, y también veremos otras aportaciones de Swing. Figura 86 221 Interfaces de usuario en Java: componentes atómicos de Swing Introducción Dentro de este capítulo vamos a tratar un gran grupo de componentes Swing, los componentes atómicos, no vamos a tratar todos ellos, ya que son muy numerosos y necesitaríamos como mínimo un par de capítulos más. Veremos que estos componentes atómicos se agrupan a su vez en tres categorías, veremos ejemplos representativos de cada una de estas categorías. Componentes atómicos Este grupo de componentes se corresponde con aquellos componentes cuya función es presentar o recibir información, en lugar de contener otros componentes, aunque es posible encontrar componentes atómicos que son la combinación de distintos componentes. Un ejemplo de componente atómico podría ser un botón, una caja de texto, una casilla de verificación, etc. Todos los componentes atómicos heredan de la clase JComponent, debido a esto todos ellos soportan características estándar de los componentes Swing, como pueden ser bordes y tooltips. Los componentes atómicos se subclasifican atendiendo a la labor que realizan. Componentes que cuya misión principal es la de obtener información relativa a la entrada del usuario. Programación en Java © Grupo EIDOS • JButton: un botón común. • JCheckBox: una casilla de verificación. • JRadioButton: un botón de opción que suelen utilizarse en grupos. • JMenuItem: un elemento de un menú. • JCheckBoxMenuItem: un elemento de menú que contiene una casilla de verificación. • JRadioButtonMenuItem: un elemento de menú que contiene un botón de opción. • JMenuBar: una barra de menú. • JMenu: una opción de menú. • JToggleButton: representa un botón con dos estados (pulsado/no pulsado). • JComboBox: una lista desplegable. • JList: una lista con elementos. • JSlider: permite seleccionar al usuario un valor dentro de un rango determinado. • JTextField: una caja de texto en la que el usuario puede escribir. Componentes que existen únicamente para mostrar información: • JLabel: etiqueta que puede mostrar texto, un icono o ambos. • JProgressBar: barra que muestra el progreso de un proceso. • JToolTip: muestra una breve descripción de un componente, aunque veremos en los distintos ejemplos que nunca vamos a utilizar directamente esta clase. Componentes que muestran una información estructurada o que permiten editarla: • JColorChooser: permite realizar la selección de un color determinado. • JFileChooser: permite seleccionar ficheros y directorios. • JTable: muestra la información en formato de tabla. • JTextComponent: de esta clase heredan distintas clases especializadas en el tratamiento de textos. • JTree: muestra datos organizados de forma jerárquica. A lo largo de los siguientes apartados se van a mostrar algunos de estos componentes con sus respectivos ejemplos. 224 © Grupo EIDOS 12. Interfaces de usuario en Java: componentes atómicos de Swing Componentes para obtener información Vamos a comenzar con algunos de los componentes atómicos de Swing cuya misión es la de obtener información a través de la entrada del usuario. JButton Esta clase hereda de la clase AbstractButton, al igual que lo hacen otras clases como pueden ser JCheckbox, JMenuItem o JToggleButton. Por lo tanto la clase JButton posee una serie de funcionalidades que son comunes a todas las clases que heredan de la clase AbstractButton. Un botón puede contener texto o imágenes o ambos elementos. El texto que contiene un botón se puede alinear con respecto a la imagen, también se pueden especificar teclas de teclado alternativas, que se indicarán mediante el subrayado de la letra del texto correspondiente. Cuando un botón se encuentra deshabilitado el Look and Feel correspondiente genera de forma automática el aspecto del botón. Sin embargo, se puede indicar una imagen para que se muestre cuando el botón se encuentre deshabilitado. Para ver la clase JButton en acción vamos a utilizar un ejemplo muy sencillo que consiste en mostrar tres botones con imágenes y ayudas (tooltips), la pulsación de dos de los tres botones activará o desactivará el botón central. En el Código fuente 144 se muestra el código que crea estos botones y los añade a una ventana (JFrame), y además registra los oyentes de eventos correspondientes. Se han utilizado tres métodos a demás del método main(). El método creaBotones() instancia todas las instancias de los botones y los configura convenientemente, el método añadeBotones() añade los botones al panel de contenido de la ventana y el método actionPerformed() se encarga de los eventos de pulsación de los botones. import import import public java.awt.*; java.awt.event.*; javax.swing.*; class Botones extends JFrame implements ActionListener{ private JButton botonIzq; private JButton botonDer; private JButton botonCentro; private ImageIcon iconoIzq=new ImageIcon("icono3.gif"); private ImageIcon iconoDer=new ImageIcon("icono1.gif"); private ImageIcon iconoCentro=new ImageIcon("icono2.gif"); public Botones (){ super("Ventana con botones"); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public void creaBotones(){ //se instancia el botón indicando la imagen botonIzq=new JButton("Desactiva botón central",iconoIzq); //se da formato al texto botonIzq.setVerticalTextPosition(AbstractButton.CENTER); botonIzq.setHorizontalTextPosition(AbstractButton.LEFT); //se indica la tecla asociada al botón botonIzq.setMnemonic(KeyEvent.VK_D); 225 Programación en Java © Grupo EIDOS //se indica el comando de acción que se utiliza cuando se pulsa el botón botonIzq.setActionCommand("desactiva"); //se asigna un tooltip botonIzq.setToolTipText("Desactivo el botón central"); botonIzq.setEnabled(false); botonDer=new JButton("Activa botón central",iconoDer); botonDer.setVerticalTextPosition(AbstractButton.CENTER); botonDer.setHorizontalTextPosition(AbstractButton.RIGHT); botonDer.setMnemonic(KeyEvent.VK_A); botonDer.setActionCommand("activa"); botonDer.setToolTipText("Activo el botón central"); botonCentro=new JButton("Botón central",iconoCentro); botonCentro.setEnabled(false); botonCentro.setToolTipText("No hago nada"); //se registran los oyentes, que son la misma clase. botonIzq.addActionListener(this); botonDer.addActionListener(this); } public void añadeBotones(){ //creamos un panel para añadir los botones JPanel panelContenido=new JPanel(); panelContenido.add(botonIzq); panelContenido.add(botonCentro); panelContenido.add(botonDer); //establecemos este panel como panel de contenido de la ventana setContentPane(panelContenido); } public void actionPerformed(ActionEvent evento){ if (evento.getActionCommand().equals("desactiva")){ //se desactiva el botón central y se actualiza //el estado de los otros botones botonCentro.setEnabled(false); botonIzq.setEnabled(false); botonDer.setEnabled(true); }else{ //se activa el botón central botonCentro.setEnabled(true); botonIzq.setEnabled(true); botonDer.setEnabled(false); } } public static void main(String s[]) { Botones ventana = new Botones(); ventana.creaBotones(); ventana.añadeBotones(); ventana.pack(); ventana.setVisible(true); } } Código fuente 144 El aspecto que muestra la ventana se puede apreciar en la Figura 87. Figura 87 226 © Grupo EIDOS 12. Interfaces de usuario en Java: componentes atómicos de Swing Se puede comprobar si pulsamos las teclas Alt+D o Alt+A tienen el mismo efecto que pulsar el botón correspondiente, además esta combinación de teclas aparece descrita en el tooltip. Podemos indicar el botón activo por defecto de un contenedor de alto nivel mediante el método setDefaultButton() de la clase JRootPane. El botón activo por defecto aparece destacado del resto y si el usuario pulsa la tecla Enter es equivalente a pulsar este botón. Podemos modificar el ejemplo anterior para que el botón activo por defecto sea el botón de la derecha. getRootPane().setDefaultButton(botonDer); Código fuente 145 Otra característica que nos ofrece la clase JButton es la posibilidad de utilizar etiquetas HTML dentro del texto del botón, para ello se debe poner la etiqueta <html> al inicio del texto, y a continuación se pueden utilizar las etiquetas HTML que se consideren necesarias para dar el formato conveniente al texto. Así por ejemplo, si retomamos nuestro código anterior y modificamos las líneas en las que se crean los botones, escribiendo el Código fuente 146. botonIzq=new JButton("<html><b><i><u>D</u>esactiva botón central</i><b>",iconoIzq); .......... botonDer=new JButton("<html><font color='red' size='4'>"+ "<u>A</u>ctiva botón central</font>",iconoDer); ........... botonCentro=new JButton("<html><small><i>Botón central</i></small>",iconoCentro); Código fuente 146 Obtenemos la Figura 88. Figura 88 Como se puede apreciar aunque los botones se encuentran desactivas, ahora el texto no aparece en color gris atenuado. Además hemos tenido que utilizar la etiqueta de subrayado de HTML (<u></u>) para indicar la tecla que se corresponde con cada botón. JCheckbox Esta clase representa a las casillas de verificación, también se pueden utilizar estas casillas de verificación dentro de elementos de menú, mediante la clase JCheckBoxMenuItem. Debido a que la clase JCheckBox hereda de la clase AbstractButton, presenta una serie de características comunes a 227 Programación en Java © Grupo EIDOS todos los tipos de botones, que ya hemos visto en la sección anterior con la clase JButton. Estas características son la posibilidad de utilizar imágenes, tooltips, teclas alternativas, etc. Las casillas de verificación se suelen agrupar y es posible seleccionar, una, algunas o ninguna de ellas. En el siguiente ejemplo se muestra la utilización de la clase JCheckBox para configurar el aspecto de un botón. Se dispone de tres casillas de verificación, una para indicar que el botón va a tener un icono, otra para indicar que tiene un tooltip y otra para indicar que tiene texto. El código se muestra en el Código fuente 147. import import import public java.awt.*; java.awt.event.*; javax.swing.*; class Casillas extends JFrame implements ItemListener{ //cada una de las casillas private JCheckBox chkIcono; private JCheckBox chkTexto; private JCheckBox chkToolTip; //el botón del que indicamos el aspecto private JButton boton; //panel de las casillas private JPanel panelCasillas; //panel del botón private JPanel panelBoton; private ImageIcon icono=new ImageIcon("icono2.gif"); public Casillas (){ super("Ventana con casillas de verificación"); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); getContentPane().setLayout(new GridLayout(1,2)); } public void creaCasillas(){ panelCasillas=new JPanel(); panelCasillas.setLayout(new GridLayout(3,1)); chkIcono=new JCheckBox("Icono"); chkIcono.setMnemonic(KeyEvent.VK_I); chkIcono.setSelected(true); chkIcono.addItemListener(this); panelCasillas.add(chkIcono); chkTexto=new JCheckBox("Texto"); chkTexto.setMnemonic(KeyEvent.VK_T); chkTexto.setSelected(true); chkTexto.addItemListener(this); panelCasillas.add(chkTexto); chkToolTip=new JCheckBox("Tooltip"); chkToolTip.setMnemonic(KeyEvent.VK_L); chkToolTip.setSelected(true); chkToolTip.addItemListener(this); panelCasillas.add(chkToolTip); getContentPane().add(panelCasillas); } public void creaBoton(){ panelBoton=new JPanel(); panelBoton.setLayout(new FlowLayout()); boton=new JButton("Soy un botón",icono); boton.setToolTipText("Soy un botón"); panelBoton.add(boton); getContentPane().add(panelBoton); } 228 © Grupo EIDOS 12. Interfaces de usuario en Java: componentes atómicos de Swing public void itemStateChanged(ItemEvent evento) { Object fuente=evento.getSource(); int estado=evento.getStateChange(); if (fuente==chkIcono){ if(estado==ItemEvent.DESELECTED) boton.setIcon(null); else boton.setIcon(icono); } if (fuente==chkTexto){ if(estado==ItemEvent.DESELECTED) boton.setText(""); else boton.setText("Soy un botón"); } if (fuente==chkToolTip){ if(estado==ItemEvent.DESELECTED) boton.setToolTipText(""); else boton.setToolTipText("Soy un botón"); } } public static void main(String s[]) { Casillas ventana = new Casillas(); ventana.creaCasillas(); ventana.creaBoton(); ventana.pack(); ventana.setVisible(true); } } Código fuente 147 Y el resultado es el de la Figura 89. Figura 89 Como se puede observar la clase JButton lanza un evento del tipo ItemEvent cuando se modifica el estado de un objeto. En el método itemStateChanged() debemos averiguar primero la fuente del evento, es decir, la casilla que ha visto modificado su estado, y a continuación si la casilla en cuestión ha sido seleccionada o no. JRadioButton Los botones de opción se suelen encontrar en grupos en los que, por convención, únicamente uno de los botones puede encontrarse seleccionado a un mismo tiempo. También podemos utilizar botones de opciones en elementos de menú mediante la clase JRadioButtonMenuItem. 229 Programación en Java © Grupo EIDOS La clase JRadioButton también tiene como superclase o clase padre a la clase AbstractButton, por lo tanto presentará el comportamiento común a todas las clases que heredan de AbstractButton, por ejemplo podemos indicar que un objeto JRadioButton muestre una imagen o un tooltip. Para esta clase vamos a mostrar un ejemplo que ofrecerá un dibujo de un animal distinto según el botón de opción que se encuentre seleccionado en cada momento. Para agrupar los botones se utiliza la clase ButtonGroup. Nuestro ejemplo va a contener tres botones de opción agrupados para mostrar tres dibujos distintos. En el Código fuente 148 se va a mostrar el fuente correspondiente. import import import public java.awt.*; java.awt.event.*; javax.swing.*; class Opciones extends JFrame implements ActionListener{ //cada una de las Opciones private JRadioButton opGato; private JRadioButton opCerdo; private JRadioButton opConejo; //imagen private JLabel imagen; //panel de las Opciones private JPanel panelOpciones; //panel de la imagen private JPanel panelImagen; public Opciones (){ super("Ventana con opciones"); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); getContentPane().setLayout(new GridLayout(1,2)); } public void creaOpciones(){ panelOpciones=new JPanel(); panelOpciones.setLayout(new GridLayout(3,1)); opGato=new JRadioButton("Gato"); opGato.setMnemonic(KeyEvent.VK_G); opGato.addActionListener(this); opGato.setActionCommand("gato.gif"); panelOpciones.add(opGato); opCerdo=new JRadioButton("Cerdo"); opCerdo.setMnemonic(KeyEvent.VK_C); opCerdo.setSelected(true); opCerdo.addActionListener(this); opCerdo.setActionCommand("cerdo.gif"); panelOpciones.add(opCerdo); opConejo=new JRadioButton("Conejo"); opConejo.setMnemonic(KeyEvent.VK_J); opConejo.addActionListener(this); opConejo.setActionCommand("conejo.gif"); panelOpciones.add(opConejo); //se agrupan las opciones ButtonGroup grupo=new ButtonGroup(); grupo.add(opGato); grupo.add(opCerdo); grupo.add(opConejo); getContentPane().add(panelOpciones); } public void creaImagen(){ panelImagen=new JPanel(); 230 © Grupo EIDOS 12. Interfaces de usuario en Java: componentes atómicos de Swing panelImagen.setLayout(new BorderLayout()); imagen = new JLabel(new ImageIcon("cerdo.gif")); imagen.setPreferredSize(new Dimension(177, 122)); panelImagen.add(imagen,BorderLayout.CENTER); getContentPane().add(panelImagen); } public void actionPerformed(ActionEvent evento){ String comando=evento.getActionCommand(); imagen.setIcon(new ImageIcon(comando)); } public static void main(String s[]) { Opciones ventana = new Opciones(); ventana.creaOpciones(); ventana.creaImagen(); ventana.pack(); ventana.setVisible(true); } } Código fuente 148 El resultado se puede comprobar en la Figura 90. Figura 90 La clase JRadioButton, en lo que a tratamiento de eventos se refiere, funciona igual que la clase JButton, el botón que se pulse (seleccione) va a lanzar un evento de la clase ActionEvent, para diferenciar entre los distintos botones se utiliza el ActionCommand del evento ActionEvent. Para mostrar la imagen se utiliza una etiqueta (JLabel) con un icono (ImageIcon). JComboBox Esta clase representa una lista desplegable de opciones, que puede ser editable o no. Cuando el usuario pulsa la lista, el objeto JComboBox muestra un menú de elementos para elegir. Una lista desplegable editable es similar a una caja de texto (JTextField) con un pequeño botón. El usuario puede escribir un valor en la caja de texto o elegir un valor del menú. En nuestro caso vamos a ver un ejemplo de una lista desplegable no editable. El ejemplo va a tener la misma funcionalidad que el utilizado para la clase JRadioButton, es decir, vamos a tener distintas opciones que van a mostrar un dibujo determinado. El código fuente completo del ejemplo es el Código fuente 149, y el resultado se muestra en la Figura 91. 231 Programación en Java import import import public © Grupo EIDOS java.awt.*; java.awt.event.*; javax.swing.*; class Combo extends JFrame implements ActionListener{ //lista desplegable private JComboBox listaOpciones; //opciones de la lista String[] opciones={"Gato","Cerdo","Conejo"}; //imagen private JLabel imagen; public Combo(){ super("Ventana con lista desplegable"); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public void creaCombo(){ listaOpciones=new JComboBox(opciones); listaOpciones.setSelectedIndex(0); getContentPane().add(listaOpciones,BorderLayout.NORTH); listaOpciones.addActionListener(this); } public void creaImagen(){ imagen = new JLabel(new ImageIcon("gato.gif")); imagen.setPreferredSize(new Dimension(177, 122)); getContentPane().add(imagen,BorderLayout.CENTER); } public void actionPerformed(ActionEvent evento){ JComboBox fuente=(JComboBox)evento.getSource(); //se obtiene el elemento seleccionado String seleccion=(String)fuente.getSelectedItem(); //se cambia la imagen imagen.setIcon(new ImageIcon(seleccion+".gif")); } public static void main(String s[]) { Combo ventana = new Combo(); ventana.creaCombo(); ventana.creaImagen(); ventana.pack(); ventana.setVisible(true); } } Código fuente 149 Figura 91 232 © Grupo EIDOS 12. Interfaces de usuario en Java: componentes atómicos de Swing Al seleccionar un elemento del objeto JComBox se lanza un evento ActionEvent, es decir, el mismo evento que se lanza cuando se pulsa un botón. El constructor utilizado para instanciar un objeto de la clase JComboBox recibe como argumento un array de cadenas (String), que representa las opciones que muestra la lista desplegable. Para indicar la opción seleccionada por defecto utilizamos el método setSelectedIndex() sobre el objeto de la clase JComboBox, y para obtener la opción seleccionada y así mostrar la imagen correspondiente se utiliza el método getSelectedItem() de la clase JComboBox. JMenu Un menú permite elegir al usuario entre múltiples opciones disponibles. Los menús parecen normalmente dentro de barras de menú o como menús contextuales (menú popup). Un elemento de menú hereda también de la clase AbstractButton, como muchas de las clases que hemos visto hasta ahora, veamos en la Figura 92, la jerarquía que presentan los distintos componentes Swing relacionados con la creación de menús. Figura 92 A continuación se comentan cada una de estas clases. • JMenuBar: representa la barra de menú que va a contener los distintos elementos de menú, que serán objetos de la clase JMenu. • JMenu: es una opción de menú determinada, que contiene varios elementos de menú, que serán objetos de la clase JMenuItem, y también puede contener submenús, es decir, objetos de la clase JMenu. • JMenuItem: es un elemento de menú. • JCheckBoxMenuItem: elemento de menú que posee una casilla de verificación. 233 Programación en Java © Grupo EIDOS • JRadioButtonMenuItem: elemento de menú que posee un botón de opción. • JSeparator: elemento de menú especial que ofrece una separación entre elementos de menú de una misma opción de menú. • JPopupMenu: representa un menú contextual o de aparición súbita, contiene elementos de menú (JMenuItem). En el Código fuente 150 se muestra la utilización de todas estas clases, además se va mostrando en un área de texto (JTextArea) los distintos eventos que van generando los distintos componentes de menú junto con la fuente que ha producido dichos eventos. Como ocurre en estos casos primero se muestra el código fuente del ejemplo. import import import public java.awt.*; java.awt.event.*; javax.swing.*; class Menus extends JFrame implements ActionListener, ItemListener{ JMenuBar barraMenu; JMenu menu, submenu; JMenuItem elementoMenu; JRadioButtonMenuItem rbElementoMenu; JCheckBoxMenuItem cbElementoMenu; JTextArea texto; JScrollPane panelScroll; public Menus(){ super("Ventana con múltiples menús"); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public void creaMenu(){ //Se crea la barra de menú barraMenu = new JMenuBar(); setJMenuBar(barraMenu); //se crea el primer menú menu = new JMenu("Un Menú"); menu.setMnemonic(KeyEvent.VK_E); barraMenu.add(menu); //unos cuantos elementos de menú elementoMenu=new JMenuItem("Elemento de menú de texto",KeyEvent.VK_E); elementoMenu.addActionListener(this); menu.add(elementoMenu); //se asigna tecla de acceso rápido elementoMenu.setAccelerator(KeyStroke.getKeyStroke( KeyEvent.VK_1, ActionEvent.ALT_MASK)); elementoMenu=new JMenuItem("Texto e icono",new ImageIcon("icono2.gif")); elementoMenu.setMnemonic(KeyEvent.VK_T); elementoMenu.addActionListener(this); menu.add(elementoMenu); elementoMenu=new JMenuItem(new ImageIcon("icono2.gif")); elementoMenu.setMnemonic(KeyEvent.VK_D); elementoMenu.addActionListener(this); menu.add(elementoMenu); //se añade un separador menu.addSeparator(); //un grupo de elementos de menú de botones de opción. ButtonGroup grupo=new ButtonGroup(); rbElementoMenu=new JRadioButtonMenuItem("Botón de opción"); 234 © Grupo EIDOS 12. Interfaces de usuario en Java: componentes atómicos de Swing rbElementoMenu.setSelected(true); rbElementoMenu.setMnemonic(KeyEvent.VK_O); grupo.add(rbElementoMenu); rbElementoMenu.addActionListener(this); menu.add(rbElementoMenu); rbElementoMenu= new JRadioButtonMenuItem("Otro botón de opción"); rbElementoMenu.setMnemonic(KeyEvent.VK_B); grupo.add(rbElementoMenu); rbElementoMenu.addActionListener(this); menu.add(rbElementoMenu); //Un grupo de casillas de verificación menu.addSeparator(); cbElementoMenu=new JCheckBoxMenuItem("Casilla de verificación"); cbElementoMenu.setMnemonic(KeyEvent.VK_C); cbElementoMenu.addItemListener(this); menu.add(cbElementoMenu); cbElementoMenu=new JCheckBoxMenuItem("Otro más"); cbElementoMenu.setMnemonic(KeyEvent.VK_M); cbElementoMenu.addItemListener(this); menu.add(cbElementoMenu); //un submenú menu.addSeparator(); submenu=new JMenu("Un submenú"); submenu.setMnemonic(KeyEvent.VK_S); elementoMenu=new JMenuItem("Un elemento de menú del submenú"); elementoMenu.setAccelerator(KeyStroke.getKeyStroke( KeyEvent.VK_2, ActionEvent.ALT_MASK)); elementoMenu.addActionListener(this); submenu.add(elementoMenu); elementoMenu= new JMenuItem("Otro elemento de menú"); elementoMenu.addActionListener(this); submenu.add(elementoMenu); menu.add(submenu); //Segúndo menú de la barra de menú menu = new JMenu("Otro Menú"); menu.setMnemonic(KeyEvent.VK_M); barraMenu.add(menu); } public void creaTexto(){ texto= new JTextArea(10, 50); texto.setEditable(false); panelScroll= new JScrollPane(texto); getContentPane().add(panelScroll, BorderLayout.CENTER); } public void actionPerformed(ActionEvent evento){ JMenuItem fuente=(JMenuItem)(evento.getSource()); String mensaje= "ActionEvent detectado.\n" + " Fuente del evento: " + fuente.getText()+"\n"; texto.append(mensaje); } public void itemStateChanged(ItemEvent evento) { String estado=""; JMenuItem fuente =(JMenuItem)(evento.getSource()); if (evento.getStateChange()==ItemEvent.SELECTED) estado="Seleccionado"; else estado="No seleccionado"; String mensaje="ItemEvent detectado.\n" + " Fuente del evento: " + fuente.getText()+"\n" + " Nuevo estado: "+estado+"\n"; texto.append(mensaje); } public static void main(String s[]) { Menus ventana = new Menus(); ventana.creaMenu(); ventana.creaTexto(); ventana.pack(); 235 Programación en Java © Grupo EIDOS ventana.setVisible(true); } } Código fuente 150 Y a continuación un ejemplo de ejecución. Figura 93 En la Figura 94 se puede ver más clara toda la estructura del menú. Figura 94 Los elementos de menú lanzan eventos ActionEvent, y los elementos de menú del tipo JCheckBoxMenuItem lanzan eventos de la clase ItemEvent. El área de texto (JTextArea) encargada de ir mostrando los eventos que se producen se añade a un panel de scroll (ScrollPane). Como se puede ver se han añadido dos teclas rápidas mediante el método setAccelerator(). 236 © Grupo EIDOS 12. Interfaces de usuario en Java: componentes atómicos de Swing Un contextual o de aparición súbita (popup) se encuentra representado por la clase JPopupMenu y debe registrarse un oyente de ratón en cada componente que tenga asociado el menú popup, el oyente debe detectar que el usuario a pulsado el botón derecho del ratón para mostrar el menú contextual correspondiente. El oyente que se ocupa de mostrar el menú contextual lanza el método show() sobre la instancia correspondiente de la clase JPopupMenu. El método show() recibe como parámetros el componente al que se asocia el menú y las coordenadas de la pantalla en la que se quiere mostrar el menú contextual. Vamos a modificar el ejemplo anterior en el que utilizábamos la barra de menú y vamos a añadir un menú contextual (JPopupMenu) que se asociará al área de texto (JTextArea) en la que se muestran los eventos lanzados por los distintos elementos de menú. Se va añadir un nuevo atributo a nuestra clase, llamado menuPopup que pertenece a la clase JPopupMenu. También se va a añadir un nuevo método llamado creaPopup() y cuyo código se muestra en el Código fuente 151. public void creaPopup(){ menuPopup=new JPopupMenu(); //se crean y añadden los distinos elementos de menú de la misma manera elementoMenu=new JMenuItem("Primer elemento del popup",KeyEvent.VK_P); elementoMenu.addActionListener(this); menuPopup.add(elementoMenu); elementoMenu=new JMenuItem("Segundo del popup",new ImageIcon("icono2.gif")); elementoMenu.setMnemonic(KeyEvent.VK_S); elementoMenu.addActionListener(this); menuPopup.add(elementoMenu); menuPopup.addSeparator(); cbElementoMenu=new JCheckBoxMenuItem("Tercer elemento"); cbElementoMenu.setMnemonic(KeyEvent.VK_T); cbElementoMenu.addItemListener(this); menuPopup.add(cbElementoMenu); //se crea el oyente y se registra para el área de texto PopupListener oyente=new PopupListener(); texto.addMouseListener(oyente); } Código fuente 151 Este método se puede lanzar una vez utilizado el método creaTexto(). Como se puede observar se crea una instancia de un objeto de la clase PopupListener, esta clase es una clase interna que hereda de la clase MouseAdapter y que va a ser el oyente de nuestro menú contextual y el que va a mostrarlo. El código de esta clase adaptadora interna es el Código fuente 152. class PopupListener extends MouseAdapter{ public void mousePressed(MouseEvent evento){ menuPopup.show(evento.getComponent(),evento.getX(),evento.getY()); } } Código fuente 152 237 Programación en Java © Grupo EIDOS Y un ejemplo de la utilización del menú contextual se puede observar en la Figura 9. Figura 9 Como se puede comprobar a la vista de la figura anterior, seguimos recogiendo los eventos de los distintos elementos de menú, incluso del menú contextual. Pero si el lector prueba este ejemplo comprobará que el menú contextual parecerá también cuando se pulse el botón izquierdo del ratón, y no sólo cuando se pulse el botón derecho, que sería lo deseable. La clase MouseEvent ofrece el método isPopupTrigger() para averiguar si debemos mostrar el menú popup o no. El método isPopupTrigger() devolverá verdadero si la pulsación del ratón se corresponde con un evento que debe mostrar un menú contextual, en el caso de Windows se corresponde con el botón derecho del ratón. Así nuestro código fuente del adaptador del ratón quedaría como se muestra en el Código fuente 10. class PopupListener extends MouseAdapter{ public void mousePressed(MouseEvent evento){ if (evento.isPopupTrigger()){ menuPopup.show(evento.getComponent(),evento.getX(),evento.getY()); } } } Código fuente 10 La sorpresa es que ahora no se muestra nunca el menú contextual, no con le botón derecho ni con el botón derecho del ratón, la verdad no he conseguido discernir porque no aparece. No vamos a mostrar de nuevo como quedaría todo el código fuente completo del ejemplo modificado, pero en el siguiente enlace se puede obtener. JSlider Este componente permite al usuario seleccionar un valor numérico entre un rango determinado, este componente se utiliza para restringir los valores que puede ofrecer el usuario y así evitar errores y el tratamiento de los mismos. 238 © Grupo EIDOS 12. Interfaces de usuario en Java: componentes atómicos de Swing Para mostrar el funcionamiento de este componente Swing se ha utilizado un ejemplo en el que según se indique en el selector (JSlider) se dará un tamaño determinado a un botón (JButton), el botón se redimensionará según se indique en el selector. Ahora vamos a ver el código, en el Código fuente 154, y más tarde lo comentaremos. import java.awt.*; import java.awt.event.*; import javax.swing.*; //eventos nuevos de Swing import javax.swing.event.*; public class Selector extends JFrame implements ChangeListener{ //selector de valores private JSlider selector; private JButton boton; public Selector(){ super("Ventana con selector (JSlider)"); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public void creaSelector(){ selector=new JSlider(JSlider.HORIZONTAL,0,300,50); selector.addChangeListener(this); selector.setMajorTickSpacing(100); selector.setMinorTickSpacing(10); selector.setPaintTicks(true); selector.setPaintLabels(true); getContentPane().add(selector,BorderLayout.NORTH); } public void creaBoton(){ boton=new JButton("Soy un botón"); JPanel panel=new JPanel(); panel.setLayout(new FlowLayout()); panel.add(boton); getContentPane().add(panel,BorderLayout.CENTER); boton.setSize(50,50); } public void stateChanged(ChangeEvent evento){ JSlider fuente=(JSlider)evento.getSource(); if (!fuente.getValueIsAdjusting()){ boton.setSize((int)fuente.getValue(),(int)fuente.getValue()); } } public static void main(String s[]) { Selector ventana = new Selector(); ventana.creaSelector(); ventana.creaBoton(); ventana.setSize(310,310); ventana.setVisible(true); } } Código fuente 154 Un ejemplo de la ejecución de esta sencilla aplicación la tenemos en la Figura 96. 239 Programación en Java © Grupo EIDOS Figura 96 A la vista del código se pueden hacer los siguientes comentarios y apreciaciones. El constructor utilizado para crear el objeto de la clase JSlider, posee un argumento para indicar la orientación del selector correspondiente, se corresponde con las constantes HORIZONTAL y VERTICAL de la clase JSlider. Los siguientes argumentos, especifican respectivamente, el valor mínimo, el valor máximo y el valor seleccionado inicialmente del objeto JSlider. Si no indicamos estos valores, el valor mínimo del selector será 0, el máximo 100 y el seleccionado inicialmente 50. Para configurar inicialmente el objeto de la clase JSlider, hemos utilizado una serie de métodos: • setMajorTickSpacing(int espaciado): este método nos permite indicar las separaciones mayores entre el máximo y el mínimo del selector. • setMinorTickSpacing(int espaciado); este método indica las separaciones menores entre el rango indicado para el componente Swing JSlider. • setPaintTicks(boolean): mediante este método indicamos si deseamos o no que parezcan las separaciones del objeto JSlider. • setPaintLabels(boolean): nos permite indicar si queremos que se pinten las diferentes etiquetas del rango. Para registrar el oyente del selector hemos utilizado el método addChangeListener(), ya que la clase JSlider lanza eventos de la clase ChangeEvent. Este es un nuevo tipo de evento que se incluye en el paquete javax.swing.event y se lanza cuando se modifica el valor actual de un objeto de la clase JSlider. El método que debe implementar un oyente de eventos ChangeEvent es el método stateChanged(). En el ejemplo anterior hemos visto que se ha utilizado el método getValueIsAdjusting() de la clase JSlider, este método devuelve verdadero mientras se esté seleccionando el valor del objeto JSlider, de esta forma no se redimensionará el botón hasta que el usuario no haya finalizado de arrastrar el tirador del selector, es decir, cuando el usuario ha finalizado con el proceso de selección. 240 © Grupo EIDOS 12. Interfaces de usuario en Java: componentes atómicos de Swing Las etiquetas que aparecen en el selector las podemos personalizar con la ayuda del método setLabelTable() de la clase JSlider y de un objeto de la clase Hashtable. Para utilizar la clase Hashtable debemos importar el paquete Primero crearemos un objeto que representa una tabla hash y mediante el método put() vamos indicando la posición que queremos que ocupe la etiqueta y el objeto JLabel que va a mostrar el texto correspondiente. Veamos, en el Código fuente 155, un sencillo código que podemos añadir a nuestro ya conocido ejemplo para mostrar cuatro etiquetas personalizadas. public void creaSelector(){ selector=new JSlider(JSlider.HORIZONTAL,0,300,50); selector.addChangeListener(this); selector.setMajorTickSpacing(100); selector.setPaintTicks(true); //tabla de etiquetas Hashtable etiquetas=new Hashtable(); etiquetas.put(new Integer(0),new JLabel("Minúsculo")); etiquetas.put(new Integer(100),new JLabel("Pequeño")); etiquetas.put(new Integer(200),new JLabel("Mediano")); etiquetas.put(new Integer(300),new JLabel("Grande")); //asignamos las etiquetas al selector selector.setLabelTable(etiquetas); selector.setPaintLabels(true); getContentPane().add(selector,BorderLayout.NORTH); } Código fuente 155 Si utilizamos esta nueva versión del método creaSelector() obtenemos el resultado que se muestra en la Figura 97. Figura 97 241 Programación en Java © Grupo EIDOS Con la clase JSlider se da por terminado el apartado dedicado al grupo de componentes Swing cuya función es la de recoger la entrada del usuario, a continuación vamos a comentar algunos componentes atómicos cuya función es la de simplemente mostrar información al usuario, algunos de los representantes de este grupo que vamos a tratar a continuación ya los conoceremos de ejemplos anteriores, como ocurre con el componente JLabel. Componentes para mostrar información Se ofrecen distintos ejemplos de componentes Swing encargados de mostrar información. JLabel Ya hemos utilizado numerosas veces este útil y sencillo componente de Swing, como ya sabemos su labor es la de mostrar una información al usuario, ya sea textual o con imágenes o con ambos elementos. En el siguiente ejemplo se muestra la utilización de la clase JLabel creando cuatro objetos distintos de esta clase, se ha añadido un borde a cada etiqueta para que se distingan claramente unas de otras. El código es el que aparece en el Código fuente 156. import import import public java.awt.*; java.awt.event.*; javax.swing.*; class Etiquetas extends JFrame{ private JLabel etiqueta1; private JLabel etiqueta2; private JLabel etiqueta3; private JLabel etiqueta4; private ImageIcon icono; public Etiquetas(){ super("Ventana con etiquetas"); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public void creaEtiquetas(){ icono=new ImageIcon("icono2.gif"); etiqueta1=new JLabel("Imagen y texto",icono,JLabel.CENTER); etiqueta1.setVerticalTextPosition(JLabel.BOTTOM); etiqueta1.setHorizontalTextPosition(JLabel.CENTER); etiqueta1.setBorder(BorderFactory.createLineBorder(Color.red)); etiqueta2=new JLabel("Sólo texto"); etiqueta2.setHorizontalAlignment(JLabel.RIGHT); etiqueta2.setVerticalAlignment(JLabel.TOP); etiqueta2.setBorder(BorderFactory.createLineBorder(Color.blue)); etiqueta3=new JLabel(icono); etiqueta3.setBorder(BorderFactory.createLineBorder(Color.green)); etiqueta4=new JLabel("Imagen y texto",icono,JLabel.CENTER); etiqueta4.setVerticalTextPosition(JLabel.TOP); etiqueta4.setHorizontalTextPosition(JLabel.CENTER); etiqueta4.setBorder(BorderFactory.createLineBorder(Color.pink)); } public void añadeEtiquetas(){ getContentPane().setLayout(new GridLayout(1,4,5,5)); 242 © Grupo EIDOS 12. Interfaces de usuario en Java: componentes atómicos de Swing getContentPane().add(etiqueta1); getContentPane().add(etiqueta2); getContentPane().add(etiqueta3); getContentPane().add(etiqueta4); } public static void main(String s[]) { Etiquetas ventana = new Etiquetas(); ventana.creaEtiquetas(); ventana.añadeEtiquetas(); ventana.pack(); ventana.setVisible(true); } } Código fuente 156 Y el resultado se puede comprobar en la Figura 98. Figura 98 Como se puede observar en el ejemplo anterior es posible especificar la alineación del texto respecto a la imagen que contiene la etiqueta utilizando el método setVerticalTextPosition() y setHorizontalTextPostion(), y mediante las constantes LEFT, CENTER, RIGHT, TOP y BOTTOM. El valor por defecto es centrado (CENTER). También se puede indicar la alineación de los componentes dentro de la etiqueta con los métodos setHorizontalAlignment() y setVerticalAlignment(). Al igual que sucedía con la clase JButton, con los componentes JLabel podemos utilizar código HTML para indicar el formato del texto que se va a mostrar. Veamos un ejemplo, en el Código fuente 157, que hace uso de esta característica. El aspecto de las etiquetas es el que muestra la Figura 99. import import import public java.awt.*; java.awt.event.*; javax.swing.*; class Etiquetas extends JFrame{ private JLabel etiqueta1; private JLabel etiqueta2; private JLabel etiqueta3; private JLabel etiqueta4; private ImageIcon icono; public Etiquetas(){ super("Ventana con etiquetas"); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public void creaEtiquetas(){ icono=new ImageIcon("icono2.gif"); 243 Programación en Java © Grupo EIDOS etiqueta1=new JLabel("<html><font size='4' color='red'><b><i>"+ "Imagen y texto</b></i></font>",icono,JLabel.CENTER); etiqueta1.setVerticalTextPosition(JLabel.BOTTOM); etiqueta1.setHorizontalTextPosition(JLabel.CENTER); etiqueta1.setBorder(BorderFactory.createLineBorder(Color.red)); etiqueta2=new JLabel("<html><ul><li>Elemento 1<li>Elemento2</ul>"); etiqueta2.setBorder(BorderFactory.createLineBorder(Color.blue)); etiqueta3=new JLabel("<html><table border='1'><tr><td>Celda 1</td>"+ "<td>Celda2</td></tr><tr><td align='center' colspan='2'>Celda 3</td>"+" </tr></table>", JLabel.CENTER); etiqueta3.setBorder(BorderFactory.createLineBorder(Color.green)); etiqueta4=new JLabel("<html><h2><i>Imagen y texto</i></h2><hr>", icono,JLabel.CENTER); etiqueta4.setVerticalTextPosition(JLabel.TOP); etiqueta4.setHorizontalTextPosition(JLabel.CENTER); etiqueta4.setBorder(BorderFactory.createLineBorder(Color.pink)); } public void añadeEtiquetas(){ getContentPane().setLayout(new GridLayout(4,1,15,15)); getContentPane().add(etiqueta1); getContentPane().add(etiqueta2); getContentPane().add(etiqueta3); getContentPane().add(etiqueta4); } public static void main(String s[]) { Etiquetas ventana = new Etiquetas(); ventana.creaEtiquetas(); ventana.añadeEtiquetas(); ventana.setSize(300,450); ventana.setVisible(true); } } Código fuente 157 Figura 99 244 © Grupo EIDOS 12. Interfaces de usuario en Java: componentes atómicos de Swing JToolTip Dentro del grupo de componentes atómicos de Swing encargados exclusivamente de mostrar información, también podemos encontrar la clase JToolTip, esta clase representa las pequeñas ayudas que se muestran en forma de pequeño texto explicativo (tooltip) cuando nos situamos sobre un componente determinado. Aunque realmente no vamos a utilizar nunca directamente la clase JToolTip, sino que utilizaremos los métodos setToolTipText() y getTootTipText() de la clase JComponent, ya hemos visto la utilización de estos métodos en ejemplos anteriores, con el primero asignamos un tooltip a un componente Swing y con el segundo obtenemos el tooltip que tiene asignando un componente Swing determinado. JProgressBar Este componente Swing muestra gráficamente el progreso de una operación determinada, la barra de progreso va mostrando el porcentaje de tarea que se ha realizado, nos va informando de la evolución de un proceso. Al crear una instancia de la clase JProgressBar podemos indicar la orientación que va a tener la barra de progreso, para ello se utilizan las constantes HORIZONTAL y VERTICAL de la clase JProgressBar. También podemos indicar el máximo y el mínimo de la barra de progreso. Los máximos y mínimos los podemos manipular con los métodos getMinumum()/setMinimum() y getMaximum()/setMaximum(). Para incrementar el valor actual de la barra de progreso, que se corresponde con el proceso actual, disponemos del método setValue(). A continuación se ofrece un sencillo ejemplo que consiste en ir pulsando un botón que incrementará por cada pulsación el valor actual de la barra de progreso. Al llegar al máximo se emitirá un sonido y después se volverá la barra de progreso a su estado inicial. El código que presenta este ejemplo es el Código fuente 158. import import import public java.awt.*; java.awt.event.*; javax.swing.*; class BarraProgreso extends JFrame implements ActionListener{ private JProgressBar barra; //valor que se va incrementando private int valor; private JButton boton; public BarraProgreso(){ super("Ventana con barra de progreso"); this.valor=0; this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public void creaBarra(){ //una barra de mínimo 0 y máximo 100 barra=new JProgressBar(JProgressBar.HORIZONTAL,0,100); //valor inicial 245 Programación en Java © Grupo EIDOS barra.setValue(0); //indicamos que aparecen las etiquetas del progreso barra.setStringPainted(true); getContentPane().add(barra,BorderLayout.NORTH); } public void creaBoton(){ boton=new JButton("Incrementar"); JPanel panel=new JPanel(); panel.setLayout(new FlowLayout()); panel.add(boton); boton.addActionListener(this); getContentPane().add(panel,BorderLayout.CENTER); } public void actionPerformed(ActionEvent event){ //se incrementa el valor valor=valor+10; if (valor>barra.getMaximum()){ //hemos sobrepasado el máximo de la barra de progreso barra.setValue(barra.getMinimum()); valor=barra.getMinimum(); } else{ barra.setValue(valor); if(valor==barra.getMaximum()) //se emite un pitido al llegar al máximo Toolkit.getDefaultToolkit().beep(); } } public static void main(String s[]) { BarraProgreso ventana = new BarraProgreso(); ventana.creaBarra(); ventana.creaBoton(); ventana.setSize(200,200); ventana.setVisible(true); } } Código fuente 158 Un ejemplo de ejecución este código se ofrece en la Figura 100. Figura 100 Como se puede comprobar en el ejemplo, la barra de progreso muestra el tanto por ciento (%) de la operación realizada, este tanto por ciento lo calcula teniendo en cuenta el valor actual y sus valores 246 © Grupo EIDOS 12. Interfaces de usuario en Java: componentes atómicos de Swing máximo y mínimo. Para que se muestre el etiqueta del tanto por ciento se ha utilizado el método setStringPainted() pasándole como argumento el valor true. Podemos mostrar en la barra de progreso cualquier texto mediante el método setString() de la clase JProgressBar y así variar el comportamiento por defecto de la barra de progreso. Para obtener el tanto por ciento de la tarea realizada (completada) representada por la barra de progreso disponemos del método getPercentComplete(). Este es el último de los componentes Swing pertenecientes al grupo de componentes encargados de mostrar información, los siguientes componentes pertenecen al grupo de los componentes atómicos que muestran también una información pero de una forma más compleja y estructurada, además en algunos casos permiten editar esta información. Componentes que muestran información estructurada En este último apartado se van a tratar dos componentes JColorChooser y JFileChooser que permiten seleccionar un color determinado y un fichero del sistema de ficheros, respectivamente. JColorChooser Este componente Swing además de ofrecer una información estructurada, como pueden ser los distintos colores, nos permite seleccionar un elemento determinado de esa información. La clase JColorChooser representa un componente Swing atómico, pero bastante complejo y completo que permite seleccionar colores de distintas formas. Además este componente permite mostrar de forma sencilla un diálogo que permite seleccionar también un color. Veremos dos ejemplos de las dos formas que podemos utilizar el componente JColorChooser: como si se tratara de otro componente más o como un diálogo. En el Look & Feel de Java (en el apartado correspondiente hablaremos de los distintos Look & Feel y como establecerlos) una instancia de la clase JColorChooser muestra el siguiente aspecto: se encuentra dividido en dos partes, un panel con pestañas (JTabbedPane) y un panel con la muestra de la selección (preview). El panel de selección con pestañas encuentra en la parte superior y presenta tres pestañas, cada una de ellas permite una forma distinta de seleccionar los colores. El panel de muestra se encuentra en la parte inferior y va mostrando el color seleccionado en cada momento del panel de selección. Para poder tratar la selección actual el componente JColorChooser hace uso de una clase llamada ColorSelectionModel que se encuentra en el subpaquete de Swing denominado javax.swing.colorchooser. El modelo de selección de color (instancia de la clase ColorSelectionModel), lanza un evento de la clase ChangeEvent (este evento vimos que también era lanzado por el componente JSlider) cada vez que se cambia la selección del color en el selector de color (JColorChooser). Si queremos detectar los cambios de selección de color del componente JColorChooser debemos registrar el oyente correspondiente al evento ChageEvent, pero no se debe hacer directamente sobre el componente JColorChooser, sino sobre su ColorSelectionModel. Para obtener la instancia de la clase ColorSelectionModel correspondiente a un objeto de la clase JColorChooser, debemos lanzar sobre el objeto JColorChooser el método getSelectionModel(). 247 Programación en Java © Grupo EIDOS Pasemos ahora a ver un código de ejemplo que utiliza un objeto de la clase JColorChooser para configurar el color del texto de una etiqueta. import import import import import public java.awt.*; java.awt.event.*; javax.swing.*; javax.swing.event.*; javax.swing.colorchooser.*; class SelectorColor extends JFrame implements ChangeListener{ private JLabel etiqueta; private JColorChooser selectColor; public SelectorColor() { super("Selector de color"); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public void creaEtiqueta(){ //Configuramos la etiqueta sobre la que se aplica la selección de color etiqueta= new JLabel("La Plataforma Java 2",JLabel.CENTER); etiqueta.setForeground(Color.green); etiqueta.setBackground(Color.white); etiqueta.setOpaque(true); etiqueta.setFont(new Font("SansSerif", Font.BOLD, 24)); etiqueta.setPreferredSize(new Dimension(80,45)); //panel con borde que va a contener la etiqueta JPanel etiquetaPanel = new JPanel(new BorderLayout()); etiquetaPanel.add(etiqueta, BorderLayout.CENTER); etiquetaPanel.setBorder(BorderFactory.createTitledBorder("etiqueta")); getContentPane().add(etiquetaPanel, BorderLayout.CENTER); } public void creaSelectorColor(){ selectColor= new JColorChooser(etiqueta.getForeground()); //se obtiene el modelo de seleción de color para registrar el oyente //de los eventos ChangeEvent selectColor.getSelectionModel().addChangeListener(this); selectColor.setBorder(BorderFactory.createTitledBorder ("Selecciona el color del texto")); getContentPane().add(selectColor,BorderLayout.SOUTH); } public void stateChanged(ChangeEvent e) { etiqueta.setForeground(selectColor.getColor()); } public static void main(String[] args) { SelectorColor ventana = new SelectorColor(); ventana.creaEtiqueta(); ventana.creaSelectorColor(); ventana.pack(); ventana.setVisible(true); } } Código fuente 159 El aspecto del ejemplo es el de la Figura 101 248 © Grupo EIDOS 12. Interfaces de usuario en Java: componentes atómicos de Swing Figura 101 Como se puede comprobar en el ejemplo el constructor utilizado de la clase JColorChooser recibe como parámetro un color, este es el color seleccionado inicialmente dentro del selector de color. En nuestro caso le hemos indicado el color de la letra de la etiqueta sobre la que se va a aplicar las distintas selecciones de color. El método stateChanged(), que se lanzará cada vez que se cambie la selección de color del selector de color, recupera el color actual mediante el método getColor() de la clase JColorChooser, y se lo aplica al texto de la etiqueta mediante el método setForeground(). En la ejecución del ejemplo se puede observar las tres formas distintas de seleccionar el color que permite el componente JColorChooser, en la Figura 102 se puede ver otra selección distinta, utilizando el formato de color RGB. En este caso el componente JColorChooser lo hemos tratado como otro componente atómico más, añadiéndolo al panel de contenido de la ventana, pero también podemos mostrarlo como un diálogo, para ello podemos utilizar el método estático showDialog() de la clase JColorChooser. La sintaxis de este método es: JColorChooser.showDialog(Component padre, String título, Color color) 249 Programación en Java © Grupo EIDOS Figura 102 El primer argumento de este método es el componente padre del diálogo (el componente del que depende), el segundo el título del diálogo y el tercero el color seleccionado por defecto en el diálogo de selección de color. Ahora vamos a modificar el ejemplo anterior para realizar una función similar pero utilizando el selector de colores como un diálogo modal. Para mostrar el diálogo vamos a utilizar un botón (JButton) y el evento ActionEvent. En este nuevo supuesto no se utiliza la clase ColorSelectionModel, sino que el color seleccionado lo obtenemos directamente cuando el usuario pulse el botón de OK del diálogo. Esto se ve mucho más claro en el código fuente del ejemplo import import import public 250 java.awt.*; java.awt.event.*; javax.swing.*; class DialogoSelectorColor extends JFrame implements ActionListener{ private JLabel etiqueta; private JButton boton; public DialogoSelectorColor() { super("Selector de color con diálogo"); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } © Grupo EIDOS 12. Interfaces de usuario en Java: componentes atómicos de Swing }); } public void creaEtiqueta(){ //Configuramos la etiqueta sobre la que se aplica la selección de color etiqueta= new JLabel("La Plataforma Java 2",JLabel.CENTER); etiqueta.setForeground(Color.green); etiqueta.setBackground(Color.white); etiqueta.setOpaque(true); etiqueta.setFont(new Font("SansSerif", Font.BOLD, 24)); etiqueta.setPreferredSize(new Dimension(80,45)); //panel con borde que va a contener la etiqueta JPanel etiquetaPanel = new JPanel(new BorderLayout()); etiquetaPanel.add(etiqueta, BorderLayout.CENTER); etiquetaPanel.setBorder(BorderFactory.createTitledBorder("etiqueta")); getContentPane().add(etiquetaPanel, BorderLayout.CENTER); } public void creaBoton(){ JPanel panel=new JPanel(); panel.setLayout(new FlowLayout()); boton=new JButton("Selección de color"); panel.add(boton); boton.addActionListener(this); getContentPane().add(panel,BorderLayout.SOUTH); } public void actionPerformed(ActionEvent evento) { Color color=JColorChooser.showDialog(this,"Selecciona un color", etiqueta.getForeground()); if (color!=null) etiqueta.setForeground(color); } public static void main(String[] args) { DialogoSelectorColor ventana = new DialogoSelectorColor(); ventana.creaEtiqueta(); ventana.creaBoton(); ventana.setSize(400,180); ventana.setVisible(true); } } Código fuente 160 El nuevo aspecto de nuestro ejemplo se muestra en la Figura 103. Figura 103 Y al pulsar el botón de selección de color aparece la Figura 104. 251 Programación en Java © Grupo EIDOS Figura 104 Si el usuario cancela la selección de color, ya sea cerrando el diálogo o pulsando el botón de cancelación, no se devolverá ningún color, sino que se devolverá el valor null (nulo). En la figura 18 se puede observar que además de los botones OK y Cancel aparece el botón Reset, al pulsar este botón el color seleccionado actualmente en el diálogo será el que se había indicado inicialmente, es decir, el color que se indica en el método showDialog(). JFileChooser Este es el último componente atómico de Swing que vamos a ver. El componente JFileChooser es similar al visto anteriormente, podemos crearlo como un componente más y añadirlo a un panel o bien mostrarlo como un diálogo modal. Este componente nos permite navegar por el sistema de ficheros y seleccionar un fichero o directorio. Normalmente se utilizan estos componentes como diálogos modales. Pero la clase JFileChooser únicamente muestra el interfaz que nos permite seleccionar los ficheros, pero el tratamiento de los mismos corre de nuestra cuenta, es decir, el tratamiento de los ficheros debemos implementarlo en nuestra aplicación. La clase JFileChooser ofrece dos métodos para mostrar los dos tipos de selectores de ficheros distintos, el que se utiliza para abrir ficheros y el que se utiliza para grabar ficheros, estos métodos son showOpenDialog() y showSaveDialog(). Ambos métodos reciben como argumento un objeto Component que representa el padre del diálogo de selección de ficheros. Estos métodos no son estáticos como ocurría con el componente ColorChooser, sino que se deben lanzar sobre una instancia de la clase JFileChooser. 252 © Grupo EIDOS 12. Interfaces de usuario en Java: componentes atómicos de Swing Tanto el método showOpenDialog() como showSaveDialog() devuelven un valor entero que se corresponde con las constantes que indican el resultado de la selección del usuario, indican si el usuario a pulsado el botón de cancelación (CANCEL_OPTION) o no (APPROVE_OPTION), ambas constantes de encuentran definidas en la clase JFileChooser. El aspecto que muestra el diálogo para la apertura de fichero es muy similar al del diálogo para guardar ficheros. A continuación vamos a mostrar un sencillo ejemplo que posee dos botones y un área de texto (JTextArea), según el botón que se pulse se mostrará un diálogo de selección de ficheros distinto, para abrir ficheros y para guardar ficheros. En el área de texto se irán mostrando las selecciones de ficheros o cancelaciones realizadas por el usuario. El código fuente completo es el Código fuente 161. import import import import public java.awt.*; java.awt.event.*; javax.swing.*; java.io.*; class DialogoSelectorFichero extends JFrame implements ActionListener{ private JTextArea resultado; private JButton botonAbrir; private JButton botonGuardar; private JFileChooser selectorFichero; public DialogoSelectorFichero() { super("Diálogo para la selección de ficheros"); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); selectorFichero=new JFileChooser(); } public void creaBotones(){ ImageIcon iconoAbrir = new ImageIcon("abrir.gif"); botonAbrir=new JButton("Abrir un fichero...", iconoAbrir); ImageIcon iconoGuardar = new ImageIcon("guardar.gif"); botonGuardar=new JButton("Guardar un fichero...", iconoGuardar); botonAbrir.addActionListener(this); botonGuardar.addActionListener(this); JPanel panelBotones=new JPanel(); panelBotones.add(botonAbrir); panelBotones.add(botonGuardar); getContentPane().add(panelBotones,BorderLayout.NORTH); } public void creaResultado(){ resultado = new JTextArea(5,20); resultado.setMargin(new Insets(5,5,5,5)); resultado.setEditable(false); JScrollPane panelResultado = new JScrollPane(resultado); getContentPane().add(panelResultado,BorderLayout.CENTER); } public void actionPerformed(ActionEvent evento) { if (evento.getSource()==botonAbrir){ int valorDevuelto= selectorFichero.showOpenDialog(this); if (valorDevuelto== JFileChooser.APPROVE_OPTION) { File fichero = selectorFichero.getSelectedFile(); resultado.append("Abierto: " + fichero.getName()+"\n"); } else resultado.append("Apertura cancelada por el usuario\n"); }else{ int valorDevuelto= selectorFichero.showSaveDialog(this); 253 Programación en Java © Grupo EIDOS if (valorDevuelto== JFileChooser.APPROVE_OPTION) { File fichero = selectorFichero.getSelectedFile(); resultado.append("Guardado: " + fichero.getName()+"\n"); } else resultado.append("Operación de grabado cancelada"+ "por el usuario\n"); } } public static void main(String[] args) { DialogoSelectorFichero ventana = new DialogoSelectorFichero(); ventana.creaBotones(); ventana.creaResultado(); ventana.pack(); ventana.setVisible(true); } } Código fuente 161 El aspecto de nuestra ventana es el de la Figura 105. Figura 105 Y la del diálogo de selección de ficheros para su apertura, es la Figura 106. Figura 106 254 © Grupo EIDOS 12. Interfaces de usuario en Java: componentes atómicos de Swing Como se puede comprobar en el ejemplo, para obtener el fichero que se ha seleccionado se utiliza el método getSelectedFile(), que devuelve un objeto de la clase File. A través de este objeto podemos manipular ficheros, en una aplicación real se utilizaría para guardar o abrir ficheros. El constructor de la clase JFileChooser puede recibir como argumento un objeto File o String para indicar el directorio inicial. El directorio inicial también se puede especificar mediante el método setCurrentDirectory() al que se le pasa como argumento un objeto de la clase File. Cada vez que se muestra el diálogo para la selección de ficheros guarda el directorio actual de la selección anterior, es decir, va recordando las rutas de las selecciones anteriores. Además permite la creación de nuevos directorios. Este ha sido el componente Swing atómico que cierra este capítulo, en el siguiente capítulo trataremos el nuevo gestor de diseño de Swing y la característica Pluggable Look & Feel. 255 Interfaces de usuario en Java: otras características de Swing Introducción Este capítulo cierra el ciclo de capítulos dedicado a Swing, este capítulo no va a ser tan extenso como los anteriores ya que trataremos dos aspectos muy determinados, el nuevo gestor de diseño de Swing, representado por la clase BoxLayout y la característica especial que ofrece Swing denominada Pluggable Look & Feel. El gestor de diseño BoxLayout La clase BoxLayout, que podemos encontrar en el paquete javax.swing, representa a un nuevo gestor de diseño ofrecido por Swing. Este gestor de diseño organiza los componentes en una pila, uno encima del otro y también de izquierda a derecha uno al lado del otro. Se puede decir que es una versión ampliada del gestor de diseño FlowLayout que ofrece AWT. Antes de seguir comentando este gestor de diseño ofrecido por Swing, vamos a ver con un sencillo ejemplo la forma en la que distribuye los componentes en el panel al que se aplica. El código fuente de este ejemplo es el Código fuente 162. Programación en Java © Grupo EIDOS import javax.swing.*; import java.awt.event.*; public class BoxLayoutSencillo{ public static void main(String s[]) { JFrame ventana = new JFrame("Ventana con BoxLayout"); ventana.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); JPanel panelContenido=new JPanel(); panelContenido.setLayout(new BoxLayout(panelContenido,BoxLayout.Y_AXIS)); panelContenido.add(new JButton("Soy un botón")); panelContenido.add(new JButton("Soy un botón más")); panelContenido.add(new JButton("Otro botón")); panelContenido.add(new JButton("Soy el último")); ventana.setContentPane(panelContenido); ventana.pack(); ventana.setVisible(true); } } Código fuente 162 Y el aspecto que ofrece es el de la Figura 107. Figura 107 En el ejemplo anterior podemos comprobar la forma de crear un gestor de diseño para distribuir los componentes de arriba a abajo. El constructor de la clase BoxLayout posee dos argumentos, el panel (JPanel) sobre el que se va a aplicar el gestor de diseño y la orientación en la que se van a distribuir los componentes dentro de dicho panel. Este último parámetro es un entero que se corresponde con las siguientes constantes definidas en la clase BoxLayout: • X_AXIS: los componentes se distribuyen de izquierda a derecha. • Y_AXIS: los componentes se distribuyen de arriba a abajo. Si retomamos el ejemplo anterior y cambiamos la constante BoxLayout.Y_AXIS por BoxLayout.X_ AXIS, se obtiene el resultado que muestra la Figura 108. Figura 108 258 © Grupo EIDOS 13. Interfaces de usuario en Java: otras características de Swing Conjuntamente con la clase BoxLayout, se suele utilizar la clase Box, esta clase ofrece una serie de métodos para controlar de una forma más precisa la forma en la que se distribuyen los componentes dentro de un panel al que se le ha aplicado este gestor de diseño BoxLayout. En el Código fuente 163 se pueden ver en acción dos de estos métodos, que son métodos estáticos de la clase Box. import import import public javax.swing.*; java.awt.event.*; java.awt.*; class DosBoxLayout{ public static void main(String s[]) { JFrame ventana = new JFrame("Ventana con BoxLayout"); ventana.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); JPanel panelSuperior=new JPanel(); panelSuperior.setBorder(BorderFactory.createLineBorder(Color.red)); panelSuperior.setLayout(new BoxLayout(panelSuperior,BoxLayout.Y_AXIS)); panelSuperior.add(new JLabel("Esto es una etiqueta")); panelSuperior.add(Box.createRigidArea(new Dimension(0,5))); panelSuperior.add(new JTextArea(5,30)); JPanel panelInferior=new JPanel(); panelInferior.setBorder(BorderFactory.createLineBorder(Color.green)); panelInferior.setLayout(new BoxLayout(panelInferior,BoxLayout.X_AXIS)); panelInferior.add(Box.createHorizontalGlue()); panelInferior.add(new JButton("Botón 1")); panelInferior.add(Box.createRigidArea(new Dimension(10,0))); panelInferior.add(new JButton("Botón 2")); ventana.getContentPane().add(panelSuperior,BorderLayout.NORTH); ventana.getContentPane().add(panelInferior,BorderLayout.CENTER); ventana.pack(); ventana.setVisible(true); } } Código fuente 163 El interfaz que genera este código es el de la Figura 109. Figura 109 259 Programación en Java © Grupo EIDOS A la vista del código se puede decir que este interfaz está compuesto por dos paneles los que se han aplicado un gestor de diseño BoxLayout distinto, uno de arriba a abajo y otro de izquierda a derecha. Se ha aplicado un borde a cada uno de estos dos paneles para distinguirlos más claramente. En el primer panel se ha utilizado el método createRigidArea() de la clase Box para crear una separación fija (área rígida) invisible entre la etiqueta (JLabel) y el área de texto (JTextArea). En este caso se ha indicado una separación vertical de cinco pixels. En el segundo panel se utiliza otro método de la clase Box, el método createHorizontalGlue(), este método provoca que los dos botones que se añadan a continuación se desplacen hacia la derecha, es decir, este método rellena el espacio sobrante y desplaza a los componentes hacia la derecha. El método createHorizontalGlue() crea un área invisible que crece horizontalmente para rellenar todo el espacio libre disponible dentro del contenedor. En este panel inferior también se utiliza el método createRigidArea() para separar los dos botones diez pixels, en este caso se ha utilizado un área sólo con componente horizontal. Si la llamada al método createHorizontalGlue() la hubiéramos realizado en el lugar en la que se encuentra la llamada al método createRigidArea(), el resultado hubiera sido la Figura 110. Figura 110 A la vista de la imagen se puede decir que el área se ha extendido entre los dos botones, enviando al segundo botón a la derecha del todo. La clase Box ofrece también el método createVerticalGlue(), en este caso el área invisible se extiende de forma vertical enviando los componentes dentro del panel hacia la zona inferior. El gestor de diseño BoxLayout respeta el tamaño máximo de los componentes que contiene y también la alineación establecida por los mismos, aunque para que no existan problemas de alineación todos los componentes deben tener la misma. Si modificamos el primer ejemplo visto en este apartado como indica el Código fuente 164, se obtiene la Figura 111. import java.awt.event.*; import java.awt.*; public class BoxLayoutSencillo{ public static void main(String s[]) { JFrame ventana = new JFrame("Ventana con BoxLayout"); ventana.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); JPanel panelContenido=new JPanel(); 260 © Grupo EIDOS 13. Interfaces de usuario en Java: otras características de Swing JButton boton1=new JButton("Soy un botón"); boton1.setAlignmentX(Component.CENTER_ALIGNMENT); JButton boton2=new JButton("Soy un botón más"); JButton boton3=new JButton("Otro botón"); boton3.setAlignmentX(Component.RIGHT_ALIGNMENT); JButton boton4=new JButton("Soy el último"); boton4.setAlignmentX(Component.CENTER_ALIGNMENT); panelContenido.setLayout(new BoxLayout(panelContenido,BoxLayout.Y_AXIS)); panelContenido.add(boton1); panelContenido.add(boton2); panelContenido.add(boton3); panelContenido.add(boton4); ventana.setContentPane(panelContenido); ventana.pack(); ventana.setVisible(true); } } Código fuente 164 Figura 111 La alineación se respetará en este otro ejemplo. import import import public javax.swing.*; java.awt.event.*; java.awt.*; class BoxLayoutSencillo{ public static void main(String s[]) { JFrame ventana = new JFrame("Ventana con BoxLayout"); ventana.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); JPanel panelContenido=new JPanel(); JButton boton1=new JButton("Soy un botón"); boton1.setAlignmentX(Component.CENTER_ALIGNMENT); JButton boton2=new JButton("Soy un botón más"); boton2.setAlignmentX(Component.CENTER_ALIGNMENT); JButton boton3=new JButton("Otro botón"); boton3.setAlignmentX(Component.CENTER_ALIGNMENT); JButton boton4=new JButton("Soy el último"); boton4.setAlignmentX(Component.CENTER_ALIGNMENT); panelContenido.setLayout(new BoxLayout(panelContenido,BoxLayout.Y_AXIS)); panelContenido.add(boton1); panelContenido.add(boton2); panelContenido.add(boton3); panelContenido.add(boton4); 261 Programación en Java © Grupo EIDOS ventana.setContentPane(panelContenido); ventana.pack(); ventana.setVisible(true); } } Código fuente 165 Cuyo resultado es la Figura 112. Figura 112 Estableciendo el Look & Feel En el capítulo anterior comentábamos que una característica que ofrece Swing es el aspecto y comportamiento configurable de sus componentes, lo que se denomina Pluggable Look & Feel, es decir, a nuestra aplicación Java le podemos asignar un aspecto específico. Incluso, como veremos un poco más adelante, podemos establecer y cambiar en tiempo de ejecución de nuestra aplicación su Look & Feel. Cuando no se indica ningún Look & Feel determinado Swing utiliza el Look & Feel por defecto, que es el Look & Feel de Java, también denominado Metal (fue el código que se le dio al proyecto que lo desarrolló). Se pueden distinguir cuatro Look & Feel estándar distintos: 262 • Java (Metal): se trata del Look & Feel por defecto y es el que se ha estado utilizando en todos los ejemplos de los componentes Swing, ya que en nuestros ejemplos no hemos especificado ningún Look & Feel. Se corresponde con la implementación de la clase javax.swing.plaf.metal.MetalLookAndFeel. Este Look & Feel lo podemos utilizar en cualquier plataforma. • Windows: es aspecto que presentan los sistemas Windows y lo encontramos implementado en la clase com.sun.java.swing.plaf.windows.WindowsLookAndFeel. A diferencia del anterior Look & Feel, éste únicamente puede ser utilizado en plataformas Windows. • Motif: representa el aspecto CDE/Motif, que es el aspecto que tienen las plataformas de Sun. Este Look & Feel es implementado por la clase com.sun.java.swing.plaf.motif.MotifLookAndFeel, y al igual que el Look & Feel de Java puede ser utilizado en cualquier plataforma. • Mac: este último Look & Feel es el correspondiente a los sistemas Macintosh. Se encuentra en al clase javax.swing.plaf.mac.MacLookAndFeel. Al igual que ocurría con el Look & Feel de Windows, este Look & Feel está restringido a una plataforma específica, esta plataforma es la plataforma Mac OS. © Grupo EIDOS 13. Interfaces de usuario en Java: otras características de Swing Todas estas clases que implementan un Look & Feel determinado, tienen la característica común que todas ellas heredan de la clase abstracta javax.swing.LookAndFeel. Para indicar un Look & Feel determinado en una aplicación Java utilizaremos la clase UIManager (gestor de interfaz gráfico) que se encuentra en el paquete javax.swing. La clase UIManager ofrece un método estático llamado setLookAndFeel(), que recibe como parámetro una cadena que especifica la clase que se corresponde con el Look & Feel que deseamos asignar a nuestra aplicación Java. El método setLookAndFeel() de la clase UIManager lanza una excepción de la clase UnsupportedLookAndFeelException, que se encuentra también dentro del paquete javax.swing, cuando el Look & Feel que queremos utilizar no es soportado por la plataforma actual, deberemos atrapar por lo tanto esta excepción cuando invoquemos el método setLookAndFeel(). De esta forma si queremos que nuestra aplicación posea el Look & Feel de Motif deberemos incluir el Código fuente 166 dentro del método main() de la aplicación. try { UIManager.setLookAndFeel("com.sun.java.swing.plaf.motif.MotifLookAndFeel"); } catch (Exception e) { } new AplicacionSwing(); Código fuente 166 Pero el Look & Feel, como ya hemos adelantado al comienzo de este apartado, se puede establecer una vez que la aplicación ya se ha iniciado y el interfaz de usuario es visible, es decir, podemos establecer el Look & Feel de la aplicación en tiempo de ejecución. En este caso además de utilizar el método setLookAndFeel() de la clase UIManager, deberemos utilizar el método estático updateComponentTreeUI() de la clase SwingUtilities, presente también en el paquete javax.swing. El método updateComponentTreeUI() recibe como argumento un objeto de la clase Component que representa el contenedor de alto nivel sobre el que se quiere actualizar su Look & Feel. El método updateComponentTreeUI() de la clase SwingUtilities debe utilizarse para cada uno de los contenedores de alto nivel del interfaz de usuario de la aplicación, para que de esta forma todos actualicen su Look & Feel en tiempo de ejecución. Además es recomendable lanzar el método pack() sobre cada uno de los contenedores de alto nivel del interfaz de usuario, para que se redimensionen todos los componentes según su nuevo Look & Feel. La utilización de este método, conjuntamente con el método setLookAndFee() se puede ver en el Código fuente 167. try { UIManager.setLookAndFeel(lf); SwingUtilities.updateComponentTreeUI(contenedor); this.pack(contenedor); } catch (Exception excepcion) { System.out.println("No se pudo asignar el Look & Feel"); } Código fuente 167 En este caso se supone que sólo existe un contenedor de alto nivel. 263 Programación en Java © Grupo EIDOS La clase UIManager, además del método setLookAndFeel(), nos ofrece otros dos métodos que pueden resultar de utilidad, estos métodos son getSystemLookAndFeelClassName() y getCrossPlatformLooAndFeelClassName(). Ambos métodos devuelven una cadena, en el primer caso se corresponde con el nombre de la clase del Look & Feel de la plataforma sobre la que se está ejecutando la aplicación, y el segundo caso se corresponde con el nombre de la clase que ofrece el Look & Feel de Java, que es el Look & Feel que se garantiza funcionará correctamente para todas las plataformas. Por lo tanto estos dos métodos se pueden utilizar como argumento para el método setLookAndFeel(), así por ejemplo si deseamos aplicar el Look & Feel de la plataforma sobre la que se ejecuta la aplicación, utilizaríamos el Código fuente 168. try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception e) { } Código fuente 168 Para mostrar la característica Pluggable Look & Feel de Swing se va a utilizar un sencillo ejemplo que consiste una aplicación con una ventana (JFrame) que permite especificar el Look & Feel en tiempo de ejecución. LA ventana de la aplicación del ejemplo, además de contener diversos componentes Swing, contiene cinco botones de opción (JOptionButton) agrupados, y que al seleccionar cada uno de ellos se aplicará sobre la aplicación el Look & Feel que representa cada opción. Es decir, existen cinco botones de opción, cuatro de ellos para cada uno de los distintos Look & Feel (Java, Windows, Motif y Mac) y otro para seleccionar el Look & Feel de la plataforma sobre la que se ejecuta la aplicación. Antes de seguir comentando más detalles de esta aplicación de ejemplo, vamos a mostrar el aspecto que tendría esta aplicación. Al ejecutar la aplicación el aspecto que muestra es el de la Figura 113, y se corresponde con el Look & Feel de Java. Figura 113 264 © Grupo EIDOS 13. Interfaces de usuario en Java: otras características de Swing Al iniciarse su ejecución dentro del constructor de la aplicación, se establece el Look & Feel mediante el nombre de la clase devuelto por el método getCrossPlatformLookAndFeelClassName() de la clase UIManager, debido a esto al arrancar la aplicación comienza a ejecutarse utilizando el Look & Feel de Java. El código fuente perteneciente al constructor de nuestra clase se puede observar en el Código fuente 169. public LF(){ super("Pluggable Look & Feel"); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); try { UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); } catch (Exception e) { } getContentPane().setLayout(new BoxLayout(getContentPane(),BoxLayout.Y_AXIS)); } Código fuente 169 En el constructor además de establecer el Look & Feel de Java se establece el gestor de diseño que va a utilizar el panel de contenido de la ventana. El gestor de diseño que se utiliza es BoxLayout, descrito en el apartado anterior, en su variante de distribución de componentes de arriba a abajo (BoxLayout.Y_AXIS). De la figura anterior también se puede comprobar que la opción seleccionada por defecto se corresponde lógicamente con el Look & Feel que presenta la aplicación inicialmente. Podemos ir asignando a la aplicación los distintos Look & Feel seleccionando la opción correspondiente. A continuación se muestran otras dos figuras, cada una con otro nuevo Look & Feel. La primera corresponde al Look & Feel de Windows y la segunda al Look & Feel de Motif. Figura 114 265 Programación en Java © Grupo EIDOS Figura 115 En mi caso no he podido utilizar el Look & Feel de Mac, ya que he utilizado una plataforma Windows para ejecutar la aplicación de ejemplo, por lo tanto éste Look & Feel no estará disponible, y además en mi caso particular el Look & Feel del sistema (Look & Feel de la plataforma actual) será por lo tanto el de Windows. No vamos hacer como en otros ejemplos de este curso, en los que se ha ofrecido el código completo del ejemplo, sólo vamos a mostrar y comentar aquellos fragmentos que resultan más interesantes (sobre todo desde el punto de vista de la característica Pluggable Look & Feel), ya que el código fuente de la aplicación de ejemplo es bastante sencillo y se utilizan componentes Swing vistos en los capítulos anteriores. Empezamos ofreciendo la declaración de nuestra clase (Código fuente 170). public class LF extends JFrame implements ActionListener{ Código fuente 170 Como se puede ver es una sencilla ventana que va a contener una serie de componentes Swing. No vamos a detenernos en la creación de los distintos componentes del interfaz de usuario, pero si que es interesante mostrar como se crean las opciones que permiten seleccionar el Look & Feel de la aplicación. Para crear las opciones que representan los distintos LooK & Feel se ha utilizado el método creaOpciones(), cuyo código ofrecemos en el Código fuente 171. public void creaOpciones(){ panelOpciones=new JPanel(); panelOpciones.setLayout(new GridLayout(1,5,5,5)); opWindows=new JRadioButton("Windows"); opWindows.setMnemonic(KeyEvent.VK_W); 266 © Grupo EIDOS 13. Interfaces de usuario en Java: otras características de Swing opWindows.addActionListener(this); panelOpciones.add(opWindows); opJava=new JRadioButton("Java/Metal"); opJava.setMnemonic(KeyEvent.VK_J); opJava.addActionListener(this); panelOpciones.add(opJava); opJava.setSelected(true); opMotif=new JRadioButton("Motif"); opMotif.setMnemonic(KeyEvent.VK_M); opMotif.addActionListener(this); panelOpciones.add(opMotif); opMac=new JRadioButton("Mac"); opMac.setMnemonic(KeyEvent.VK_C); opMac.addActionListener(this); panelOpciones.add(opMac); opSistema=new JRadioButton("Sistema"); opSistema.setMnemonic(KeyEvent.VK_S); opSistema.addActionListener(this); panelOpciones.add(opSistema); panelOpciones.setBorder(BorderFactory.createLoweredBevelBorder()); //se agrupan las opciones ButtonGroup grupo=new ButtonGroup(); grupo.add(opWindows); grupo.add(opJava); grupo.add(opMotif); grupo.add(opMac); grupo.add(opSistema); getContentPane().add(panelOpciones); } Código fuente 171 La pulsación de cada opción será tratada dentro del método actionPerformed() que realmente, conjuntamente con el constructor de la clase, es dónde utilizamos métodos y clases relacionados directamente con el mecanismo Pluggable Look & Feel. A continuación se ofrece el código fuente perteneciente al método actionPerformed(). Será en este método dónde se establezca el Look & Feel seleccionado a través de los distintos botones de opción. public void actionPerformed(ActionEvent evento){ Object fuente=evento.getSource(); String lf=""; if (fuente==opWindows) lf="com.sun.java.swing.plaf.windows.WindowsLookAndFeel"; else if(fuente==opJava) lf="javax.swing.plaf.metal.MetalLookAndFeel"; else if(fuente==opMotif) lf="com.sun.java.swing.plaf.motif.MotifLookAndFeel"; else if(fuente==opMac) lf="javax.swing.plaf.mac.MacLookAndFeel"; else if(fuente==opSistema) lf=UIManager.getSystemLookAndFeelClassName(); try { UIManager.setLookAndFeel(lf); SwingUtilities.updateComponentTreeUI(this); this.pack(); } catch (UnsupportedLookAndFeelException excepcion) { texto.append("Look & Feel no soportado.\n"+excepcion+"\n"); } catch (ClassNotFoundException excepcion){ 267 Programación en Java © Grupo EIDOS texto.append("Clase no encontrada.\n"+excepcion+"\n"); } catch (InstantiationException excepcion){ texto.append("Excepción de instanciación.\n"+excepcion+"\n"); } catch(IllegalAccessException e){ texto.append("Excepción de acceso.\n"+e+"\n"); } } Código fuente 172 Como se puede comprobar a la vista del código anterior, se decidimos atrapar la excepción UnsupportedLookAndFeelException, se nos obligará a atrapar tres excepciones más. Por lo demás, si se tiene en cuenta lo explicado hasta el momento, el código de este método es bastante sencillo, lo único que se hace es establecer el Look & Feel que se corresponde con la opción pulsada. Si ocurre una excepción se muestra en el área de texto (JTextArea) de la aplicación. Si en mi caso particular, utilizando una plataforma Windows, selecciono la opción correspondiente al Look & Feel de Mac, obtendré una excepción lanzada por el método setLooAndFeel(), esto se puede observar en la Figura 116. Figura 116 El fichero fuente de Java perteneciente a este ejemplo se puede obtener aquí. Con este apartado damos por terminado la construcción de interfaces en Java y también los componentes Swing, aunque en uno de los capítulos dedicados a los applets volveremos a tratar un componente Swing, el contenedor de alto nivel representado por la clase JApplet. Como se habrá podido comprobar Swing es bastante extenso y potente, tan extenso que sería posible dedicar un curso completo dedicado a Swing. Con estos tres capítulos hemos pretendido mostrar los aspectos más destacados de Swing, sin tener la intención de abarcar todo, ya que como hemos dicho nos dará para un curso completo más. En el siguiente capítulo comenzaremos a ver el otro tipo de aplicaciones que podemos realizar con Java, los applets. 268 Applets de Java: introducción a los Applets Conceptos previos A lo largo del presente capítulo vamos a hacer referencia a una serie de términos relacionados con los applets de Java que conviene tener claros, por ello hemos incluido este apartado, muchos de estos conceptos pueden que sean conocidos para todos, pero es mejor partir de una serie de definiciones que compartamos todos. Internet Internet es un conjunto de redes cuyo origen se remonta al año 1969 en Estados Unidos. En un principio Internet se creó con fines militares durante La Guerra Fría. Era un proyecto del Departamento de Defensa de Estados Unidos. En un principio este proyecto se denominó ARPANET (Advanced Research Projects Agency-ARPA). El objetivo para el que se había creado el proyecto era mantener una infraestructura robusta de diferentes ordenadores conectados entre sí. Si se suprimía algún nodo de la red, la red debería seguir funcionando, es decir, no existía ningún nodo central o principal. Estas características son debidas al origen militar de la red, de esta forma, si algunas de las partes de la red se destruían debido a un ataque militar, el resto de la red puede seguir funcionando. Programación en Java © Grupo EIDOS De este inicio militar Internet paso a ser utilizado dentro del campo académico, se utilizaba en universidades para proyectos de investigación. Del ámbito académico Internet pasó a ser de dominio público, para todo tipo de actividades, culturales, comerciales, lúdicas, filosóficas, etc. La fama y popularidad de Internet se consiguió y llegó a través de la World Wide Web. La Web o la Gran Telaraña Mundial se basa en el protocolo de Transferencia de Hipertexto HTTP (Hypertext Transport Protocol). La Web nació en el CERN (European Center for Nuclear Research) de la mano de Tim Berners-Lee como intento de crear un sistema hipermedia, basado en enlaces hipertextuales, para la compartición de información científica entre los integrantes del laboratorio del CERN, de una manera clara y sencilla aprovechando los recursos que ofrecía Internet en aquella época. A finales de 1995 la Web se había convertido en el servicio más solicitado y famoso de Internet. URLs y direcciones IP Una URL (Uniform Resource Locator) es el mecanismo que se utiliza en Internet para localizar e identificar de forma única un recurso dentro de Internet. La sintaxis general de una URL se compone de tres partes: protocolo://<nombre del dominio>/<nombre del recurso> El campo protocolo especifica el protocolo de Internet que se va a utilizar para transferir los datos del recurso entre el cliente y el servidor. Existen varios protocolos en Internet como pueden ser: FTP (File Transfer Protocolo) para la transferencia de ficheros; HTTP (HyperText Transfer Protocol) protocolo para la transferencia de hipertexto; Telnet, Gopher, etc. A continuación del protocolo aparece los dos puntos (:), la doble barra (//) y el nombre del dominio. El nombre del dominio es la forma de identificar una máquina de forma única en Internet, este nombre de dominio puede ser la dirección IP, en el formato de tétrada punteada (201.234.123.87) o bien su traducción correspondiente a un nombre más descriptivo, como puede ser www.eidos.es. Una dirección IP es un número de 32 bits para identificar de forma única una máquina conectada a Internet. Se divide en grupos de 8 bits y se identifica con su número en notación decimal separado cada uno de ellos por puntos, a este formato se le denomina tétrada punteada. Debido a que recordar direcciones IP puede ser difícil y poco manejable se suelen identificar con nombres de máquinas, así la dirección IP 206.26.48.100 se corresponde con el nombre de la máquina java.sun.com que resulta más fácil de recordar y significativo. Un mismo nombre puede tener diferentes direcciones IP, en Internet esta correspondencia entre direcciones IP y nombres la gestionan servidores de nombres que se encargan de traducir los nombres fáciles de recordar a sus direcciones de 32 bits. Acompañando el nombre del dominio puede aparecer precedido de dos puntos un número de puerto. Los números de puerto se utilizan para identificar de forma única los distintos procesos dentro de una máquina en la red. A cada servidor se le asigna un número de puerto conocido. El rango del número de puerto es 0-65535, ya que se trata de números de 16 bits, los números de puerto del rango 0-1023 se encuentran restringidos ya que se utilizan para servicios conocidos como pueden ser HTTP 270 © Grupo EIDOS 14. Applets de Java: introducción a los Applets (HyperText Transfer Protocol) en el puerto 80, FTP (File Transfer Protocol) en el puerto 21, telnet en el puerto 23 y otros servicios del sistema. Cada dirección IP es un conjunto de puertos a los que los clientes se pueden conectar a través de Internet. La última parte de la URL indica dónde se encuentra localizado el recurso dentro del servidor, es decir, es el camino del fichero demandado en la URL. El servidor verificará esta parte de la URL para identificar que página HTML, imagen, directorio o aplicación se está demandando. Cada elemento que forma el camino del recurso se para con una barra (/). Clientes y Servidores Web La arquitectura Cliente/Servidor podemos verla como una forma de abordar los problemas. Dentro de la red el sistema cliente es la estación de trabajo y el servidor es una versión de mayor tamaño capaz de almacenar gran cantidad de datos y ejecutar grandes aplicaciones. Cuando un ordenador se comunica con otro a través de Internet, en dicha comunicación se usa el modelo cliente/servidor. Los recursos de Internet los proporcionan unas máquinas denominadas servidores, que dan el servicio a las peticiones de los clientes. Los clientes son los ordenadores que acceden a estos recursos a través de aplicaciones cliente. Básicamente, un servidor atiende y trata peticiones de varios clientes. En el entorno de Internet la arquitectura cliente/servidor es bastante común y se encuentra muy extendida. Normalmente un servidor se ejecuta un una máquina diferente de la del cliente, aunque no siempre es así. La interacción entre cliente y servidor normalmente comienza en el lado del cliente. El cliente realiza una petición de un objeto o transacción del servidor, el servidor deberá tratar la petición o denegarla. Si la petición es tratada, el objeto se le enviará al cliente. En el entorno de la World Wide Web los servidores se denominan servidores Web y los clientes se denominan navegadores Web. Un servidor Web es el encargado de publicar o poner a disposición de una serie de clientes unos recursos. Estos recursos pueden variar desde una página HTML a un script CGI para la consulta de una tabla en una base de datos, o bien desde una página HTML con diferentes applets a una página ASP que realiza el mantenimiento de una tabla, o incluso un servicio de correo. A la hora de pensar en un servidor Web no debemos limitar sus funciones a la del servicio Web propiamente dicho, es decir, a la publicación de páginas HTML. Los navegadores Web realizan peticiones de documentos que se encuentran en servidores Web, permitiendo así visualizar los documentos de la World Wide Web. Algunos de los clientes más populares son Netscape Navigator y Microsoft Internet Explorer. El proceso de visualización de un documento en la Web comienza cuando el navegador Web envía una petición al servidor Web. El navegador Web envía información, sobre sí mismo y sobre el fichero que está demandando, al servidor Web en cabeceras de petición del protocolo HTTP. El servidor Web recibe y consulta las cabeceras HTTP para obtener toda información relevante, tal como el nombre del fichero que se indica en la petición, y envía el fichero con las cabeceras de 271 Programación en Java © Grupo EIDOS respuesta del protocolo HTTP. Entonces el navegador Web utiliza las cabeceras de respuesta HTTP para determinar como mostrar o tratar el fichero que le envía el servidor Web. HTML HTML es la abreviatura de HyperText Markup Language , y es el lenguaje que todos los programas navegadores usan para presentar información en la World Wide Web (WWW). Este es un lenguaje muy sencillo que se basa en el uso de etiquetas, consistentes en un texto ASCII encerrado dentro de un par de paréntesis angulares(<..>). El texto incluido dentro de los paréntesis nos dará una explicación de la utilidad de la etiqueta. Así por ejemplo la etiqueta <TABLE> nos permitirá definir una tabla. Las etiquetas podrán incluir una serie de atributos o parámetros, en su mayoría opcionales, que nos permitirán definir diferentes posibilidades o características de la misma. Estos atributos quedarán definidos por su nombre (que será explicativo de su utilidad) y el valor que toman separados por un signo de igual. En el caso de que el valor que tome el atributo tenga más de una palabra deberá expresarse entre comillas, en caso contrario no será necesario. Así por ejemplo la etiqueta <TABLE border="2"> nos permitirá definir una tabla con borde de tamaño 2. Entre otras cosas, el manejo de estas etiquetas nos permitirá: • Definir la estructura lógica del documento HTML. • Aplicar distintos estilos al texto (negrita, cursiva, ...). • La inclusión de hiperenlaces, que nos permitirán acceder a otros documentos relacionados con el actual. • La inclusión de imágenes y ficheros multimedia (gráficos, vídeo, audio). HTTP Como ya se ha comentado anteriormente, el protocolo HTTP (HiperText Transfer Protocol) es el utilizado para la transferencia de hipertexto entre el servidor Web y el navegador. Introducción a los applets de Java Una vez comentados una serie de conceptos relacionados con los applets de Java, vamos a pasar a tratar los applets propiamente dichos. Como ya sabrá el lector a estas alturas, los programas Java comprenden dos grupos principales: applets y aplicaciones. Un applet es un programa dinámico e interactivo que se ejecuta dentro de una página Web, desplegada por un navegador con capacidad para Java como puede ser los navegadores Navigator de Netscape o el Internet Explorer de Microsoft, por lo tanto los applets para ejecutarse dependen de un navegador Web que soporte para Java. Los applets es uno de los principales motivos que han hecho al lenguaje Java tan popular. 272 © Grupo EIDOS 14. Applets de Java: introducción a los Applets Un applet es un objeto dentro de la jerarquía de clases de Java, es una subclase de la clase Panel, y se encuentra en el paquete java.applet, este paquete además de contener la clase Applet contiene tres interfaces AudioClip, AppletStub y AppletContext. Para poder incluir un applet dentro de una página Web, se deben seguir los siguientes pasos: después de crear una clase con el applet y compilarla dando como resultado un archivo de clase (.CLASS), se debe crear una página Web que contenga al applet, para ello se debe hacer uso del lenguaje HTML (HyperText Markup Language) que dispone de una serie de etiquetas especiales para incluir applets en las páginas Web. Estas etiquetas las comentaremos en el apartado correspondiente dentro de este mismo capítulo. Después de tener un applet y la página Web que hace referencia al mismo, se debe hacer disponible para la World Wide Web. Los applets Java se pueden colocar en un servidor Web de la misma manera que los archivos HTML, no se necesita software de servidor especial para hacer los applets disponibles en la Web, todo lo que se necesita es ubicar el fichero HTML y los archivos de clase compilados del applet en el servidor Web, también se deberán incluir junto al fichero .class del applet, todos los ficheros de clase que utilice el applet y que no se encuentren dentro las clases estándar de Java contenidas en de la Máquina Virtual. Cuando un navegador Web se dispone a visualizar una página Web que contiene un applet o varios, el navegador carga además de la clase, en la que se encuentra el applet, todas las clases que utilice el mismo, y se ejecuta en la máquina local del cliente; a partir de ahora cada vez que se haga referencia a la "máquina local", se está hablando de la máquina dónde se ejecuta el navegador y carga el applet a través de Internet. Los applets se descargan utilizando el mismo protocolo que se utiliza para transferir las páginas Web, es decir el protocolo de transferencia de hipertexto HTTP (HyperText Transfer Protocol). Cabe destacar que los applets poseen una serie de capacidades que las aplicaciones no poseen, esto es debido a que los applets tienen su propia clase llamada Applet, y disponen de tres interfaces AppletStub, AppletContext y AudioClip, que les ofrecen apoyo para las siguientes tareas: • Los applets pueden reproducir sonidos. • Los applets pueden realizar la carga de documentos HTML, ya que se pueden comunicar con el navegador Web gracias al interfaz AppletContext. • Pueden invocar métodos públicos de otros applets en la misma página Web, es decir, varios applets de pueden comunicar entre sí de forma muy sencilla. • Se ejecutan dentro de una página Web cargada por un navegador. Todas estas características de los que se han esbozado en estos puntos, se comentarán y tratarán de forma más detenida en los distintos apartados de este tema. Antes de seguir hablando de los applets como si fuera algo abstracto o difícil de realizar, vamos a comentar una serie de pasos básicos para crear nuestro primer applet utilizando la herramienta de Microsoft Visual J++ 6. Al arrancar Visual J++ en la pestaña de Nuevo seleccionamos la carpeta Páginas Web y dentro de esta opción elegimos Subprograma en HTML, esta es una curiosa forma que se ha elegido para nombrar a los applets. Algunos autores hacen referencia a los applets mediante los términos de miniaplicaciones o subprogramas. 273 Programación en Java © Grupo EIDOS Le damos un nombre a nuestro proyecto y pulsamos Abrir, en este momento Visual J++ generará un fichero fuente de Java llamado Applet1.java, que contiene la estructura de un applet de ejemplo, y además genera un fichero HTML llamado Page1.htm, que va a ser la página Web que haga referencia al applet antes mencionado. Para empezar desde cero y evitar confusiones, vamos a eliminar todo el código que aparece en el fichero fuente del applet y vamos a comenzar a escribir nosotros mismos nuestro primer applet. En un primer lugar debemos importar el paquete que contiene a la clase Applet, ya hemos comentado en este mismo apartado que un applet es un tipo de panel especial que se va a mostrar en el entorno de un navegador Web dentro de una página HTML. import java.applet.*; Código fuente 173 A continuación debemos declarar nuestra clase. Debido a que vamos a crear un applet, nuestra clase heredará de la clase Applet. Antes de seguir cambiaremos el nombre de nuestro fichero fuente para cambiar el nombre de la clase del applet, en mi caso lo he llamado AppletSencillo, y la declaración de la clase se muestra en el Código fuente 174. public class AppletSencillo extends Applet{ } Código fuente 174 Nuestro applet no va hacer mucho, simplemente vamos a mostrar un mensaje en la superficie del applet, para ello vamos a sobrescribir el método paint() de la clase Applet. Más adelante en este capítulo comentaremos el ciclo de vida de los applets y que implica sobrescribir el método paint(), también trataremos la clase Graphics y veremos como mostrar gráficos en un applet, de momento sólo debemos saber que el parámetro de la clase Graphics que recibe el método paint() representa a la superficie del applet. Para mostrar el mensaje "Hola Mundo", implementamos el método paint() como indica el Código fuente 175. public void paint(java.awt.Graphics g){ g.drawString("Hola Mundo",10,10); } Código fuente 175 De esta forma el código completo de nuestra clase es el Código fuente 176. import java.applet.*; public class AppletSencillo extends Applet{ public void paint(java.awt.Graphics g){ 274 © Grupo EIDOS 14. Applets de Java: introducción a los Applets g.drawString("Hola Mundo",10,10); } } Código fuente 176 Como se puede ver en el código, un applet no presenta ningún método main(), como ocurría con las aplicaciones. En la aplicaciones era necesario porque era el método de arranque de la clase y era dónde se instanciaba un objeto la clase de la aplicación. Sin embargo esto no pasa en los applets, como se puede ver nosotros no creamos ninguna instancia de la clase de nuestro applet, ahora bien, alguien debe hacerlo, este alguien es el navegador Web. El navegador Web crea una instancia de la clase de nuestro applet a través de la máquina virtual (MV) que contiene, cuando encuentra la etiqueta HTML que hace referencia al applet crea de forma automática una instancia del mismo. Ahora compilamos nuestro applet. Al compilar el applet se genera el fichero .class correspondiente, al igual que ocurría con las aplicaciones. Una vez compilado y generado nuestro applet tenemos que ver la forma de ejecutarlo, pero antes de ejecutarlo debemos modificar la página Web Page1.htm para que haga referencia a nuestro applet, ya que nosotros hemos cambiado el nombre del mismo, y la página Web sigue haciendo referencia a la clase Applet1. Para modificar la página Web hacemos doble click sobre ella en el Explorador de proyectos y en ese momento se abre la página Web en el área que teníamos reservada para el editor de código. El aspecto que ofrece el entorno de desarrollo se puede ver en la Figura 117. Figura 117 El editor reservado para la página Web presenta tres vistas diferente de la página HTML: Diseño, Código y Vista rápida. En la parte de diseño podemos insertar elementos HTML o modificar los existentes, de forma similar a como lo podemos hacer con otros editores HTML más avanzados y sofisticados como puede ser Microsoft FrontPage, en esta vista podemos ver el aspecto que tiene el applet dentro de la página HTML, en este caso no estamos viendo nuestro applet sino el que había creado de ejemplo Visual J++. 275 Programación en Java © Grupo EIDOS Si seleccionamos el área que representa al applet y pulsamos con el botón derecho del ratón podemos seleccionar la opción de menú Propiedades. Al seleccionar esta opción del menú contextual podemos modificar las propiedades del applet desde una ventana de propiedades que aparece en el entorno y que se suele encontrar debajo del explorador de proyectos. En la ventana de Propiedades vamos a modificar la propiedad code del applet, esta propiedad debe indicar el nombre de la clase del applet que queremos mostrar en la página Web. Por lo tanto el valor de esta propiedad va a ser AppletSencillo, también vamos a modificar las propiedades width y height que indican las dimensiones del applet, yo he indicado el valor 100 para ambas propiedades. También podemos modificar las dimensiones del applet seleccionando y arrastrando el ratón, modificando de esta forma su tamaño. Una vez realizadas estas modificaciones sobre las propiedades, ya podemos ver nuestro applet en el lugar en el que aparecía el applet de ejemplo creado por Visual J++. Como se puede ver en la Figura 118, la vista Diseño muestra el applet en un estado similar al que presentaría en ejecución, y como era deseado y esperado muestra el mensaje Hola Mundo. Figura 118 En la vista Código vemos el código HTML de la página Web (Figura 119), aunque el applet lo seguimos viendo en modo de ejecución, para ver el código HTML que se corresponde con la inclusión del applet en la página Web seleccionamos el applet, pulsamos con el botón derecho del ratón y seleccionamos la opción de menú Ver siempre como texto, de esta forma podemos ver los valores de la etiqueta <APPLET>. Esta etiqueta la discutiremos en el apartado dedicado a los applets y el código HTML. Figura 119 276 © Grupo EIDOS 14. Applets de Java: introducción a los Applets La última vista, Vista rápida, se muestra la página Web de la forma en la que se mostraría en un navegador que soporte Java, esta vista si que muestra la verdadera ejecución del applet, no como ocurría en la vista de diseño, aunque al ser un applet tan sencillo en ambas vistas ofrece el mismo aspecto. Por lo tanto si en la vista rápida vemos nuestro applet de forma correcta, es como si ya lo hubiésemos probado en un navegador, pero de todas formas recomiendo ejecutar el applet directamente sobre un navegador Web. Para ejecutar el applet pulsamos directamente el botón representado por una tecla "play" en el entorno de desarrollo, es decir, al igual que ejecutábamos una aplicación. En ese momento se ejecuta el applet, pero no mediante un navegador Web, sino mediante una herramienta del entorno Visual J++ denominada Visor de subprograma. El aspecto de este visor se puede ver en la Figura 120, y la función del mismo es el de desplegar applets, además ofrece un menú que no permite interactuar con el applet a través de una serie de opciones como: Recargar, Iniciar, etc. Figura 120 Pero si queremos ejecutar el applet desde el entorno de Visual J++ 6 y que además arranque de forma automática el navegador Web para que se ejecute el applet en su interior, debemos acudir a las propiedades del proyecto y en la pestaña Inicio, en la lista desplegable Cuando se ejecute el proyecto, cargar:, seleccionamos la página HTML que hace referencia a nuestro applet. De esta forma cuando ejecutemos el proyecto, se lanzará el navegador Web (Internet Explorer) y cargará la página Web que hace referencia a nuestro applet, y se ejecutará por lo tanto el applet como se puede comprobar en la Figura 121. Esta es la forma más recomendable para probar un applet desde el entorno de desarrollo, pero existe otra forma. Simplemente localizamos en el Explorador de Windows la página Web que hace referencia a nuestro applet y hacemos doble click sobre ella, de esta forma se lanzará el navegador Web y cargará la página en cuestión. Ahora vamos a pasar a construir este mismo applet pero desde el entorno de desarrollo de la herramienta JBuilder 3.5. Para ello creamos un nuevo proyecto utilizando (si queremos) el asistente de proyectos. Una vez creado el proyecto desde el menú de opciones seleccionamos la opción Archivo/ Nuevo y seleccionaremos de la galería de objetos el applet, en ese momento se lanzará el asistente de creación de un nuevo applet. 277 Programación en Java © Grupo EIDOS Figura 121 El primer paso de este asistente, que se puede ver en la Figura 122, permite indicar a que paquete va a pertenecer nuestra clase y el nombre de la misma. En la opción clase base podemos elegir la clase Applet de la que queremos heredar, existen dos clases: Applet del paquete java.applet y JApplet del paquete javax.swing. JApplet es un componente Swing que hereda de la clase java.applet.Applet, es la versión Swing de los applets, la clase javax.swing.JApplet la veremos en próximos capítulos. En el ejemplo nuestro applet se llama AppletEjemplo y hereda de la clase java.applet.Applet. En el primer paso del asistente para applets también podemos indicar si queremos que se generen comentarios, si queremos que se ejecute como una aplicación cuando proceda y si deseamos que se generen todos los métodos del ciclo de vida de los applets, estos métodos los veremos en el apartado dedicado al ciclo de vida de los applets. Figura 122 En la figura 7, se definen los parámetros el applet, en nuestro ejemplo el applet no va a tener ninguno, ya que consiste únicamente en escribir un mensaje en la superficie del applet. 278 © Grupo EIDOS 14. Applets de Java: introducción a los Applets Figura 123 En el último paso se nos permite especificar los parámetros de configuración de applet en la página HTML, es decir, el nombre de la clase, las dimensiones y la alineación. También podemos indicar si queremos o no que se genere de forma automática la página HTML que va a contener nuestro applet, de la página podemos especificar su nombre y título. Figura 124 Al pulsar finalizar se generará el fichero fuente del applet y la página HTML que lo contiene. Ahora accedemos al código fuente y añadimos el método paint() con el mismo código que vimos anteriormente para Visual J++. Una vez compilado el applet podemos pulsar el botón "play" para la ejecución del mismo. 279 Programación en Java © Grupo EIDOS En ese momento aparece la pantalla de configuración del proyecto para ejecutar el applet (Figura 125), esta pantalla que aparece únicamente la primera vez, nos permite asignar la clase principal del applet, al igual que ocurría con las aplicaciones Java, también es posible definir las dimensiones del applet y los parámetros, pero estos datos ya los hemos indicado anteriormente en el asistente para la creación de applets. Figura 125 Una vez indicada la información necesaria para ejecutar el applet JBuilder inicia la ejecución del mismo en una herramienta propia de JBuilder, comparable al appletviewer del JDK o al visor de subprograma del Visual J++. El aspecto del visor de applets de JBuilder se puede comprobar en la Figura 126. Figura 126 280 © Grupo EIDOS 14. Applets de Java: introducción a los Applets Ya hemos visto como crear y ejecutar un applet con Visual J++ y JBuilder, ahora sólo nos queda hacer lo mismo con la herramienta de Sun JDK. Para ello crearemos el fichero fuente de Java que va a contener el código del applet que sería el Código fuente 177. import java.applet.*; public class AppletSencillo extends Applet{ public void paint(java.awt.Graphics g){ g.drawString("Hola Mundo",10,10); } } Código fuente 177 Compilamos el código con la herramienta javac y creamos una página HTML que haga referencia a la clase de nuestro applet. Al hacer doble clic sobre la página, ya podríamos ver el applet en nuestro navegador Web, pero también podemos utilizar la herramienta appletviewer que se ofrece con el JDK. Para ejecutar nuestro applet con el appletviewer únicamente deberemos escribir la instrucción en la línea de comandos, como se muestra en el Código fuente 178. appletviewer pagina.html Código fuente 178 Y el aspecto del appletviewer es el de la Figura 127. Figura 127 Desde el menú Applet del appletviewer podemos invocar los distintos métodos del ciclo de vida de los applets, que veremos en detalle más adelante en este mismo capítulo. Al principio de este apartado comentábamos que para hacer disponibles los applets en la Web, necesitamos de un servidor Web, ahora bien, para probarlos, es decir, en tiempo de desarrollo de los mismos, puede no ser necesario. Así con nuestro sencillo applet lo probaríamos en una máquina local y una vez que hemos comprobado que funciona lo copiaríamos al servidor Web que corresponda. 281 Programación en Java © Grupo EIDOS Este ha pretendido ser el primer contacto con los applets, hemos realizado un applet muy sencillo y lo hemos realizado desde tres herramientas distintas: Microsoft Visual J++ 6, Borland JBuilder 3.5 y el JDK 1.3 de Sun. También hemos visto las diferentes formas que tenemos de ejecutarlo. En los siguientes apartados profundizaremos más en todo lo relacionado con los applets. El ciclo de vida de los applets Los applets podemos decir que presentan un ciclo de vida, es decir, durante su ejecución pasan por diferentes estados. El ciclo de vida de vida de los applets es controlado por cuatro métodos de la clase Applet, estos métodos son init(), start(), stop() y destroy(). Cuando se carga una página Web que contiene un applet, éste pasa por varias etapas durante el tiempo que aparece en pantalla, el applet realiza unas tareas muy diferentes durante cada una de estas etapas. Si queremos que en una etapa del ciclo de vida de un applet se realice una tarea determinada deberemos sobrescribir el método adecuado. No siempre es necesario sobrescribir todos los métodos del ciclo de vida de un applet. A continuación se comenta cuando se ejecutan cada uno de los métodos del ciclo de vida de un applet. El método init() es llamado una única vez por la Máquina Virtual de Java (JVM) del navegador cuando el applet se carga por primera vez, antes de ser mostrado al usuario, es en este método donde se deben inicializar los objetos, construir los interfaces de usuario, etc. El método start() es llamado cada vez que la página Web que contiene el applet es mostrada en la pantalla del usuario. Al método stop() se le llama cada vez que el usuario carga otra página Web, es decir, cuando se abandona la página que contiene al applet. El último método, destroy(), es llamado una única vez, justo antes de ser eliminado el applet, cuando se abandona el navegador. Este método es invocado automáticamente para realizar cualquier limpieza que sea necesaria, por lo general no es necesario sobrescribir este método, sólo será necesario cuando se tengan recursos específicos que necesiten ser liberados. Es durante esta etapa final de salida, cuando la Máquina Virtual de Java completa algunas funciones de recolección de basura para asegurar que los recursos que empleó el applet sean borrados de la memoria y de que el applet sea completamente destruido cuando se salga de él. Como se ha podido observar en el ciclo de vida de un applet los métodos init() y destroy() se lanzarán solamente una vez cada uno, y sin embargo los métodos start() y stop() se lanzarán las veces que sean necesarios, según interactúe el usuario con el navegador. La Figura 128 muestra la relación entre los métodos del ciclo de vida del applet y los acontecimientos que los marcan: 282 © Grupo EIDOS 14. Applets de Java: introducción a los Applets Figura 128 Vamos a realizar un seguimiento a través del ciclo de vida de un applet, para lo cual vamos a apoyarnos en una clase Ventana, que va a ser muy sencilla y cuyo código es el Código fuente 179. import java.awt.*; import java.awt.event.*; public class Ventana extends Frame{ public Ventana(String titulo){ super(titulo); setSize(150,150); show(); addWindowListener(new AdaptadorVentana(this)); } } class AdaptadorVentana extends WindowAdapter{ private Ventana fuente; public AdaptadorVentana(Ventana fuente){ this.fuente=fuente; } public void windowClosing(WindowEvent evento){ fuente.dispose(); } } Código fuente 179 Si algún alumno tiene problemas para entender el código anterior, le remito al capítulo dedicado al interfaz de usuario en Java. La clase Ventana la vamos a utilizar en nuestro applet para crear una ventana cada vez que se ejecuta un método del ciclo de vida de un applet. Para diferenciar las ventanas, a cada una de ellas le pasamos a su constructor el nombre del método del ciclo de vida que las ha llamado. Es decir, cada ventana tendrá un título distinto. El código del applet es el Código fuente 180. import java.applet.*; public class CicloVida extends Applet{ public void init(){ Ventana ventana=new Ventana("init()"); } 283 Programación en Java public void Ventana } public void Ventana } public void Ventana } } © Grupo EIDOS start(){ ventana=new Ventana("start()"); stop(){ ventana=new Ventana("stop()"); destroy(){ ventana=new Ventana("destroy()"); Código fuente 180 En este caso cada clase se encuentra en un fichero fuente, es decir, existen los ficheros Ventana.java y CicloVida.java. Si ejecutamos nuestro applet pulsando play (en cualquiera de los entornos: Visual J++ o JBuilder), como ya vimos en el apartado anterior, o bien con el appletviewer o directamente con te navegador Web deben aparecer dos ventanas, una creada por el método init() y otra por el método start(). Una vez que hemos cargado el applet, si cerramos el navegador aparecen momentáneamente otras dos ventanas, creadas esta vez por el método stop() y destroy(), ya que además de descargar el applet se cierra el navegador. Hasta aquí todo es correcto y sucede tal y como habíamos explicado anteriormente. Un ejemplo de ejecución del applet se puede ver en la Figura 129. Figura 129 Continuamos con la ejecución de nuestro applet. Pero si cargamos una página distinta, además de ejecutarse el método stop() se ejecuta el método destroy(), cosa que en absoluto es correcta, sólo se debería ejecutar el método stop(), ya que el navegador no se ha cerrado. Y si volvemos a la página del applet (pulsando la flecha hacia atrás del navegador) además de ejecutarse el método start() también se ejecuta el método init(), situación que también es incorrecta. Esto no es debido a que nuestro applet esté mal construido, sino que es debido a una implementación distinta dentro de la máquina virtual del navegador Web. En mi caso he detectado este fallo en el navegador Internet Explorer en su versión 5.0. 284 © Grupo EIDOS 14. Applets de Java: introducción a los Applets Si probamos este mismo applet cargando la página que lo contiene desde el navegador Netscape Navigator en su versión 4.5 el applet funciona correctamente, es decir, al cambiar de página se ejecuta únicamente el método stop() y al volver a la página el método start(). Realizo estos comentarios, sólo para las versiones señaladas de los navegadores, que son además los que se han utilizado para las pruebas de los applets que aparecen en este curso. Estos cuatro métodos que componen el ciclo de vida de un applet se ejecutan, como hemos podido comprobar, de forma automática y siguiendo un orden determinado. Otro método que también se ejecuta de forma automática es el método paint(). Este método, que ya hemos comentado someramente en el apartado anterior, tiene la función de pintar o mostrar la apariencia del applet en la pantalla. Este método es otro método de la clase Applet que podemos sobrescribir si lo necesitamos, igual que ocurría con los métodos del ciclo de vida de los applets. Con respecto al ciclo de vida del applet el método paint() se ejecuta por primera vez inmediatamente después del método start() y luego cada vez que se necesita "repintar" la pantalla, es decir, cada vez que se refresca la superficie que comprende al área contenida por el applet. La máquina virtual lanzará el método paint() cada vez que el applet desaparezca total o parcialmente de la pantalla. El método paint() recibe como parámetro un objeto de la clase Graphics, que pertenece al paquete java.awt. La instancia del objeto de la clase Graphics representa la superficie del applet, y lo vamos a utilizar para escribir y dibujar en la superficie del mismo. En el apartado correspondiente veremos como dibujar de forma básica en la superficie de nuestro applet. El método paint() también se ejecuta cuando se llama al método repaint() de la clase Applet. Seguridad en los applets Java incluye características de seguridad para reforzar su empleo en Internet. Un problema de seguridad potencial tiene que ver con los applets de Java, que son código ejecutable que opera en la máquina local del usuario que se conecta a la página Web en la que se encuentran los applets. Java emplea verificación de código y acceso limitado al sistema de archivos para garantizar que el código no dañe nada en la máquina local. Se debe tener claro que el applet reside en el servidor Web, pero cuando se carga la página Web que lo contiene, las clases del applet se transfieren hasta la máquina cliente, que es dónde se inicia la ejecución del applet. En este apartado, como su nombre indica, vamos a comentar los mecanismos de seguridad que ofrece Java a la hora de ejecutar los applets en la máquina cliente o máquina local, y que restricciones de seguridad impone. Existen dos módulos software encargados del análisis de la seguridad y de la imposición de restricciones, estos módulos son el ClassLoader y el Security Manager. El ClassLoader (clase abstracta del paquete java.lang) tiene las siguientes funciones: por un lado colocar las clases que integran cada applet en un espacio de nombres único y estanco, de forma que es imposible para un applet acceder o manipular recursos de otros applets. Por otro lado analiza los bytecodes que componen la representación binaria intermedia del código del applet para asegurar que son conformes a las normas de Java y no realizan operaciones peligrosas, 285 Programación en Java © Grupo EIDOS como conversiones de tipos ilegales, accesos a índices inexistentes de arrays, paso de parámetros incorrectos, etc. Todas estas funciones las lleva a cabo un verificador de código (bytecode verifier). Las comprobaciones que realiza el verificador de código (para más información sobre el proceso de verificación, consultar la siguiente dirección de Internet: http://www.javasoft.com/sfaq/verifier.html) se ven facilitadas por la filosofía de diseño de Java. Así, por ejemplo, Java carece de punteros, por lo que es imposible hacer referencia a una posición de memoria explícita. Además, el intérprete comprueba siempre que el acceso a un array se realiza a través de un índice dentro de un rango válido. Cuando se carga desde un servidor a nuestra máquina local una página Web que contiene algún applet, el código del applet es verificado por el segundo módulo de seguridad para garantizar que el applet sólo emplea llamadas y métodos válidos del sistema. Si el applet pasa la verificación se le permite ser ejecutado, pero a los applets no se les da acceso al sistema de archivos de la máquina local. El objeto responsable de evitar que los applets realicen una operación potencialmente peligrosa es el SecurityManager (gestor de seguridad, clase abstracta del paquete java.lang). Si el SecurityManager determina que la operación está permitida, deja que el programa siga con su ejecución. Cuando una aplicación Java se ejecuta, no hay SecurityManager. Esto es debido a que se espera que cualquier aplicación que el usuario ejecuta es segura para ese usuario. De todas formas podemos escribir e instalar propio SecurityManager para nuestras aplicaciones, por lo tanto en un principio las aplicaciones Java no tiene ningún tipo de restricción. Cada navegador de páginas Web con capacidad para Java tiene un objeto SecurityManager que comprueba si se producen violaciones de las restricciones de seguridad de los applets. Cada vez que una clase Java hace una petición de acceso a un recurso del sistema, la instancia de la clase SecurityManager comprueba la petición realizada, si no se tiene permiso para efectuar el acceso éste se deniega y en caso contrario se le concede y se sigue normalmente con la ejecución del applet. Cuando el SecurityManager detecta una violación crea y lanza un objeto SecurityException y se detiene la ejecución del applet. Normalmente, el constructor de una SecurityException muestra un mensaje de advertencia que indica que se ha producido una excepción de seguridad. En la Figura 130, se puede ver de forma ordenada todo el proceso que se sigue para comprobar que el applet es seguro, desde que el applet se empieza a cargar desde Internet hasta que empieza a ejecutarse en la máquina cliente: Figura 130 286 © Grupo EIDOS 14. Applets de Java: introducción a los Applets La clase SecurityManager posee una serie de métodos para realizar las siguientes acciones que refuerzan su política de seguridad: • Determinar si una petición de conexión, a través de la red, desde una máquina en un puerto específico puede ser aceptada. • Comprobar si un hilo de ejecución puede manipular otro hilo de ejecución diferente. • Comprobar si se puede establecer una conexión vía socket con una máquina remota en un puerto específico. • Impedir la creación de un nuevo objeto de la clase ClassLoader. • Impedir la creación de un nuevo objeto de la clase SecurityManager, ya que podría sobrescribir la política de seguridad existente. • Comprobar que un fichero puede ser borrado. • Comprobar si un programa puede ejecutar otro programa en el sistema local. • Impedir que un programa termine con la ejecución de la Máquina Virtual de Java. • Comprobar si se puede acceder a una librería dinámica. • Comprobar si se puede escuchar en un determinado puerto para esperar peticiones de conexión. • Determinar si un programa puede cargar paquetes Java específicos. • Determinar si un programa puede crear nuevas clases en un paquete Java específico. • Identificar a que propiedades del sistema se puede acceder. • Comprobar si se puede leer un fichero. • Comprobar si se puede escribir datos en un fichero. • Comprobar si un programa puede crear su propia implementación de los sockets para la red. • Establecer si un programa puede crear una ventana de alto nivel. Cualquier ventana que se cree incluirá algún tipo de advertencia visual. Después de explicar los mecanismos de los que dispone Java para verificar que un applet es seguro, se va a pasar a detallar las restricciones de seguridad que poseen los applets (para más información sobre la seguridad de Java se puede consultar la siguiente dirección: http://www.javasoft.com/sfaq/: • Los applets no pueden cargar librerías o definir métodos nativos. Si un applet pudiera definir una llamada a un método nativo le daría acceso a la máquina en la que se ejecuta. • Los applets no pueden detener la ejecución de la Máquina Virtual de Java. • No pueden leer ni escribir en el sistema de archivos de la máquina del navegador. No se pueden abrir ni crear ficheros en la máquina del cliente, ni tampoco crear o leer directorios. Si 287 Programación en Java © Grupo EIDOS se intenta crear o abrir un objeto java.io.File o java.io.FileInputStream o java.io.FileOutputStream en la máquina del cliente dará lugar a una excepción de seguridad. • Un applet no puede hacer conexiones a través de la red, sólo se puede conectar con el servidor desde el que se cargó el applet. • No pueden conectarse a puertos del cliente. • Los applets no pueden actuar como servidores de la red esperando para aceptar conexiones de sistemas remotos. • Los applets que se quieran comunicar entre sí deben estar en la misma página Web, en la misma ventana del navegador, y además deben ser originarios del mismo servidor. • No pueden instanciar un objeto de las clases ClassLoader o SecurityManager. • No pueden acceder o cargar clases de paquetes llamados java que no sean los paquetes estándar del API de Java. • No pueden ejecutar ningún programa en el sistema local del cliente. • No pueden leer todas las propiedades del sistema en el que se cargaron. En particular, no puede leer ninguna de las siguientes propiedades del sistema: user.name (nombre de la cuenta del usuario), user.home (directorio home del usuario), java.home (directorio de instalación de Java), user.dir (directorio de trabajo actual del usuario) y java.class.path (directorio en el que se encuentran las clases de Java). • Los applets tampoco pueden definir ninguna propiedad del sistema. • Las ventanas que abren los applets tienen un aspecto diferente, aparece la advertencia de que se trata de una ventana abierta o creada por un applet. • Los applets no pueden instanciar objetos COM (Component Object Model) o comunicarse con ellos, esto es para preservar la seguridad, ya que los objetos COM son poco seguros y permiten realizar operaciones peligrosas en la máquina local. Los applets se pueden cargar de dos maneras, la forma en que un applet entra en el sistema afecta a lo que se le va a permitir hacer. Si un applet se carga a través de la red, entonces es cargado por el ClassLoader y está sujeto a las restricciones impuestas por el SecurityManager. Pero si un applet reside en el disco local del cliente, y en un directorio que está en el java.class.path (propiedad de la clase System, que indica en que lugar se encuentran las clases de Java) entonces es cargado por el FileLoader del sistema. Las diferencias principales con los anteriores son las siguientes: 288 • Pueden leer y escribir ficheros. • Pueden cargar librerías en la máquina del cliente. • Pueden ejecutar procesos, es decir, pueden ejecutar cualquier programa en la máquina del cliente. • Pueden parar la ejecución de la Máquina Virtual de Java. • No son pasados por el verificador de bytecode. © Grupo EIDOS 14. Applets de Java: introducción a los Applets Esta relajación de la seguridad es debido a que se supone que los applets cargados del sistema local son más fiables que los que se cargan a través de la red de forma anónima. También se relajará la seguridad de los applets en el caso de que éstos se encuentren firmados digitalmente a través de un certificado. El único "agujero" en la seguridad de un applet es que éste puede reservar mucha cantidad de memoria creando continuamente una gran cantidad de objetos, por ejemplo, un applet podría crear un gran número de ventanas agotando el sistema GUI (Graphical User Interface) de la máquina, iniciar la ejecución de muchos hilos de ejecución paralelos o cargar desde la red una gran cantidad de datos, este tipo de ataque se denomina ataque de negación de servicio (denial of service attack). Este ataque consume muchos recursos del sistema y puede disminuir la velocidad de la máquina o de la conexión a la red de forma considerable. Aunque este ataque es molesto no puede causar un daño real al sistema, se considera que este ataque está fuera del modelo de seguridad de Java. Trusted/Untrusted applets Cuando decimos que un applet es trusted (de confianza), este applet podrá saltarse las restricciones de seguridad que vienen impuestas a los applets; pero si es untrusted (no es de confianza) tendrá que ajustarse a las restricciones. Un applet de Java se ejecuta dentro de un entorno ofrecido por la Máquina Virtual que evita que el applet realice acciones sobre nuestro sistema que pueden ser potencialmente peligrosas desde el punto de vista de la seguridad. La Máquina Virtual de Java deberá discernir si debe permitir que el applet se salte las restricciones de seguridad o que por el contrario las cumpla escrupulosamente, es decir, debe distinguir si un applet es trusted o untrusted. Por defecto todos applets que cargamos a través de Internet son untrusted, por lo tanto se deben ceñir a las restricciones de seguridad impuestas por la Máquina Virtual. Para que un applet sea trusted debe ir firmado digitalmente o bien las clases del applet deben estar situadas en el CLASSPATH. De esta forma, si nosotros hemos creado nuestro applet, y nuestra variable de entorno CLASSPATH tiene el valor c:\Java\clases deberemos situar los ficheros de clase del applet en el directorio c:\Java\clases. Esto sólo es recomendable para applets que hayamos desarrollado nosotros o alguna persona de confianza. Diferencias entre applets y aplicaciones A continuación se comentan las diferencias entre las aplicaciones y los applets de Java. Las aplicaciones son programas individuales que se ejecutan utilizando sólo el intérprete de Java, los applets sin embargo se ejecutan desde un navegador de páginas Web, o desde un visor de applets. Una referencia a un applet se introduce en una página Web empleando una etiqueta HTML especial. Cuando un navegador carga la página, carga el applet desde el servidor Web y lo ejecuta en el sistema local. Ya que los applets se ejecutan dentro de un navegador, tienen la ventaja de la estructura que éste les ofrece: una ventana, un contexto de manejo de eventos y gráficos y el interfaz de usuario que la rodea. 289 Programación en Java © Grupo EIDOS Sin embargo, todas estas ventajas que tienen los applets sobre las aplicaciones, se ven ensombrecidas por las restricciones impuestas a los applets. Ya que los applets pueden desplegarse desde cualquier parte y ejecutarse en un sistema cliente, las restricciones son necesarias para prevenir que un applet cause daños al sistema o rupturas de seguridad. Sin estas restricciones los applets podrían escribirse para contener virus, gusanos, etc.… y podrían propagarse por toda la red en cuestión de horas. Sin embargo las aplicaciones de forma explícita no ofrecen estas restricciones de seguridad. La etiqueta applet En este apartado vamos a comentar las características que ofrece el lenguaje HTML para la integración de los applets dentro de páginas Web. Puesto que los applets están diseñados para usarse en conjunción con la WWW (World Wide Web), es necesario contar con una manera estándar de llamar a los applets dentro de un navegador Web. Esto lo permiten un juego de etiquetas HTML (Hypertext Markup Language) que se creó para especificar toda la información necesaria para ejecutar un applet. Así, el código HTML puede emplearse para hacer lo siguiente: • Especificar el directorio del applet. • Indicar la ubicación del código que emplea el applet. • Especificar el tamaño en pantalla del applet. • Ofrecer alternativas para los navegadores que no tengan capacidad para Java. • Pasar parámetros al applet. Después de crear el applet y escribir su código, debe añadirse a una página HTML para poder ejecutarse dentro de un navegador. Para ello se debe utilizar la etiqueta <APPLET> del lenguaje HTML. La etiqueta <APPLET> posee los siguientes atributos que a continuación se muestran en la Figura 131, los que son opcionales se encuentran encerrados entre corchetes. Figura 131 290 © Grupo EIDOS 14. Applets de Java: introducción a los Applets En su forma más elemental, utilizando sólo los atributos CODE, WIDTH y HEIGHT, la etiqueta <APPLET> crea un espacio del tamaño deseado en el que se cargará y ejecutará el applet. A continuación se pasa a describir los atributos de la etiqueta <APPLET>. Codebase Este atributo opcional especifica la dirección URL base del applet, es decir, el directorio o carpeta que contiene el código del applet. Si este atributo no es especificado se utiliza el URL del documento HTML. Code Este atributo requerido ofrece el nombre del fichero .CLASS que contiene el applet. Si no se utiliza el atributo CODEBASE, el archivo se busca en el mismo directorio del archivo HTML que referencia al applet. Alt Este atributo opcional especifica el texto que debe ser mostrado en pantalla en el caso de que el navegador utilizado para ver las páginas HTML no tenga capacidad para Java. Name Esta atributo de carácter opcional especifica un nombre para el applet. Esto hace posible que los applets de una misma página Web se puedan comunicar entre sí, haciendo referencia a su nombre. Width, Height Estos atributos requeridos definen el ancho y alto (en pixels) del applet, sin tener en cuenta los diálogos o ventanas que pueda mostrar el applet. Align Este atributo de naturaleza opcional especifica la alineación del applet. Los posibles valores de este atributo son los que se enumeran a continuación: • TEXTTOP: alinea la parte superior del applet con la parte superior del texto más alto en la línea. • TOP: alinea el applet con el elemento más alto en la línea, el cual puede ser otro applet, una imagen, o la parte superior del texto. • ABSMIDDLE: alinea el centro del applet con el centro del elemento más grande de la línea. • MIDDLE: alinea el centro del applet con el centro de la línea base del texto. 291 Programación en Java © Grupo EIDOS • BASELINE: alinea la parte inferior del applet con la línea base del texto. • ABSBOTTON: alinea la parte inferior del applet con el elemento más bajo en la línea. Vspace, Hspace Estos atributos opcionales especifican el número de pixels que debe haber encima y debajo del applet (VSPACE) y a cada lado del applet (HSPACE), es decir, los márgenes del applet. Fuera ya de los atributos de la etiqueta <APPLET> pueden aparecer otras etiquetas que se comentan a continuación: <PARAM> Esta etiqueta permite especificar parámetros de un applet. Los applets leen los valores de los parámetros con el método getParameter() de la clase Applet. Los parámetros para los applets son como los argumentos de la línea de comandos para las aplicaciones. Permiten que el usuario pueda personalizar algunas de las operaciones o características del applet. Al definir parámetros se aumenta la flexibilidad del applet, el applet podrá ejecutarse en diferentes situaciones sin necesidad de modificar su código y volver a compilarlo. Al definir un parámetro para un applet mediante la etiqueta <PARAM> se debe tener en cuenta que es lo que se va a permitir al usuario configurar del applet, como se debe llamar el parámetro, que tipo de valor va a tener el parámetro y cual es su valor por defecto. Además de ofrecer parámetros al usuario para que pueda configurar el applet, se le debe indicar como se pueden utilizar estos parámetros. Por lo tanto se debe implementar el método getParameterInfo() de la clase Applet para que devuelva toda la información y ayuda referente a los parámetros del applet. Los navegadores pueden utilizar esta información para ayudar a los usuarios e indicar que valores puede dar a los parámetros. Etiquetas HTML alternativas Si el navegador que carga la página en la que se encuentra el applet no reconoce la etiqueta <APPLET> al no tener capacidad para Java, entonces utiliza estas etiquetas alternativas cuando muestre el documento HTML. Los navegadores con capacidad para Java ignoran todas estas etiquetas alternativas. 292 Applets de Java: utilizando los applets Interacción de los applets con el navegador web Cuando realizamos la introducción a los applets, en el capítulo anterior, comentamos que los applets pueden realizar una serie de operaciones apoyándose en el navegador Web. En este apartado vamos a comentar con ejemplos una serie de características que ofrecen los applets y que en cierta medida son exclusivas, y por lo tanto no las presentan las aplicaciones. Lo primero que vamos a comentar acerca de las capacidades de los applets, es la posibilidad que tienen éstos de comunicarse entre sí en una misma página Web. Para realizar una comunicación entre diferentes applets de deben tener en cuenta dos restricciones de seguridad: • Los applets deben estar en la misma página Web, en la misma ventana del navegador. • Los applets deben ser originarios del mismo servidor. Un applet para comunicarse con otro puede hacerlo refiriéndose al nombre del applet con el que se quiere comunicar, para ello se usa el método getApplet() del interfaz AppletContext. Este interfaz se corresponde con el entorno o contexto en el que se encuentra un applet, es decir, el documento que contiene al applet y los otros applets contenidos en el mismo documento. El contexto del applet se obtiene utilizando el método getAppletContext() de la clase Applet, este método devuelve un objeto que representa el contexto en el que se encuentra el applet. Programación en Java © Grupo EIDOS Los métodos del interfaz AppletContext pueden ser usados para obtener información relativa al entorno del applet. El método getApplet(), a partir de un nombre que se le pasa como parámetro, nos devuelve una instancia de la clase Applet que representa al applet que posee el nombre que se le ha pasado como parámetro a getApplet() y que se encuentra dentro del entorno correspondiente al AppletContext. Si el applet no existe nos devolverá un nulo. También se puede utilizar otro método del interfaz AppletContext, llamado getApplets(), que devolverá una enumeración (objeto de tipo java.util.Enumeration) de todos los applets existentes en el documento representado por el correspondiente AppletContext. Para darle un nombre a cada applet se debe utilizar dentro del documento HTML el atributo name de la etiqueta <APPLET>, así por ejemplo si queremos identificar con el nombre pepe a un applet representado por la clase Ejemplo escribiríamos lo que vemos en el Código fuente 181. <applet code="Ejemplo.class" width="450" height="200" name="pepe"> Código fuente 181 Vamos a ver todo esto mediante un ejemplo sencillo que va a consistir en un par de applets, representados por las clases Remitente y Destinatario. Estos dos applets se van a enviar un mensaje entre sí. La clase Destinatario va a facilitar un método público llamado enviarMensaje() que permite a la clase Remitente enviar un mensaje. El código de la clase Destinatario, es el que muestra el Código fuente 182. import java.applet.*; import java.awt.*; public class Destinatario extends Applet{ private TextField mensaje; public void init(){ mensaje=new TextField(20); add(mensaje); } public void enviarMensaje(String men){ mensaje.setText(men); } } Código fuente 182 Como se puede ver el método enviarMensaje() permite asignar texto a la caja de texto del applet que actúa como destinatario. El código es bastante sencillo y no ofrece ninguna complicación. Ahora vamos a ver el segundo applet, el que actúa de remitente. Este applet si que va a hacer uso del interfaz AppletContext. Este applet ofrece una caja de texto y un botón, en la caja de texto escribiremos el mensaje que queremos que aparezca en la caja de texto del applet de destino, y al pulsar el botón invocaremos el método enviarMensaje() de la clase Destinatario. El código de la clase Remitente aparece en el Código fuente 183. 294 © Grupo EIDOS 15. Applets de Java: utilizando los applets import java.applet.*; import java.awt.*; import java.awt.event.*; public class Remitente extends Applet implements ActionListener{ private Button boton; private TextField mensaje; public void init(){ boton=new Button("Enviar mensaje"); mensaje=new TextField(20); add(mensaje); add(boton); boton.addActionListener(this); } public void actionPerformed(ActionEvent evento){ //se obtiene el contexto del applet AppletContext contexto=getAppletContext(); //se obtiene la referencia al otro applet a través de su nombre Applet destinatario=contexto.getApplet("destino"); //se invoca el método que nos interesa del applet Destinatario ((Destinatario)destinatario).enviarMensaje(mensaje.getText()); } } Código fuente 183 Especial atención merece el método actionPerformed(), que se ejecutará al pulsar el botón de envío de mensaje del applet. En este método se obtiene primero el contexto del applet, es decir un objeto AppletContext. A continuación se obtiene una referencia al applet que nos interesa, en este caso el applet se llama destino. Y por último se invoca el método deseado del applet, para poder invocarlo debemos realizar un casting (conversión de clases), ya que la clase Applet no dispone del método obtenerMensaje(), este método pertenece a nuestra clase Destinatario. En el código HTML de la página Web que contiene a los dos applets debemos asignar a la propiedad name de la etiqueta APPLET, que hace referencia al applet Destinatario, el valor destino, ya que es la cadena que le pasamos por parámetro al método getApplet(). El código HTML se muestra en el Código fuente 184. <HTML> <HEAD> <META NAME="GENERATOR" Content="Microsoft Visual Studio"> <META HTTP-EQUIV="Content-Type" content="text/html"> <TITLE>Document Title</TITLE> </HEAD> <BODY> <APPLET code="Destinatario" width="250" height="30" name="destino"> </APPLET> <hr> <APPLET code="Remitente" width="300" height="130"> </APPLET> </BODY> </HTML> Código fuente 184 En la Figura 132 se puede ver un ejemplo de la ejecución de estos dos applets cooperantes entre sí. 295 Programación en Java © Grupo EIDOS Figura 132 Antes habíamos comentado que el interfaz AppletContext ofrece dos métodos para tener referencias a los applets dentro de una misma página Web, los métodos getApplet() y getApplets(). En el ejemplo anterior hemos visto un ejemplo que utiliza el método getApplet(), ahora vamos a ver un nuevo ejemplo que utiliza el método getApplets(). Vamos a crear un applet que muestre el nombre de las clases de todos los applets que se encuentren en la página Web actual, evidentemente este applet también debe estar incluido en la página. Para mostrar el nombre de las clases, nuestro applet va a utilizar un objeto de la clase TextArea. Al pulsar el botón que ofrece el applet se procederá a obtener las referencias a los applets, para así poder mostrar el nombre de la clase de cada uno de ellos. El código de este applet se indica en el Código fuente 185. import java.applet.*; import java.awt.*; import java.awt.event.*; import java.util.*; public class ListaApplets extends Applet implements ActionListener{ private TextArea listado; private Button boton; public void init(){ setLayout(new BorderLayout()); listado=new TextArea(5,30); boton=new Button("Listar Applets"); add("North",listado); add("South",boton); boton.addActionListener(this); } public void actionPerformed(ActionEvent evento){ listarApplets(); } public void listarApplets(){ AppletContext contexto=getAppletContext(); String nombreClase=""; listado.setText(""); //obtenemos la referencia a todos los applets de la página Enumeration applets=contexto.getApplets(); //recorremos la lista de applets while (applets.hasMoreElements()){ 296 © Grupo EIDOS 15. Applets de Java: utilizando los applets //obtenemos la referencia al applet Object applet=applets.nextElement(); //recuperamos el nombre de la clase del applet nombreClase=applet.getClass().getName(); //añadimos el nombre de la clase al área de texto listado.append(nombreClase+"\n"); } } } Código fuente 185 Como se puede observar se importa el paquete java.util, este paquete es necesario para poder utilizar la clase Enumeration. Esta clase la utilizamos al ejecutar el método getApplets() del interfaz AppletContext. El método getApplets() nos devuelve un objeto de la clase Enumeration que contiene todos los applets que existen en la página Web actual. La clase Enumeration es una clase de utilidad que posee únicamente dos métodos hasMoreElements() y nextElement(). El primero de ellos devuelve un valor booleano y se utiliza para comprobar si existen más elementos en la enumeración, mientras que el segundo de los métodos devuelve el siguiente elemento de la enumeración. El método nextElement() devuelve siempre un objeto de la clase Object, ya que un objeto de la clase Enumeration puede contener cualquier tipo de objeto. Si situamos este nuevo applet en la misma página Web que contenía los dos anteriores y lo ejecutamos mostrará el aspecto de la Figura 133. Figura 133 Otra posibilidad que nos ofrecen los applets es la de cargar páginas en el navegador, es decir, podemos indicar al navegador que muestre una página determinada. Para ello también nos vamos a apoyar en el interfaz AppletContext, este interfaz ofrece el método showDocument() para realizar esta tarea. Este método recibe como parámetro un objeto de la clase URL. La clase URL representa una URL (Uniform Resource Locator) y se encuentra dentro del paquete java.net. Este paquete contiene una serie de clases especializadas en la utilización de recursos y comunicaciones en Internet. A continuación vamos a realizar un applet que muestre una ventana en la que podemos indicar la URL que deseamos cargar en el navegador. Vamos a crear, además de la clase del applet, una clase llamada VentanaURL que va a representar la ventana que muestra el applet, antes de seguir con la exposición de este ejemplo vamos a ver el código fuente completo en el Código fuente 186. 297 Programación en Java © Grupo EIDOS import java.applet.*; import java.awt.*; import java.awt.event.*; import java.net.*; public class MuestraDocumento extends Applet{ private VentanaURL ventana; public void init(){ ventana=new VentanaURL(getAppletContext()); } } class VentanaURL extends Frame implements ActionListener{ //caja de texto que contiene la URL private TextField textoURL; private Button boton; private AppletContext contexto; //el constructor tiene el parámetro contexto para que le pasemos //el contexto del applet public VentanaURL(AppletContext contexto){ super("Mostrar Documento"); this.contexto=contexto; boton=new Button("Muestra URL"); textoURL=new TextField(20); setLayout(new GridLayout(1,2,5,5)); add(textoURL); add(boton); pack(); show(); boton.addActionListener(this); addWindowListener(new AdaptadorVentana()); } public void actionPerformed(ActionEvent evento){ String cadenaURL=textoURL.getText(); URL url=null; //se crea la URL, y es necesario atrapar una excepcion //que lanza su constructor try{ url=new URL(cadenaURL); }catch(MalformedURLException ex){ textoURL.setText("Error en la URL"); } //se muestra la URL en el navegador if (url!=null) contexto.showDocument(url); } class AdaptadorVentana extends WindowAdapter{ public void windowClosing(WindowEvent evento){ dispose(); } } } Código fuente 186 Como se puede ver la clase MuestraDocumento no hace mucho, el applet simplemente crea en su método init() una instancia de la clase VentanaURL, a la que le pasa por parámetro el contexto del applet, ya que la clase que va a realizar la operación de mostrar la URL va a ser la clase VentanaURL, por lo tanto vamos a pasar a comentar la clase VentanaURL. En el constructor de la clase VentanaURL creamos el interfaz de usuario, registramos los oyentes necesarios para la pulsación del botón y para el cierre de la ventana. Además se inicializa el atributo contexto con el contexto que se ha obtenido en el applet. 298 © Grupo EIDOS 15. Applets de Java: utilizando los applets Al pulsar el botón se procede a recuperar la URL de la caja de texto y a continuación se muestra en el navegador. Es necesario crear una instancia de la clase URL, ya que el método showDocument() recibe un objeto de esta clase como parámetro. Al crear la URL se debe atrapar la excepción que lanza su constructor, esta excepción es de la clase MalformedURLException, esta clase al igual que la clase URL se encuentra en el paquete java.net. El método showDocument() lo lanzamos sobre el atributo contexto, que como ya hemos comentado es el contexto que nos proporciona el applet. Si probamos este applet con Internet Explorer 5, volvemos a tener el problema de la ejecución del método destroy() al abandonar la página Web, este problema ya lo comentábamos en el capítulo anterior en el apartado dedicado al ciclo de vida de los applets. Como hemos dicho con IE 5 se ejecuta el método destroy() por lo tanto perdemos el contexto del applet y no podemos volver a cargar otra URL, es decir, el applet funciona sólo la primera vez. Sin embargo con Netscape Navigator 4.5 el funcionamiento es correcto y podemos cargar tantas URLs como queramos. Este applet, al consistir únicamente en una ventana, podemos insertarlo en la página Web con dimensiones cero. En la Figura 134 se puede ver un ejemplo de la ejecución de este applet. Figura 134 Otra característica que ofrecen los applets y que se encuentra íntimamente relaciona con el navegador Web es la posibilidad de mostrar mensajes en la línea de estado del navegador. Para esto se vuelve a utilizar un método del interfaz AppletContext, en este caso showStatus(). El método showStatus() recibe como parámetro la cadena de texto que queremos mostrar en la línea de estado del navegador Web. Antes de continuar se debe aclarar que la línea de estado del navegador Web se encuentra en la zona inferior del mismo y es dónde se indica en que estado se encuentra el documento cargado actualmente. Para ilustrar la utilización de este nuevo método vamos a realizar un applet que consiste en una caja de texto, en la que introduciremos el texto que queremos aparezca en la línea de estado del navegador, y un botón con el que indicaremos que queremos asignar el texto correspondiente. El código de este sencillo applet es el Código fuente 187. 299 Programación en Java © Grupo EIDOS import java.applet.*; import java.awt.*; import java.awt.event.*; public class LineaEstado extends Applet implements ActionListener{ private Button boton; private TextField texto; public void init(){ boton=new Button("Muestra Estado"); texto=new TextField(20); setLayout(new GridLayout(1,2,5,5)); add(texto); add(boton); boton.addActionListener(this); } public void actionPerformed(ActionEvent evento){ AppletContext contexto=getAppletContext(); contexto.showStatus(texto.getText()); } } Código fuente 187 Y un ejemplo de la ejecución del mismo se puede observar en la Figura 135. Figura 135 Hasta ahora se han comentado funciones de los applets que tienen que ver con el navegador Web directamente, como el propio título del apartado indica, pero también vamos a incluir en este mismo apartado un par de funciones de los applets, que aunque ya no tengan que ver directamente con el navegador, pueden resultar interesantes. La primera de estas funciones ya la habíamos comentado con anterioridad y consiste en el paso de parámetros al applet desde la página Web. Este paso de parámetros se realiza mediante la etiqueta <PARAM>. Para recuperar el parámetro desde el applet utilizamos el método getParameter() de la clase Applet, pasándole por parámetro una cadena que se corresponde con el nombre del parámetro. El Código fuente 188 se corresponde con el de un applet que recoge un parámetro llamado nombre, y muestra un saludo en la pantalla. 300 © Grupo EIDOS 15. Applets de Java: utilizando los applets import java.applet.*; import java.awt.*; public class Parametros extends Applet{ private String parametro=""; public void init(){ parametro=getParameter("nombre"); if (parametro==null) parametro= "persona desconocida"; } public void paint(Graphics g){ g.drawString("Buenos días "+parametro,10,20); } } Código fuente 188 Como se puede observar en el código, el método init() es el encargado de recuperar el parámetro y es en el método paint() dónde se muestra el saludo utilizando el atributo del applet que posee el valor ofrecido por el parámetro. En la Figura 136 se muestra la ejecución de este applet, en las dos situaciones, con parámetro y sin el. Figura 136 Y el código HTML de la página de la Figura 136 es el Código fuente 189. <HTML> <HEAD> <META NAME="GENERATOR" Content="Microsoft Visual Studio"> <META HTTP-EQUIV="Content-Type" content="text/html"> <TITLE>Document Title</TITLE> </HEAD> <BODY> <APPLET code="Parametros" height="50" name="destino" width="250"> <PARAM name="nombre" value="Pepe"> </APPLET> <hr> 301 Programación en Java © Grupo EIDOS <APPLET code="Parametros" height="50" name="destino" width="250"> </APPLET> </BODY> </HTML> Código fuente 189 La siguiente, y última función de los applets que vamos a comentar en este apartado, es la reproducción se sonidos. Para reproducir sonidos disponemos del interfaz AudioClip que lo podemos encontrar dentro del paquete java.applet, pero para utilizar un objeto de este tipo debemos antes asignarle el fichero de sonido que representa. El interfaz AudioClip ofrece tres métodos que nos permiten manipular un fichero de sonido, éstos son play(), loop() y stop(). El método play() ejecuta el fichero de sonido, es decir, reproduce el sonido, el método loop() también lo reproduce pero en un bucle infinito, y stop() detiene la ejecución del sonido. Vamos a ver esto con el ejemplo de un applet que permite reproducir un sonido, pararlo y ejecutarlo en un bucle, primero se muestra el código (Código fuente 190) y a continuación lo comentamos. import java.awt.*; import java.applet.*; import java.awt.event.*; import java.net.*; public class Sonidos extends Applet implements ActionListener{ private AudioClip sonido; private Button ejecutar,bucle,parar; public void init(){ setLayout(new GridLayout(3,1,10,10)); ejecutar=new Button("Play"); bucle=new Button("Loop"); parar=new Button("Stop"); add(ejecutar); add(bucle); add(parar); ejecutar.addActionListener(this); bucle.addActionListener(this); parar.addActionListener(this); //obtenemos el camino hasta el lugar dónde se encuentra el applet URL codeBase=getCodeBase(); //cargamos el fichero de sonido sonido=getAudioClip(codeBase,getParameter("sonido")); } public void actionPerformed(ActionEvent evento){ AppletContext contexto=getAppletContext(); if (evento.getSource()==ejecutar){ contexto.showStatus("Reproducir sonido"); sonido.play(); } else if (evento.getSource()==parar){ contexto.showStatus("Detener sonido"); sonido.stop(); } else{ contexto.showStatus("Reproducir sonido en un bucle"); sonido.loop(); } } } Código fuente 190 302 © Grupo EIDOS 15. Applets de Java: utilizando los applets Como se puede comprobar a la vista del código, en este caso tenemos tres fuentes de eventos y un mismo oyente, por lo que en el método actionPerformed() se debe identificar la fuente del evento, para ello utilizamos el método getSource() de la clase java.util.EventObject, de esta clase heredan todas las clase que representan a los eventos. Este método nos devuelve la referencia al objeto que ha producido el evento, por lo tanto debemos comparar el valor devuelto con cada uno de los objetos que son fuente de eventos, es decir, con cada uno de los objetos Button. En el método init() del applet además de crear el interfaz de usuario (Figura 137) y registrar los oyentes correspondientes, cargamos el fichero de sonido. Pero antes necesitamos obtener la ruta hasta nuestro applet, esto se consigue mediante el método getCodeBase() de la clase Applet que nos devuelve un objeto de la clase URL que contiene la dirección hasta nuestro applet. A continuación utilizamos el método getAudioClip() de la clase Applet, el cual permite cargar en el objeto AudioClip correspondiente el fichero de sonido que le especificamos por parámetro. Como se habrá observado, el nombre del fichero de sonido lo pasamos mediante un parámetro del applet (etiqueta <PARAM>) y lo recuperamos con el método getParameter() de la clase Applet. Figura 137 Eventos, gráficos y fuentes en los applets En este apartado vamos a tratar las clases Graphics, FontMetrics y Font, es decir, vamos a tratar la parte gráfica de los applets, algo que habíamos comentado de forma muy breve al utilizar el método paint(). También se va a comentar algunos aspectos del tratamiento de eventos dentro de los applets y se mostrarán algunos tipos de eventos que no habíamos visto hasta ahora. Todo ello se irá mostrando y comentando a través de numerosos ejemplos de applets. Pasemos a la parte gráfica de nuestro apartado. El objeto de la clase Graphics que recibe como parámetro el método paint() de la clase Applet representa a la superficie del applet y todo lo que se dibuje o escriba en el se mostrará en el applet. Aunque el método paint() lo estamos utilizando en la clase Applet, se debe señalar que este método se hereda de la clase Container, y por lo tanto lo presentan todos los objetos que realicen funciones de contenedor. La superficie del applet en el navegador Web Internet Explorer 5 se representa mediante el color gris por defecto, pero en el navegador Netscape Navigator 4.5 se representa mediante el color blanco por defecto. El método de la clase Graphics que hemos estado utilizando en diferentes momentos en este capítulo ha sido drawString(). Este método mostrará la cadena de caracteres que le pasemos por parámetro y en la posición que le indiquemos. El origen de coordenadas en la superficie del applet se encuentra en la parte superior izquierda. 303 Programación en Java © Grupo EIDOS Vamos a realizar un applet que vaya mostrando las coordenadas sobre las que se encuentra en ese momento el cursor del ratón dentro del applet, además estas coordenadas se muestran justamente al lado del cursor, es decir, en la propia coordenada en la que se encuentra. Nuestro applet va a tener una clase interna que va a ser una clase adaptadora que hereda de la clase MouseMotionAdapter, es decir, va a ser la clase oyente de los movimientos del ratón en nuestro applet. Este ejemplo es interesante también porque vemos un evento que no habíamos tratado hasta ahora, el evento de los movimientos del ratón. El método que vamos a utilizar y sobrescribir de la clase MouseMotionAdapter es mouseMoved() . Como su propio nombre indica este método se ejecutará cuando se mueva el ratón. En este método recogemos los valores actuales de las coordenadas y se los asignamos a los atributos coordX y coordY de nuestro applet, estos atributos han sido declarados para representar a las coordenadas x e y del ratón dentro de nuestro applet. En la última línea del método mouseMoved() se llama al método repaint() para que se actualice y repinte la superficie de nuestro applet. Los valores de las coordenadas se obtienen utilizando los métodos getX() y getY() de la clase MouseEvent. En el método paint() escribiremos el valor de las coordenadas en la coordenada exacta, pero antes de seguir comentando más acerca de este applet de ejemplo veamos su código, en el Código fuente 191. import java.awt.*; import java.applet.*; import java.awt.event.*; public class Coordenadas extends Applet{ int coordX, coordY; public void init(){ //se inicializan las coordenadas coordX=-1; coordY=-1; //registramos el oyente de los movimientos del ratón addMouseMotionListener(new AdaptadorMovimientosRaton()); } public void paint(Graphics g){ //se comprueba que las coordenadas son correctas if (coordX!=-1) //se escribe la coordenada en la posición en la //que está el ratón g.drawString("("+coordX+","+coordY+")",coordX,coordY); } //clase adaptadora interna que trata los movimientos del ratón class AdaptadorMovimientosRaton extends MouseMotionAdapter{ public void mouseMoved(MouseEvent evento){ coordX=evento.getX(); coordY=evento.getY(); //se vuelve a pintar el applet repaint(); } } } Código fuente 191 En el método paint() mostramos las coordenadas en las que se encuentra el cursor del ratón mediante el ya conocido método drawString() de la clase Graphics. 304 © Grupo EIDOS 15. Applets de Java: utilizando los applets Pero si probamos este applet comprobamos que no funciona correctamente, ya que cuando salimos de la superficie del applet las coordenadas se quedan pintadas en la superficie del applet. Para evitar este efecto debemos modificar el código del applet para tener en cuenta también los eventos de la entrada y salida del ratón en nuestro applet. Para ello hemos creado una clase interna que herede a la clase adaptadora MouseAdapter y que implemente los métodos mouseExited() y mouseEntered(). Al applet Coordenadas se le añade un nuevo atributo de tipo booleano llamado fuera, para saber en que situación estamos. El nuevo código del applet se muestra en el Código fuente 192. import java.awt.*; import java.applet.*; import java.awt.event.*; public class Coordenadas2 extends Applet{ int coordX, coordY; //indica si el ratón está fuera del applet boolean fuera; public void init(){ coordX=-1; coordY=-1; fuera=false; addMouseMotionListener(new AdaptadorMovimientosRaton()); addMouseListener(new AdaptadorRaton()); } public void paint(Graphics g){ //se comprueba si el ratón se encuentra en la //superficie del applet if(!fuera){ if(coordX!=-1) g.drawString("("+coordX+","+coordY+")",coordX,coordY); showStatus(""); } else showStatus("El ratón ha salido"); } class AdaptadorMovimientosRaton extends MouseMotionAdapter{ public void mouseMoved(MouseEvent evento){ coordX=evento.getX(); coordY=evento.getY(); repaint(); } } class AdaptadorRaton extends MouseAdapter{ public void mouseExited(MouseEvent evento){ //el ratón ha salido fuera=true; repaint(); } public void mouseEntered(MouseEvent evento){ //el ratón ha entrado fuera=false; repaint(); } } } Código fuente 192 305 Programación en Java © Grupo EIDOS En este código se muestra un mensaje en la línea de estado del navegador, pero no se utiliza el interfaz AppletContext, sino que directamente se utiliza el método showStatus() de la clase Applet, el resultado es el mismo que veíamos en el apartado anterior. Con esta nueva versión del applet ya no se produce el efecto comentado anteriormente, cuando desde el método mouseExited() se invoca al método paint() del applet mediante repaint(), el atributo fuera tiene el valor true, por lo tanto en el método paint() no escribe nada y se eliminan las coordenadas de la superficie del applet. En este punto es necesario realizar una aclaración. Cuando modificamos el código fuente de un applet que ya hemos ejecutado en el navegador, si lo volvemos a probar, en muchos casos, los cambios realizados no se reflejan, esto es debido a que el applet se encuentra en la memoria caché del navegador y se está utilizando la versión anterior de nuestro fichero de clase. En este caso la solución es cerrar el navegador y volver a abrirlo para probar el nuevo applet. En la Figura 138 se puede observar un ejemplo de ejecución de este applet. Figura 138 Si cambiamos todas las sentencias repaint() por la siguiente paint(getGraphics()), podemos pensar que el efecto es el mismo, es decir, la llamada al método paint(). Si realizamos este cambio y ejecutamos el applet de nuevo vemos un efecto distinto, las coordenadas que escribimos nunca se borran, como se puede observar en la Figura 139. El método getGraphics() de la clase Component nos devuelve el objeto Graphics que se corresponde con la superficie del componente gráfico correspondiente, en este caso la superficie del applet. Figura 139 306 © Grupo EIDOS 15. Applets de Java: utilizando los applets Este efecto se produce porque no hemos definido correctamente lo que significa llamar al método repaint(), habíamos dicho que el método repaint() llamaba al método paint() del applet, pero esto no es correcto, ya que la llamada no es directa, sino que el método repaint() llama antes al método update() de la clase Component y después de llamar al método update() llama al método paint(). La función que realiza el método update() antes de llamar al método paint() es la de limpiar o borrar toda la superficie que se encuentre representada por la clase Graphics. Por lo tanto si antes de todas las sentencias paint(getGraphics()) añadimos la sentencia update(getGraphics()) el efecto será el idéntico al que se producía al lanzar el método repaint(). Vamos a seguir comentando la utilización del objeto Graphics en los applets en conjunción con el tratamiento de eventos dentro de los mismos. En este caso se va a mostrar un ejemplo de un applet que consiste en escribir en la superficie del mismo la cadena "¡click!" en las coordenadas en las que se haya producido la pulsación del ratón. Para ello vamos a utilizar una clase adaptadora que herede de la clase MouseAdapter, y el método que nos va a interesar va a ser el método mousePressed(). Este método se ejecutará cuando se pulse el botón del ratón. El código de este applet es el que se muestra en el Código fuente 193. import java.awt.*; import java.applet.*; import java.awt.event.*; public class AppletClick extends Applet{ private int coordX,coordY; public void init(){ coordX=-1; coordY=-1; addMouseListener(new AdaptadorRaton(this)); } public void paint(Graphics g){ if (coordX!=-1) g.drawString("¡Click!",coordX,coordY); } //métodos de acceso public void modificaX(int x){ coordX=x; } public void modificaY(int y){ coordY=y; } } class AdaptadorRaton extends MouseAdapter{ private AppletClick fuente; public AdaptadorRaton(AppletClick fuente){ this.fuente=fuente; } //detectamos la pulsación en el ratón public void mousePressed(MouseEvent evento){ fuente.modificaX(evento.getX()); fuente.modificaY(evento.getY()); fuente.repaint(); } } Código fuente 193 307 Programación en Java © Grupo EIDOS Como se puede comprobar, al no utilizar una clase interna debemos ofrecer un par de métodos de acceso para que la clase AdaptadorRaton pueda modificar los atributos coordX y coordY de la clase AppletClick, van a ser estos dos atributos los que representen la coordenada en la que se debe escribir la cadena. Además debemos llevar la referencia de la clase fuente del evento, para ello la clase AdaptadorRaton tiene como atributo un objeto de la clase AppletClick. En este caso no es necesario controlar que el ratón salga de la superficie de nuestro applet, ya que el evento de la pulsación del ratón siempre se va a producir en el interior del mismo. El aspecto de este applet en ejecución es el de la Figura 140. Figura 140 Podemos realizar una modificación sobre el código anterior para que la cadena "¡Click!" sólo aparezca en el momento de la pulsación del botón y no permanezca pintada en al superficie del applet. Para ello debemos implementar el método mouseReleased(). El método mouseReleased() se ejecutará cuando se suelte el botón del ratón, es decir, un click de ratón ejecutará primero el método mousePressed() y a continuación el método mouseReleased(). En el método mouseReleased() asignamos el valor –1 a los atributos de la clase AppletClick que representan las coordenadas del ratón y a continuación se llama al método repaint(). El código del método mouseReleased() se muestra en el Código fuente 194. public void mouseReleased(MouseEvent evento){ fuente.modificaX(-1); fuente.modificaY(-1); fuente.repaint(); } Código fuente 194 Creo que ya hemos visto bastante de momento sobre el método drawString() de la clase Graphics, el método paint() y el tratamiento de eventos sobre la superficie del applet, por lo tanto a continuación nos vamos a centrar más en los distintos métodos de la clase Graphics. De esta clase comentaremos desde los métodos que nos permiten modificar el aspecto del applet a los que nos permiten dibujar distintas figuras e insertar ficheros de imágenes. También trataremos los tipos de fuente dentro de los applets. 308 © Grupo EIDOS 15. Applets de Java: utilizando los applets En todos los ejemplos anteriores cuando escribimos una cadena en la superficie del applet ésta parece en negro, si queremos que aparezca en un color determinado el texto, utilizaremos el método setColor() de la clase Graphics, de esta forma todo lo que se pinte en el objeto Graphics del applet aparecerá con el color especificado como parámetro en el método setColor(). El método setColor() recibe como parámetro un objeto de la clase Color que va a representar el color que se quiere asignar al objeto Graphics correspondiente. La clase Color se encuentra dentro del paquete java.awt y representa como su nombre indica los colores. Esta clase ofrece una serie de constantes para representar colores estándar: rojo, azul, amarillo, etc. Así para asignar el color azul a un objeto Graphics escribiremos lo que indica el Código fuente 195. g.setColor(Color.blue); Código fuente 195 Pero si queremos asignar un color personalizado por nosotros mismos, deberemos crear un objeto de la clase Color y pasárselo por parámetro al método setColor(). Para indicar un color personalizado, el constructor de la clase Color acepta como parámetros tres enteros en los rangos 0-255 siguiendo por lo tanto la definición de colores según la norma RGB. Cada uno de los enteros indica la intensidad de color rojo, verde y azul, respectivamente. De esta forma, si queremos asignar un color personalizado tenemos que escribir una sentencia similar a la que muestra el Código fuente 196. g.setColor(new Color(200,4,100)); Código fuente 196 Para asignar un color al fondo del applet utilizamos el método setBackGround(), pero este método no lo ofrece la clase Graphics, sino que se encuentra en la clase Applet. Al método setBackGround() le pasamos por parámetro un objeto de la clase Color, al igual que hacíamos con el método anterior. setBackground(new Color(12,134,100)); Código fuente 197 Hemos modificado el color de fondo del applet y el color de los elementos gráficos que van a aparecer en el mismo, ahora vamos a modificar el tipo de letra. Para modificar el tipo de letra utilizaremos el método setFont() de la clase Graphics. Este método recibe como parámetro un objeto de la clase Font que se corresponde con la letra que se desea utilizar. La clase Font se encuentra en el paquete java.awt y tiene un constructor que nos permite especificar el tipo o nombre de la letra, el aspecto que va a tener (normal, cursiva o negrita) y el tamaño de la misma. Así si queremos asignar a nuestro applet una letra de tipo Courier en negrita y de tamaño 20 píxeles escribiremos el Código fuente 198. 309 Programación en Java © Grupo EIDOS g.setFont(new Font("Courier",Font.BOLD,20)); Código fuente 198 Para definir los diferentes aspectos de las fuentes la clase Font ofrece una serie de constantes, Font.BOLD para la fuente en negrita, Font.ITALIC para la letra cursiva y Font.PLAIN para que muestre el aspecto normal. Para mostrar un ejemplo sencillo de todo lo visto hasta el momento vamos a realizar el famoso applet "Hola Mundo", pero modificando los colores y el tipo de letra. El código de este applet es el Código fuente 199. import java.awt.*; import java.applet.*; public class HolaMundo extends Applet{ public void init(){ setBackground(Color.pink); } public void paint(Graphics g){ g.setColor(new Color(200,4,100)); g.setFont(new Font("TimesRoman",Font.ITALIC,20)); g.drawString("Hola Mundo",20,20); } } Código fuente 199 Y el aspecto de esta nueva versión del applet "Hola Mundo" se puede comprobar en la Figura 141. Figura 141 La clase Font ofrece una serie de métodos que nos permiten consultar las características de la fuente actual. Estos métodos son: 310 • getName(): devuelve una cadena que indica el nombre de la fuente. • getSize(): devuelve el tamaño de la fuente actual como un entero. © Grupo EIDOS 15. Applets de Java: utilizando los applets • getStyle(): devuelve el aspecto de la fuente actual mediante un entero que se corresponde con los valores de las constantes de la clase Font. Font.PLAIN fuente normal tiene el valor 0, Font.BOLD fuente negrita tiene el valor 1, Font.ITALIC tiene el valor 2 y la combinación de negrita y cursiva devolverá el valor 3. • isPlain(): devuelve verdadero si el aspecto de la fuente es normal. • isBold(): devuelve verdadero si el aspecto de la fuente es negrita. • isItalic(): devuelve verdadero si el aspecto de la fuente es cursiva. Para obtener información acerca de las fuentes en el paquete java.awt encontramos también la clase FontMetrics. Esta clase ofrece una serie de métodos que permiten consultar una serie características de las fuentes que tiene asignadas actualmente la clase Graphics y que tiene que ver con las métricas de la fuente. Para obtener una instancia de un objeto de la clase FontMetrics lanzaremos el método getFontMetrics() de la clase Graphics sobre el objeto Graphics del que se quiere consultar la información de su letra. Algunos de los métodos de la clase FontMetrics son getHeight() y getCharWidth(), que nos indican la altura de la letra y la anchura de un carácter determinado, respectivamente, en pixeles. Como se puede deben ver estos métodos ofrecen una información más detallada y específica de las métricas de la fuente que los métodos de la clase Font. Otros métodos de esta clase son: • stringWidth(): se le pasa una cadena por parámetro y devuelve la anchura de la cadena en pixeles. • getAscent(): devuelve la distancia entre la línea base de la fuente y la parte superior de los caracteres. • getDescent(): devuelve la distancia entre la línea base de la fuente y la parte inferior de los caracteres, esto se aplica a caracteres como p y q que caen más bajo de la línea base. • getLeading(): devuelve el interlineado de la fuente. El siguiente ejemplo es un applet que permite configurar el mensaje "Hola Mundo", ofrece un interfaz de usuario para realizar esta tarea, además mediante este interfaz de usuario vamos a comentar el tratamiento de nuevos eventos, como son el de selección de una lista desplegable, la entrada de texto en un caja de texto y el de la selección de una casilla de verificación. Para la construcción del interfaz se ha utilizado el gestor de diseño GridLayout, BorderLayout y el anidamiento de objetos Panel. Se podría considerar que este applet es un ejemplo que además de mostrar lo que hemos explicado sobre el tratamiento de fuentes y color, también ofrece un resumen de muchos de los puntos vistos en anteriores apartados y capítulos, por lo que puede resultar bastante interesante. Este applet va a ser algo complicado, por lo tanto vamos a cambiar un poco la filosofía que veníamos utilizando hasta ahora en la explicación de los ejemplos, en lugar de mostrar todo el código y luego comentarlo, vamos a desglosarlo y comentarlo más minuciosamente, aunque para una mayor claridad al final de la exposición mostraremos el código completo. El primer punto que vamos a tratar va a ser la construcción del interfaz de usuario. Para que sea más claro primero vamos a mostrar el aspecto del applet, a continuación comentamos un poco su estructura y a continuación se procederá a facilitar el código fuente. El interfaz de usuario que presenta el applet 311 Programación en Java © Grupo EIDOS va a ser el de la Figura 142, que como se puede ver está formado por un objeto TextField, varios objetos Label, dos objetos Checkbox y dos objetos Choice. Figura 142 En realidad todos los componentes AWT utilizados se encuentran incluidos dentro de un objeto Panel, y es este objeto Panel, conteniendo todos los componentes, el que se añade al applet. Se han utilizado diversos paneles auxiliares, con diferentes gestores de diseño, para ir colocando los componentes del interfaz de usuario. El panel principal, denominado en el código panelInterfaz, presenta un gestor de diseño de la clase GridLayout de 2 filas y una columna. En la primera fila se añade un panel representado por el objeto panel1, que contendrá los controles que indican el tamaño y aspecto de la fuente, y en la segunda fila un panel representado por el objeto panel3. A su vez estos dos paneles incluyen en su interior otros gestores de diseño y otros paneles. En la Figura 143 se puede ver un esquema en el que se identifican cada uno de los paneles y como se encuentran divididos. Figura 143 El código que permite construir este interfaz de usuario lo tenemos en el método creaInterfaz(). Todos los componentes AWT utilizados para crear el interfaz han sido declarados como atributos de nuestro applet. public void creaInterfaz(){ //panel que contiene todo el interfaz panelInterfaz=new Panel(); panelInterfaz.setLayout(new GridLayout(2,1)); //panel que contiene los controles de tamaño y aspecto de la fuente 312 © Grupo EIDOS 15. Applets de Java: utilizando los applets panel1=new Panel(); panel1.setLayout(new GridLayout(1,3,5,1)); //panel que contiene la caja de texto con el tamaño de la fuente panel2=new Panel(); panel2.setLayout(new BorderLayout()); tamaño=new TextField(10); panel2.add("North",new Label("Tamaño")); panel2.add("South",tamaño); panel1.add(panel2); negrita=new Checkbox("Negrita"); panel1.add(negrita); cursiva=new Checkbox("Cursiva"); panel1.add(cursiva); panelInterfaz.add(panel1); //panel que contiene las dos listas desplegables panel3=new Panel(); panel3.setLayout(new GridLayout(1,2,5,1)); //panel que contiene la lista con los tipos de letra panel4=new Panel(); panel4.setLayout(new GridLayout(2,1)); panel4.add(new Label("Tipo Letra:")); tipoLetra=new Choice(); tipoLetra.addItem("Courier"); tipoLetra.addItem("Arial"); tipoLetra.addItem("TimesRoman"); panel4.add(tipoLetra); panel3.add(panel4); //panel que contiene la lista con los colores panel5=new Panel(); panel5.setLayout(new GridLayout(2,1)); panel5.add(new Label("Color Letra:")); colorLetra=new Choice(); colorLetra.addItem("rojo"); colorLetra.addItem("azul"); colorLetra.addItem("verde"); panel5.add(colorLetra); panel3.add(panel5); panelInterfaz.add(panel3); add(panelInterfaz); } Código fuente 200 A la vista del código fuente del método creaInterfaz() y el esquema de la Figura 143, damos por concluida la primera parte de la explicación. Al añadir el panel panelInterfaz al applet, el resto de la superficie del applet queda reservada para contener la cadena de texto a la que se va a ir modificando su aspecto según lo indiquemos en los controles del interfaz de usuario. Ahora debemos centrarnos en el tratamiento de eventos. En nuestro applet tenemos cinco fuentes de eventos, la caja de texto, las dos casillas de verificación y las dos listas desplegables, pero vamos a tener un único oyente, nuestro applet. La caja de texto va a lanzar un evento del tipo TextEvent cada vez que se modifique, ya que queremos que se cambie el tamaño de las letras mientras que lo indicamos en la caja de texto. Este evento puede ser atrapado y tratado por un oyente que implemente el interfaz TextListener. El interfaz TextListener tiene un único método, llamado textValueChanged() y que se ejecutará cuando se modifique el contenido de la caja de texto. Por lo tanto el applet deberá implementar el interfaz TextListener. 313 Programación en Java © Grupo EIDOS Cuando cambiamos el valor de un objeto Checkbox o un objeto Choice se lanza un evento de la clase ItemEvent. Este evento será tratado por un oyente que implemente el interfaz ItemListener. El interfaz ItemListener, al igual que el anterior, tiene un único método llamado itemStateChanged() que se ejecutará cuando se seleccione un elemento de la lista o se modifique el valor de la casilla de verificación. Por lo tanto nuestro applet además de implementar el interfaz TextListener debe implementar el interfaz ItemListener. La declaración de nuestra subclase de clase Applet quedaría como indica el Código fuente 201. public class Fuentes extends Applet implements ItemListener, TextListener{ } Código fuente 201 En el método itemStateChanged() deberemos distinguir en que situación estamos, ya que este método se puede ejecutar desde cuatro fuentes diferentes, las dos casillas de verificación o las dos listas desplegables, realizando en cada caso un proceso distinto, cada uno de estos procesos los comentaremos más adelante. El tratamiento de cualquiera de las cinco situaciones distintas que se puedan dar en el lanzamiento de un evento, es decir, modificación del contenido de la caja de texto, modificación de cualquiera de las dos casillas de verificación y selección de un elemento de cualquiera de las dos listas, ocasionarán una llamada al método repaint(), y consecuentemente una llamada al método paint(), que es precisamente el método que pasamos a comentar a continuación. En el método paint() lo que vamos a hacer va a ser asignar al objeto Graphics de nuestro applet los valores que se hayan indicado en el interfaz de usuario y que tienen que ver con el color de la letra, tamaño, tipo y aspecto. Por lo tanto es necesario definir en nuestro applet cuatro atributos que representen cada uno de estos valores. Y en los métodos para el tratamiento de eventos, es decir en los métodos textValueChanged() y itemStateChanged(), lo que deberemos hacer antes de llamar al método repaint() es actualizar el valor del atributo correspondiente. El método paint() simplemente emplea los valores de los atributos del applet para dibujar en pantalla el mensaje Hola Mundo. El código de este método es el Código fuente 202. public void paint(Graphics g){ g.setFont(new Font(tipo,aspecto,tam)); g.setColor(color); g.drawString("Hola Mundo",10,180); } Código fuente 202 Aquí se pueden apreciar los atributos que se han definido para nuestra clase Fuentes. El atributo tipo de la clase String contendrá el valor que se ha seleccionado en la lista desplegable que contiene los tipos de letra disponibles, el atributo aspecto del tipo primitivo int contiene el valor de la combinación de las casillas de verificación que indican si la letra va ser en negrita y/o en cursiva. El atributo tam del tipo primitivo int contiene el valor de la caja de texto que indica el tamaño de la fuente, y por último el atributo color que es un objeto de la clase Color, va a contener el color indicado en la lista desplegable correspondiente. Si bien el resto de los valores de los atributos se asigna de 314 © Grupo EIDOS 15. Applets de Java: utilizando los applets forma más o menos directa, en el caso del atributo color debemos emplear un método auxiliar llamado devuelveColor() que permite realizar la traducción del color especificado en la lista de colores con los disponibles en la clase Color como constantes de la misma. El código de este método es el Código fuente 203. public Color devuelveColor(String col){ if (col.equals("rojo")) return Color.red; else if (col.equals("azul")) return Color.blue; else return Color.green; } Código fuente 203 En el método init() del applet se deben inicializar los todos estos atributos, además de esta inicialización se lanza el método creaInterfaz() y se registra el oyente para las cinco fuentes de eventos ya comentadas. El código del método init() es el Código fuente 204. public void init(){ creaInterfaz(); tam=20; tipo="Arial"; color=Color.green; aspecto=Font.PLAIN; tipoLetra.addItemListener(this); colorLetra.addItemListener(this); tamaño.addTextListener(this); negrita.addItemListener(this); cursiva.addItemListener(this); } Código fuente 204 Ya sólo nos queda entrar en detalle en los métodos que tratan los eventos. El método textValueChanged() recupera el valor de la caja de texto correspondiente, lo transforma al tipo primitivo int y se lo asigna al atributo tam de la clase Fuentes. La conversión de cadena de caracteres a tipo int se realiza mediante el método estático parseInt() de la clase Integer, si se produce algún error en la transformación se lanza la excepción NumberFormatException, si se produce esta excepción no se modifica el valor del atributo tam. public void textValueChanged(TextEvent evento){ int t; try{ t=Integer.parseInt(tamaño.getText()); tam=t; repaint(); } catch (NumberFormatException ex){} } Código fuente 205 315 Programación en Java © Grupo EIDOS El método itemStateChanged() debe utilizar el método getSource() sobre el objeto evento de la clase ItemEvent que recibe como parámetro, para averiguar la fuente del evento, ya que, como ya hemos comentado, hay cuatro fuentes posibles. Si el origen del evento es cualquiera de los dos objetos Choice se recupera el valor de la lista desplegable mediante el método getSelectedItem() de la clase Choice, y se le asigna al atributo correspondiente. Y si el origen del evento es uno de los objetos Checkbox se modifica el atributo aspecto sumándole o restándole, según el estado del objeto Checkbox, el valor que se corresponde con la casilla verificada, es decir, Font.BOLD o Font.ITALIC. El código de este método se puede observar en el Código fuente 206. public void itemStateChanged(ItemEvent evento){ if(evento.getSource()==tipoLetra){ tipo=tipoLetra.getSelectedItem(); } else if(evento.getSource()==colorLetra){ color=devuelveColor(colorLetra.getSelectedItem()); } else if(evento.getSource()==negrita){ if (negrita.getState()) aspecto=aspecto+Font.BOLD; else aspecto=aspecto-Font.BOLD; } else if (evento.getSource()==cursiva){ if (cursiva.getState()) aspecto=aspecto+Font.ITALIC; else aspecto=aspecto-Font.ITALIC; } repaint(); } Código fuente 206 Una vez comentado cada fragmento del código del applet Fuentes, en el Código fuente 207, se muestra el código completo del mismo para tener una visión más general. import import import public java.awt.*; java.applet.*; java.awt.event.*; class Fuentes extends Applet implements ItemListener, TextListener{ private Choice tipoLetra, colorLetra; private Checkbox negrita, cursiva; private TextField tamaño; private Panel panelInterfaz, panel1, panel2,panel3,panel4,panel5; private String tipo; private int tam; private Color color; private int aspecto; public void init(){ creaInterfaz(); tam=20; tipo="Arial"; color=Color.green; aspecto=Font.PLAIN; 316 © Grupo EIDOS 15. Applets de Java: utilizando los applets tipoLetra.addItemListener(this); colorLetra.addItemListener(this); tamaño.addTextListener(this); negrita.addItemListener(this); cursiva.addItemListener(this); } public void itemStateChanged(ItemEvent evento){ if(evento.getSource()==tipoLetra){ tipo=tipoLetra.getSelectedItem(); } else if(evento.getSource()==colorLetra){ color=devuelveColor(colorLetra.getSelectedItem()); } else if(evento.getSource()==negrita){ if (negrita.getState()) aspecto=aspecto+Font.BOLD; else aspecto=aspecto-Font.BOLD; } else if (evento.getSource()==cursiva){ if (cursiva.getState()) aspecto=aspecto+Font.ITALIC; else aspecto=aspecto-Font.ITALIC; } repaint(); } public void textValueChanged(TextEvent evento){ int t; try{ t=Integer.parseInt(tamaño.getText()); tam=t; repaint(); } catch (NumberFormatException ex){} } public Color devuelveColor(String col){ if (col.equals("rojo")) return Color.red; else if (col.equals("azul")) return Color.blue; else return Color.green; } public void paint(Graphics g){ g.setFont(new Font(tipo,aspecto,tam)); g.setColor(color); g.drawString("Hola Mundo",10,180); } public void creaInterfaz(){ //panel que contiene todo el interfaz panelInterfaz=new Panel(); panelInterfaz.setLayout(new GridLayout(2,1)); //panel que contiene los controles de tamaño y aspecto de la fuente panel1=new Panel(); panel1.setLayout(new GridLayout(1,3,5,1)); //panel que contiene la caja de texto con el tamaño de la fuente panel2=new Panel(); panel2.setLayout(new BorderLayout()); tamaño=new TextField(10); panel2.add("North",new Label("Tamaño")); panel2.add("South",tamaño); panel1.add(panel2); negrita=new Checkbox("Negrita"); panel1.add(negrita); cursiva=new Checkbox("Cursiva"); panel1.add(cursiva); panelInterfaz.add(panel1); 317 Programación en Java © Grupo EIDOS //panel que contiene las dos listas desplegables panel3=new Panel(); panel3.setLayout(new GridLayout(1,2,5,1)); //panel que contiene la lista con los tipos de letra panel4=new Panel(); panel4.setLayout(new GridLayout(2,1)); panel4.add(new Label("Tipo Letra:")); tipoLetra=new Choice(); tipoLetra.addItem("Courier"); tipoLetra.addItem("Arial"); tipoLetra.addItem("TimesRoman"); panel4.add(tipoLetra); panel3.add(panel4); //panel que contiene la lista con los colores panel5=new Panel(); panel5.setLayout(new GridLayout(2,1)); panel5.add(new Label("Color Letra:")); colorLetra=new Choice(); colorLetra.addItem("rojo"); colorLetra.addItem("azul"); colorLetra.addItem("verde"); panel5.add(colorLetra); panel3.add(panel5); panelInterfaz.add(panel3); add(panelInterfaz); } } Código fuente 207 En el siguiente enlace se puede encontrar el fichero de código fuente del applet. En la Figura 144 se ofrece un ejemplo de la ejecución del applet. Y con esto se da por concluida la explicación de este applet de ejemplo. Lo que veremos a continuación serán los diferentes métodos que nos ofrece la clase Graphics para realizar figuras. Figura 144 Hasta ahora sólo hemos visto un método que nos permita dibujar algo en la superficie del applet, el método drawString(), pero la clase Graphics ofrece una serie de métodos que nos permiten dibujar diferentes tipos de figuras como pueden ser circunferencias, rectángulos, todo tipo de polígonos, etc. 318 © Grupo EIDOS 15. Applets de Java: utilizando los applets La clase Graphics tiene una serie de métodos del tipo drawXXX() donde XXX será el tipo de figura que se desea dibujar. Algunos de estos métodos del tipo drawXXX() tienen su equivalente método fillXXX(), estos métodos dibujan la misma figura pero la rellenan de color. Por ejemplo el método drawRect(), que dibuja un rectángulo, tiene su equivalente fillRect(), que dibuja un rectángulo de color, según el color que se haya especificado al objeto Graphics. Todos estos métodos de dibujo reciben una serie de parámetros del tipo entero int que se corresponde con las coordenadas y dimensiones de las figuras. El Código fuente 208 dibujaría un rectángulo de anchura 125 y altura 180 en la posición (35,15) . public void paint(Graphics g){ g.drawRect(35,15,125,180); } Código fuente 208 El siguiente applet de ejemplo permite elegir de una lista desplegable la figura que se desea dibujar en la superficie del mismo. Al producirse la selección se dibuja en el applet la figura correspondiente. El tratamiento de eventos en este applet es muy sencillo simplemente debemos implementar el interfaz ItemListener, ya que es el encargado de atrapar los eventos que provienen de la selección de una lista. En el método itemStateChanged() vamos a recoger el nombre de la figura seleccionada y se lo vamos a asignar a un atributo de nuestra clase llamado seleccion y que va a ser un objeto de la clase String. El atributo seleccion contiene el nombre de la figura que se desea dibujar. El método itemStatedChanged() también realizará una llamada al método repaint() para que se ejecute el método paint() del applet. En el método paint() dibujaremos la figura correspondiente atendiendo al valor del atributo seleccion. El código completo de este applet se puede ver en el Código fuente 209. import java.awt.*; import java.applet.*; import java.awt.event.*; public class Figuras extends Applet implements ItemListener{ private Choice figuras; private String seleccion; public void init(){ setLayout(new BorderLayout()); figuras=new Choice(); figuras.addItem("línea"); figuras.addItem("rectángulo"); figuras.addItem("cuadrado"); figuras.addItem("rectángulo redondeado"); figuras.addItem("rectángulo 3D"); figuras.addItem("arco"); figuras.addItem("elipse"); figuras.addItem("círculo"); figuras.addItem("polígono"); add("South",figuras); seleccion=""; figuras.addItemListener(this); } public void itemStateChanged(ItemEvent evento){ seleccion=figuras.getSelectedItem(); 319 Programación en Java © Grupo EIDOS repaint(); } public void paint(Graphics g){ if (seleccion.equals("línea")) // coordenadas origen, coordenadas fin g.drawLine(35,15,90,200); else if (seleccion.equals("rectángulo")) //coordenadas,anchura,altura g.drawRect(35,15,125,180); else if (seleccion.equals("cuadrado")) g.drawRect(35,15,125,125); else if (seleccion.equals("rectángulo redondeado")) //coordenadas,anchura,altura,arco de la esquina g.drawRoundRect(35,15,125,180,20,20); else if (seleccion.equals("rectángulo 3D")) //coordenadas,anchura,altura,elevación g.draw3DRect(35,15,125,185,true); else if (seleccion.equals("arco")) //coordenadas,anchura,altura,ángulo inicio, grados g.drawArc(35,15,160,160,90,180); else if (seleccion.equals("elipse")) //coordenadas,anchura, altura g.drawOval(35,15,160,100); else if (seleccion.equals("círculo")) g.drawOval(35,15,100,100); else if (seleccion.equals("polígono")){ //coordenadas x, coordenadas y, número de puntos int coordX[]={39,94,97,142,53,58,26}; int coordY[]={33,74,36,70,108,80,106}; int puntos=coordX.length; g.drawPolygon(coordX,coordY,puntos); } } } Código fuente 209 En este enlace se puede obtener el código del ejemplo. En este ejemplo se han utilizado los diferentes métodos drawXXX() de la clase Graphics, cada uno de ellos se acompaña de un comentario en el que se indica el significado de los parámetros. Un ejemplo de la ejecución del applet anterior se puede observar en la Figura 145. Figura 145 320 © Grupo EIDOS 15. Applets de Java: utilizando los applets Es posible copiar un área rectangular de un objeto Graphics a una posición diferente, para ello se utiliza el método copyArea() de la clase Graphics. Este método recibe los siguientes parámetros: coordenadas de origen, anchura y altura del área a copiar, coordenadas de destino. Así por ejemplo, si deseamos copiar un área cuadrada de 100 pixeles a 100 pixeles a su derecha escribiremos lo que muestra el Código fuente 210. g.copyArea(0,0,100,100,100,0); Código fuente 210 Para borrar un área rectangular de un objeto Graphics se utiliza el método clearRect() de la misma clase. Este método recibe como parámetro las coordenadas y la anchura y altura del área a borrar, es decir, tiene los mismos parámetros que el método drawRect(). Para terminar este apartado comentaremos el método drawImage() de la clase Graphics. Este método permite mostrar en la superficie del applet el contenido de un fichero de imagen. Como parámetros recibe un objeto de la clase Image, las coordenadas en las que va a mostrarse la imagen y una referencia a la clase que va a contener a la imagen y la va a mostrar. La clase Image se encuentra en el paquete java.awt y representa una imagen. Para crear un objeto Image se utilizará el método getImage() de la clase Applet. Este método devuelve el objeto Image que se corresponde con el nombre del fichero de imagen que se le pasa por parámetro. Para obtener el nombre completo del fichero de imagen se debe utilizar el método ya conocido getCodeBase() de la clase Applet. El Código fuente 211 se corresponde con un applet que muestra una imagen que se encuentra enmarcada mediante un rectángulo con las esquinas redondeadas. import java.applet.*; import java.awt.*; import java.net.URL; public class Imagen extends Applet{ Image imagen; public void init(){ URL codeBase=getCodeBase(); //creamos el objeto imagen imagen=getImage(codeBase,"arma.gif"); } public void paint(Graphics g){ //tamaño de la imagen int anchura=imagen.getWidth(this); int altura=imagen.getHeight(this); //se dibuja el rectángulo g.drawRoundRect(52,52,anchura+10,altura+10,30,30); //se dibuja la imagen g.drawImage(imagen,57,57,anchura,altura,this); } } Código fuente 211 321 Programación en Java © Grupo EIDOS Para obtener el tamaño de la imagen utilizamos los método getWidth() y getHeight() de la clase Image. Estos dos métodos tienen un parámetro que será una referencia a la clase que muestra la imagen, en este caso es el mismo applet, por lo que se utiliza this. El aspecto de este applet es el de la Figura 146 Figura 146 Compresión de applets Como ya hemos comentado los applets pueden estar formados por diferentes clases. Cuando cargamos uno de estos applets en una página Web, se establece una conexión para cada una de la clases de los applets con el servidor Web que contiene las mismas. De esta forma si hay una página Web con muchos applets o con un applet muy complejo que para su ejecución necesita de un gran número de clases, se incrementarán los tiempos de espera en la carga de los mismos, ya que cada vez que se carga un fichero .CLASS es necesaria una nueva conexión a través de la red, esto es, si el applet tiene veinte clases entonces serán necesarias veinte peticiones HTTP (HyperText Transfer Protocol) que el navegador debe realizar. Esto supone una gran pérdida de tiempo, ya que además de conectarse veinte veces al mismo servidor Web, los ficheros .CLASS no se encuentran comprimidos con utilidades de compresión como PKZIP o similares. Para reducir los tiempos de descarga de los applets se ha creado un mecanismo para enviar los applets a través de la Web dentro de un sólo fichero que contiene todos los ficheros de las clases necesarias y todos los recursos que utilice el applet como pueden ser imágenes o sonidos. De esta forma el navegador sólo necesita realizar una sola conexión con el servidor Web, ya que todos los ficheros necesarios se encuentran en un único fichero. Además de introducir en un mismo archivo todos los ficheros necesarios, se comprimen éstos de forma individual, así se reduce de forma considerable el tiempo de carga. Esta solución la implementan dos mecanismos diferentes, pero que esencialmente hacen lo mismo: ficheros CAB (cabinet files) y ficheros JAR (Java archive). La gran diferencia entre ficheros CAB y ficheros JAR es el formato de los ficheros y los algoritmos de compresión. Los ficheros CAB usan el formato de los ficheros cabinet de Microsoft, que ha sido utilizado por los productos y paquetes de instalación de Microsoft durante bastante tiempo. 322 © Grupo EIDOS 15. Applets de Java: utilizando los applets En este apartado vamos a comentar ambos mecanismos y también vamos a comentar que herramientas tenemos a nuestra disposición para comprimir ficheros de clase. Para la creación de ficheros cabinet disponemos de la herramienta Microsoft SDK for Java 3.2, esta herramienta la obtenemos del Web de Microsoft, ya que desgraciadamente Visual J++ no nos ofrece ninguna herramienta para empaquetar ficheros de clase. Los ficheros cabinet se basan en el sistema de compresión Lempei-Ziv, como parte fundamental de este formato está la herramienta de compresión Diamond, que provee una compresión eficiente para los datos de programas de instalación y aplicaciones para Internet. Con la herramienta Diamond se pueden almacenar varios ficheros en un único fichero cabinet, y comprimir cada uno de ellos. Diamond es utilizado por la herramienta llamada CABARC (CABinet ARChiver), y esta será la herramienta que usaremos para la creación del fichero cabinet que, como se ha comentado, contendrá todos los ficheros de las clases utilizadas por los applets. La herramienta CABARC la encontramos dentro de Microsoft SDK for Java 3.2. La herramienta CABARC tiene múltiples opciones, pero las más relevantes son: n para la creación de un nuevo fichero cabinet y l para listar los contenidos del fichero cabinet. De esta forma para crear el fichero cabinet llamando clases.cab con todos los ficheros .CLASS del directorio actual se debe escribir lo siguiente en la línea de comandos: cabarc n clases.cab *.class Para asegurarnos que los ficheros que han sido añadidos al fichero clases.cab son los correctos, se puede escribir lo que indica el Código fuente 212. cabarc l clases.cab Código fuente 212 Para poder utilizar el fichero clases.cab dentro de la página Web, se debe utilizar un parámetro de la etiqueta <APPLET> llamado CABBASE. <APPLET CODE="AppletInicio.class" WIDTH=0 HEIGHT=0 > <PARAM NAME="cabbase" VALUE="clases.cab"> </APPLET> Código fuente 213 El único navegador que soporta ficheros cabinet es Internet Explorer. El formato genérico que presentan los ficheros .CAB es el que se puede apreciar en la Figura 147. Otra utilidad adicional que presentan los ficheros .CAB, es la posibilidad de firmarlos digitalmente. De esta forma, cuando un usuario cargue el applet se le preguntará si acepta la firma digital. Si acepta el applet ya no estará sujeto a las restricciones de seguridad, es decir, se podrá conectar a cualquier máquina de Internet, podrá acceder al sistema local de ficheros, podrá instanciar y usar objetos COM, etc. La firma digital asegura la integridad (el código no ha sido alterado ni manipulado después de su publicación) y la autenticidad (indicando el lugar del que proviene el código). 323 Programación en Java © Grupo EIDOS Figura 147 Para realizar una firma digital es necesario tener un certificado, para conseguir un certificado válido (SPC, Software Publisher Certificate) es necesario contactar con una autoridad de certificación (CA, Certification Authorities) algunas de ellas son: GTE, VeriSing Inc., etc. La persona que pide el certificado debe identificarse con sus credenciales y garantizar que el código (en nuestro caso los applets) no contiene virus ni elementos maliciosos que puedan dañar el sistema del usuario. El segundo tipo de empaquetado que podemos realizar es a través del formato JAR, este formato es más estándar que el anterior, ya que lo soportan tanto los navegadores Web Netscape Navigator e Internet Explorer, además es el formato propuesto por Sun. Los ficheros JAR se comprimen atendiendo al formato ZIP. Los ficheros JAR también permiten la firma de código. Para crear ficheros JAR Sun nos ofrece la herramienta Java Archive Tool, esta herramienta se ofrece junto el kit de desarrollo JDK (Java Developers Kit) de Sun. Para crear un fichero JAR con todos los ficheros del directorio actual deberemos escribir lo siguiente en la línea de comandos, dónde fichero.jar contendrá todos los ficheros comprimidos. jar cf fichero.jar * Para que un applet contenido en un fichero JAR se pueda incluir en una página Web es necesario escribir el código HTML que se muestra en el Código fuente 214. <APPLET CODE="ClaseApplet.Class" WIDTH=120 HEIGHT=120 ARCHIVE="Fichero.jar"> </APPLET> Código fuente 214 En este caso se utiliza una propiedad de la etiqueta APPLET llamada ARCHIVE. En definitiva debemos utilizar ficheros CAB y/o JAR debido a que: 324 • Ofrecen una compresión eficiente. • Supone un beneficio inmediato y significante para los usuarios de nuestra página Web ya que permite una reducción en el tiempo de carga e inicialización de los applets. © Grupo EIDOS 15. Applets de Java: utilizando los applets Componentes Swing y applets En todos los ejemplos utilizados en este capítulo y en el anterior hemos utilizado componentes AWT dentro de objetos de la clase Applet, para construir interfaces de usuario dentro de nuestros applets, también podríamos haber utilizado componentes Swing conjuntamente con la clase javax.swing.JApplet, pero si lo hubiéramos hecho posiblemente al cargar nuestro applet en el navegador no nos habría funcionado. El problema consiste en que en las MV (maquinas virtuales) que poseen los navegadores Netscape o Explorer no poseen la última versión del lenguaje Java, como ejemplo cabe reseñar que Internet Explorer 5 o Netscape Navigator 4.5 no permiten la ejecución de estos applets, mejor dicho, no ocurre nada cuando se ejecuta una página que invoca a uno de estos applets. La máquina virtuales de estos dos navegadores se corresponden con la versión 1.1 del lenguaje Java, es decir, no soportan Java 2. Lo mismo ocurrirá con el visor de applets de Visual J++, sin embargo el visor de applets del JBuilder y del JDK (appletviewer) si que implementan la última versión del lenguaje Java, la plataforma Java 2. La solución para poder utilizar applets Swing (JApplet) en un navegador Web consiste en instalar un software (a modo de parche) que podemos encontrar en el sitio Web de Sun y que se denomina Java Plug-in. Este añadido permite ejecutar applets implementados en Swing, que heredarán de la clase JApplet, esta clase del paquete javax.swing la comentaremos en el siguiente apartado. El Plug-in lo podemos obtener en la dirección http://java.sun.com/products/plugin/, a la hora de obtener el Plug-in deberemos indicar el tipo de plataforma en el que vamos a utilizarlo. También existen varias versiones del Plug-in en nuestro caso nos interesa la última que se corresponde con el JDK 1.3, por lo tanto seleccionaremos el software Java Plug-in 1.3. La instalación del Plug-in en un sistema operativo Windows no tiene ningún tipo de complicación, simplemente deberemos ejecutar el fichero que hemos obtenido de Sun y seguir las instrucciones correspondientes incluso el hecho de que se tenga que utilizar diferentes navegadores Internet Explorer o Netscape Navigator no va a resultar ningún problema, ya que se instala en el sistema operativo independientemente del navegador que se utilice. Solo a la hora de crear la página Web que vaya a hacer la llamada al applet es la que va a ser diferente dependiendo del tipo de navegador. Otra cosa importante es que a diferencia de los applets que no utilizan Swing, con este tipo de applets no vamos a utilizar la etiqueta <APPLET> en el código HTML en ningún caso, puesto que en realidad lo que estamos haciendo es una llamada a un componente ActiveX (proporcionado por el Java Plug-in) que se encarga de su visualización. Debido a esto la etiqueta utilizada es <OBJECT> o <EMBED> dependiendo de si el navegador Web es Internet Explorer o Netscape Navigator respectivamente. Así por ejemplo si tenemos la clase SwingApplet, que es la clase que representa a un applet, si queremos incluirla en una página HTML, escribiríamos el código HTML que aparece en el Código fuente 215. <APPLET code="SwingApplet.class" align="baseline" width="200" height="200"> </APPLET> Código fuente 215 Pero si esta clase es un applet de Swing, es decir, hereda de la clase JApplet, deberemos escribir el Código fuente 216, suponiendo que el navegador Web que va a cargar la página, es Internet Explorer. 325 Programación en Java © Grupo EIDOS <OBJECT classid="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93" width="200" height="200" align="baseline"> <PARAM NAME="code" VALUE="SwingApplet.class"> <PARAM NAME="type" VALUE="application/x-java-applet;version=1.3"> <PARAM NAME="scriptable" VALUE="true"> </OBJECT> Código fuente 216 Pero si el navegador Web es Netscape Navigator escribiremos el Código fuente 217. <EMBED type="application/x-java-applet;version=1.3" width="200" height="200" align="baseline" code="SwingApplet.class" </EMBED> Código fuente 217 Y lo más recomendable es no presuponer nada sobre el navegador Web que va a ejecutar el applet, y utilizar el código HTML que se muestra en el Código fuente 218, que es una mezcla de los dos anteriores, para que funcione el applet correctamente tanto con el navegador Web Internet Explorer como con el navegador Web Netscape Navigator. <OBJECT classid="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93" width="200" height="200" align="baseline"> <PARAM NAME="code" VALUE="SwingApplet.class"> <PARAM NAME="type" VALUE="application/x-java-applet;version=1.3"> <PARAM NAME="scriptable" VALUE="true"> <COMMENT> <EMBED type="application/x-java-applet;version=1.3" width="200" height="200" align="baseline" code="SwingApplet.class" </EMBED> </COMMENT> </OBJECT> Código fuente 218 En la Figura 148 se puede ver un ejemplo de ejecución de este applet de Swing, cuyo código fuente es el Código fuente 219. import java.awt.*; import javax.swing.*; public class SwingApplet extends JApplet { JButton boton; public void init() { getContentPane().setLayout(new FlowLayout()); boton = new JButton("Soy un applet de Swing"); getContentPane().add(boton); } } Código fuente 219 326 © Grupo EIDOS 15. Applets de Java: utilizando los applets Figura 148 Si el lector a probado en su equipo este ejemplo, habrá comprobado lo lento y tedioso que resulta ejecutar los applets que utilizan Swing a través del Plug-in de Java, además esto se debe considerar como un parche a medias, ya que el Plug-in de Java los único que permite es poder utilizar los componentes de Swing, pero no el resto de características de la versión 2 del lenguaje Java. De momento, hasta que no exista un navegador Web que implemente en su máquina virtual de Java la versión Java 2, es recomendable no abusar del uso de applets de Swing. La clase JApplet En el uno de los capítulos dedicados a los componentes Swing, más concretamente a los contenedores Swing, ya introdujimos esta clase. Se trata de un contenedor Swing de alto nivel que se muestra dentro de páginas Web a través de navegadores, es por lo tanto la versión Swing de la clase java.applet.Applet, además la clase JApplet es clase hija de la clase java.applet.Applet. Básicamente un objeto de la clase javax.swing.JApplet va a tener las mismas funciones que un objeto de la clase java.applet.Applet, ya que como ya hemos dicho, se trata de la versión Swing de los applets de Java. A continuación vamos a comentar algunas de las diferencias que existen entre estas dos clases y que novedades aporta la clase JApplet sobre la clase Applet, muchas de estas novedades se desprenden de las características comunes que presentan los componentes Swing. La clase JApplet pertenece al paquete de los componentes Swing javax.swing, no pertenece a un paquete específico como si lo hace la clase Applet. Al ser un contenedor Swing de alto nivel, la clase JApplet posee un panel raíz (root pane) al igual que sucedía con la clase JFrame. Como resultados más destacables de esta característica tenemos que es 327 Programación en Java © Grupo EIDOS posible añadir una barra de menú a un applet y que para añadir componentes al mismo debemos hacer uso de su panel de contenido (content pane). De las afirmaciones anteriores se extraen las siguientes apreciaciones a la hora de utilizar objetos de la clase JApplet: • Los componentes se añaden al panel de contenido del applet, no directamente al applet, es decir, haremos uso del método getContentPane() para obtener el panel de contenido, y sobre este panel de contenido lanzaremos los métodos add() que sean necesarios para añadir los distintos componentes que va a contener nuestro applet. • El gestor de diseño se aplicará sobre el panel de contenido del applet, no sobre el applet. • El gestor de diseño por defecto de los applets de Swing (JApplet) es el gestor BorderLayout, a diferencia de la clase Applet que presentaba como gestor de diseño por defecto al gestor FlowLayout. Además, como todo componente Swing, la clase JApplet soporta la característica Pluggable Look & Feel, es decir, a nuestros applets de Swing podemos darles el aspecto que deseemos entre los diferentes Look & Feel (Mosaic, Java, Mac y Windows). Como vemos la clase JApplet ofrece a los applets convencionales, la funcionalidad que poseen los contenedores de alto nivel de Swing. Por lo demás casi todo lo visto para la clase Applet se puede aplicar a la clase JApplet. De esta forma si tomamos el ejemplo utilizado para mostrar la característica Pluggable Look & Feel, en el tema llamado Interfaces de usuario en Java: Otras características de Swing, podemos modificarlo para que funcione como un objeto de la clase JApplet. No vamos a mostrar de forma completa el código, sino sólo los fragmentos que cambian. En primer lugar la cabecera de la clase es la que se muestra en el Código fuente 220. Y el constructor se debe eliminar. public class LF extends JApplet implements ActionListener{ Código fuente 220 El método main() de arranque de la aplicación, se debe sustituir por el método init(), que inicializará el applet y que posee el Código fuente 221. public void init(){ try { UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); } catch (Exception e) { } getContentPane().setLayout(new BoxLayout(getContentPane(),BoxLayout.Y_AXIS)); creaMenu(); creaTexto(); creaBotones(); creaSelector(); creaCombo(); creaEtiqueta(); 328 © Grupo EIDOS 15. Applets de Java: utilizando los applets creaOpciones(); } Código fuente 221 En el método actioPerformed() se debe eliminar la llamada al método pack(). El resto del código es exactamente igual al de la aplicación. Si ejecutamos este applet con el visor de applets de JDK (herramienta appletviewer), obtenemos el resultado que muestra la Figura 149. Figura 149 El código completo del ejemplo se puede obtener en este enlace. Con los applets de Swing damos por finalizada la parte del curso dedicada al segundo tipo de programas que podemos realizar en Java, los applets. 329 Aspectos avanzados de Java: procesos Introducción En este capítulo y en el siguiente vamos a tratar algunos aspectos y características avanzadas del lenguaje Java. Como características avanzadas se han seleccionado las siguientes: • La posibilidad de multiproceso en Java, es decir, la posibilidad de tener distintos hilos de ejecución paralelos. Veremos la clase Thread y el interfaz Runnable. • Utilización de canales de entrada salida, para leer y escribir información en diversos medios, desde la memoria a ficheros en disco. Ilustraremos los canales con numerosos ejemplos. • Aplicaciones cliente/servidor en Java a través del mecanismo de los sockets, es decir, a través de conexiones en Internet. Comentaremos las clases Socket y ServerSocket. Procesos y multiproceso El ser humano y toda criatura viva posee la capacidad del multiproceso. Para regir correctamente sus acciones reflejas, los seres humanos mantienen una serie de procesos simultáneos y que en perfecta armonía hacen posible su desarrollo como ser vivo. El latido del corazón, la digestión, la respiración, son algunos de los procesos que de forma simultánea, y perfectamente coordinada, tienen lugar diariamente en nuestro cuerpo. Como ya ha ocurrido en multitud de ocasiones el ser humano ha emulado lo que ya estaba presente en la naturaleza (véanse las alas de un avión a imagen de las alas de las aves como muestra de lo dicho), y a igual que Programación en Java © Grupo EIDOS procesos humanos los programas de software necesitan de los procesos para acomodarse a los requerimientos actuales de los usuarios. Hace unos años, un procesador de textos debía paralizar el sistema del usuario mientras preparaba el documento para la impresión y lo imprimía. El programa se apoyaba en el tamaño del buffer de la impresora para retornar el control del ordenador al usuario con mayor o menor celeridad. Los procesadores de textos de la generación anterior a la actual ya salvaban esta incomodidad permitiendo el proceso en segundo plano de la tarea de impresión. La época en que todo el tiempo de la CPU de un ordenador personal era propiedad de un único proceso, ya pasó. Ahora no solamente múltiples procesos se están ejecutando en un mismo instante de tiempo, sino que también multitud de programas realizan diversos cometidos simultáneamente en el equipo del usuario. Hola mundo con hilos Vamos a rescribir el applet que realizábamos en un capítulo anterior y que mostraba en pantalla el saludo Hola Mundo, para que utilicemos la técnica de multihilo en Java. El código se muestra en el Código fuente 222. import java.awt.*; import java.applet.*; public class HolaMundoHilo extends Applet implements Runnable{ Thread oHilo; public void start(){ if( oHilo == null ){ oHilo = new Thread(this); oHilo.start(); } } public void run(){ // Lo que tengamos que hacer } public void stop(){ if( oHilo != null ){ oHilo.stop(); oHilo = null; } } public void paint( Graphics g ){ g.drawString( "!Hola Mundo¡", 0, 10 ); } } Código fuente 222 Si comparamos éste con el antiguo, vemos unas mínimas diferencias, a saber: 1. Hacemos que la clase principal de nuestra applet incorpore el interfaz Runnable. La interfaz Runnable habilita los mecanismos necesarios para la ejecución de un proceso, en este caso la declaración del método run(). 2. Dotamos a la clase de una variable que será la encargada de almacenar el proceso del applet. Indicamos para esta tarea que nuestra variable será de la clase Thread (hilo de proceso, 332 © Grupo EIDOS 16. Aspectos avanzados de Java: procesos proceso). Thread es una clase definida en el paquete java.lang por lo que no es necesario importarlo ya que este paquete es automáticamente importado para todos los fuentes. 3. Añadimos el método start(), en él crearemos el hilo de proceso para la applet, y enviaremos el mensaje start() sobre él para que se inicie su ejecución. Una vez finalizada la construcción del objeto Thread ligándolo al applet, se deposita en el objeto oHilo para futuras referencias. En la línea siguiente se pone en funcionamiento el proceso por medio del envío del mensaje start() sobre oHilo. Una vez que el proceso es puesto en marcha, éste se encarga de llamar al método run() de la clase (se debe recordar que en el paso uno hicimos las modificaciones oportunas para importar el interfaz Runnable). Dentro del método run() será donde situaremos el código de aquello que deseamos realice nuestro proceso. Si run() es el punto de entrada del proceso, stop() es el de salida. En el método stop() del applet tras comprobarse que oHilo no está vacío, se envía sobre él el mensaje stop() y se vacía la variable de su contenido, asignándola el valor null. En caso de que se retorne a dicha página el método start() del applet será el encargado de crear nuevamente el proceso, reiniciando todo el ciclo. Aunque como se puede observar en el código del applet nuestro proceso no hace nada, simplemente se ha añadido para mostrar una pequeña introducción a la programación multiproceso en Java. Paralelismo Por diferentes motivos, el paralelismo, que habilitan los procesos, es un elemento a tener muy en cuenta a la hora de escribir el código de un proceso. Hemos de adecuar nuestra mente monoproceso a una mente multiproceso en la que diferentes procesos, o incluso múltiples ocurrencias del mismo proceso pueden estar ejecutándose a un tiempo. Con un problema similar, aunque a menor escala, se encuentra el programador que ha de migrar sus aplicaciones desde un entorno monousuario hacia uno multiusuario. Exponemos el típico problema que se plantea en el que un punto de venta con varios operadores despachando artículos. Dos dependientes, A y B, realizan la venta de una cafetera del mismo modelo. Una de las acciones del programa es descontar de la tabla de existencias el artículo despachado. Podríamos optar por un código similar al Código fuente 223. oExistencias.nCantidad --; Código fuente 223 Imaginemos que al comenzar había 20 cafeteras, sí ambas actualizaciones son coincidentes en el tiempo, y el programa no fue concebido para ejecutarse en un entorno multiusuario, pudiera producirse la circunstancia en que los dos puntos de venta tomaran 20 como base de la línea que decrementa, perdiéndose una de las actualizaciones. En este caso la solución es sencilla, bien por medio de la seguridad que una base de datos aporta a este tipo de transacciones, bien porque nosotros manualmente bloqueemos la fila donde se va a producir la actualización. Podemos pensar que el problema, después de todo, no es tan grave, pero pensemos en el Código fuente 224, que bien pudiera formar parte del proceso que dispensa dinero en un cajero automático. 333 Programación en Java © Grupo EIDOS if( nSaldo > 0 ){ // Se realizan diversas operaciones tomando como base nSaldo // .. nSaldo -= nInvertido; } Código fuente 224 Si sabemos que el proceso que contiene dicho código puede ejecutarse varías veces, puede darse el caso de que dos o más lleguen a la comparación al mismo tiempo... y ya tenemos problema a la vista puesto que nSaldo - nInvertido será idéntico en ambos procesos, uno de los decrementos se pierde. Imaginemos que el sistema ha autorizado dos trasferencias de 100.000 dólares es una cuenta cuyo nSaldo era de 112.003 dólares. Podríamos estar pensando que reduciendo más y más el área del ejemplo podríamos llegar a eliminar el problema. Esto no es posible sin operaciones atómicas (este tipo de operaciones parecen suceder todas a un mismo tiempo, es decir, no pudiendo ser interrumpidas por otro proceso durante la ejecución del primero). Pero éste es un problema soluble. La librería de Java fue concebida desde el principio con los procesos en mente, por lo que no deberemos preocuparnos de la pulcritud de las clases estándar. Además se aportan todos los elementos dentro del lenguaje para que los procesos que nosotros creemos, se comporten correctamente en un ambiente multiproceso. Vamos a aproximarnos al tema que nos ocupará por medio de la prueba y error, que aunque tedioso en algunos casos es uno de los mejores métodos para enfrentarse a un lenguaje de programación. Observemos el Código fuente 225. public CuentaProcesos{ int nValor; public void agregame(){ nValor ++; } public int cuantos(){ return nValor; } } Código fuente 225 Olvidemos por el momento como se crean los procesos, pensemos que en el sistema se han creado una serie de ellos a partir de la clase CuentaProcesos y se están ejecutando. El incremento de nValor no es una operación atómica. Requiere tres pasos: obtener el valor actual de nValor, agregarle uno y asignarlo a nValor. Al no ser una operación atómica no esta sincronizada y, por tanto, no es segura. Esta falta de seguridad puede provocar fallos a la hora de llevar la cuenta de procesos, cometido de la hilo que hemos codificado. Una palabra clave, synchronized, viene a resolver nuestro problema. public CuentaProcesos{ int nValor; 334 © Grupo EIDOS 16. Aspectos avanzados de Java: procesos public synchronized void agregame(){ nValor ++; } public int cuantos(){ return nValor; } } Código fuente 226 Con synchronized informamos a la máquina virtual de Java que el método sólo podrá ejecutarse por un proceso al mismo tiempo. Los procesos esperarán a que uno termine para que otro empiece, pero esto tiene un serio inconveniente en grandes métodos synchronized. Estos pueden provocar importantes cuellos de botella en el sistema al esperar que les llegue el turno de ejecución. En el caso de las variables, Java las define como sincronizadas por defecto. En el raro caso de que no deseemos sincronización para variables deberemos indicar el modificador volatile para las mismas. El método cuantos() no ha de ser synchronized ya que tan sólo retorna el valor de una variable del objeto. Veamos ahora el Código fuente 227. public class Dimension{ private int nAlto, nAncho; public int nAlto(){ return nAlto; } public nAncho(){ return nAncho; } // ... métodos para fijar nAlto y nAncho } public ImprimeDimension{ public void print( Dimension d ){ System.out.println( "El ancho es " + d.nAncho() + " y el alto es " + d.nAlto()+" para el objeto dimensión" ); } } Código fuente 227 Similares en cometido al método cuantos() de la clase CuentaProcesos, son en la clase Dimension los métodos nAlto() y nAncho(). No necesitan ser synchronized ya que tan solo retornan variables del objeto (recuérdese que Java las define como synchronized por defecto). Es cometido y responsabilidad de la parte de código que llama a nAncho() y nAlto() saber si ha de sincronizarse o no. Aunque la operación del método print() es simple, no es atómica. El método print() lee dos valores y los imprime. Precisamente entre la ejecución de nAlto() y nAncho() podría suceder que otro proceso cambiara los valores, falseándose los resultados mostrados. Cuando se trabaja con procesos es de vital importancia no suponer que nada se ha ejecutado entre dos partes de nuestro programa, incluso entre dos partes de una misma expresión. Debemos tener siempre presenta la pregunta ¿qué sucederá si dos procesos ejecutan esta línea al mismo tiempo?, igualmente veamos si la operación que se está realizando es atómica o no. Veamos una versión más segura de ImprimeDimension que nos permite ser más precisos a la hora de especificar que parte del método deseamos que esté protegido. 335 Programación en Java © Grupo EIDOS public ImprimeDimension2{ public void print( Dimension d ){ int nAnchoAux, nAltoAux; // Todo el bloque ocurre de forma atómica synchronized(this){ nAnchoAux = d.nAncho(); nAltoAux = d.nAlto(); } System.out.println( "El ancho es " + nAnchoAux + " y el alto es " + nAltoAux +" para el objeto dimensión" ); } } Código fuente 228 Ahora hemos visto otra de las posibilidades que nos da la sentencia synchronized. A esta sentencia podemos enviarle un parámetro que indique el objeto a bloquear si más de un proceso intenta la ejecución del código encerrado entre llaves. De este modo liberamos al sistema de caer en cuellos de botella de los anteriormente indicados. Ahora los programadores tenemos la potestad de indicar qué partes de un método son seguras y qué parte no necesitan serlo, pudiéndose, por tanto, ejecutarse a un mismo tiempo. Aunque hemos dado un paso importante nadie impide que otro proceso cambie el valor de Dimension d. Para impedir este comportamiento indeseable remitiremos a synchronized el objeto d, asegurando de este modo que nada ni nadie podrá modificar el valor de Dimension d durante la obtención de los valores nAlto y nAncho. public ImprimeDimension3{ public void print( Dimension d ){ int nAnchoAux, nAltoAux; // Todo el bloque ocurre de forma atómica // y solamente un proceso puede cambiar d synchronized(d){ nAnchoAux = d.nAncho(); nAltoAux = d.nAlto(); } System.out.println( "El ancho es " + nAnchoAux+ " y el alto es " + nAltoAux+" para el objeto dimensión" ); } } Código fuente 229 Con esta leve pero efectiva modificación hemos completado la seguridad que estaba en nuestras manos, siempre y cuando asumamos que la forma de obtener Dimension d es segura. Esta asunción es totalmente fiable siempre que se trate de clases del sistema. Pero fuimos nosotros los que codificamos la clase Dimension y es nuestra responsabilidad codificar un método seguro para cambiar los valores nAlto y nAncho. Podemos asegurar esto codificando un único método que fije ambos valores. public class Dimension2{ private int nAncho, nAlto; // Los métodos para obtener nAlto y nAncho... 336 © Grupo EIDOS 16. Aspectos avanzados de Java: procesos public synchronized void fijarAltoAncho(nAlto, nAncho){ nAlto = nAltoNuevo; nAncho = nAnchoNuevo; } } Código fuente 230 Haciendo synchronized el único método con el que se pueden alterar los contenidos de nAncho y nAlto protegemos el código ante cualquier actualización concurrente que pudiera suceder. Con synchronized garantizamos que sólo un método de este tipo se estará ejecutando a un mismo tiempo. Esta garantía es posible debido a que una de las características de synchronized es precisamente que sólo un método synchronized puede estar ejecutándose para la misma clase en el mismo instante de tiempo. Utilizando procesos. El interfaz runnable Para realizar un proceso en Java se debe seguir los siguientes pasos: 1. Heredamos de la clase Thread. public class MiThreadHeredado extend Thread{ public void run(){ // Lo que deseemos que ejecute MiThreadHeredado } } Código fuente 231 2. No tenemos nada más que un descendiente de Thread con las nuevas características que le hayamos indicado. Pero para que todo se ponga en funcionamiento, debemos crear una ocurrencia real en el ámbito de ejecución de Java para que, sea lo que fuere, lo que programamos en el método run() se active. oMTH = new MiThreadHeredado(); Código fuente 232 3. Una vez creado el objeto, tan sólo nos queda echarlo a andar. Esto lo conseguimos enviando el mensaje start() sobre el objeto recién creado. El método start(), codificado dentro de la librería estándar de Java, será el encargado de enviar sobre sí mismo (el objeto que estamos creando, this) el mensaje run() que ejecutará el método homónimo. El método run() ejecutado será él que codificamos con aquello con lo que deseábamos particularizar la nueva clase. oMTH.start() Código fuente 233 337 Programación en Java © Grupo EIDOS Algunas tareas adicionales que habitualmente desearemos ejecutar sobre un proceso serán pararlo temporal o definitivamente y reanudar su ejecución tras una parada temporal. Para paralizar temporalmente la ejecución de un proceso utilizaremos el mensaje suspend(). Cuando deseemos reanudar la ejecución de un proceso parado con suspend() utilizamos resume(). oMTH.suspend(); // lo que necesitemos hacer mientras oMTH no se está ejecutando oMTH.resume(); Código fuente 234 Cuando indicamos que un método es synchronized, Java realiza automáticamente esta desactivación y posteriormente la activación. Todos los objetos que estén haciendo uso del código del método en cuestión recibirán el correspondiente mensaje suspend(), exceptuando el que tenga la atención de la máquina virtual de Java en ese instante. Una vez concluya la ejecución del método que debe ser sincronizado (fue definido como synchronized) todos los procesos que fueron temporalmente paralizados recibirán el mensaje resume(). Esta tarea sería ardua si tuviéramos que realizarla manualmente. Gracias a que Java fue diseñado desde un principio para ser un entorno multiproceso podemos despreocuparnos ahora de estas pequeñeces. Para una parada definitiva haremos uso del método stop(). ¿Qué ocurre si necesitamos que la nueva clase que vamos a definir tome ciertas características de, por ejemplo, la clase Impresora que no hereda de Thread?; ¿podemos en Java heredar de dos clases a la vez?; ¿cómo podemos resolver este pequeño inconveniente con lo que ya sabemos?. Efectivamente la respuesta negativa a la segunda pregunta nos pone en la pista para dar una respuesta correcta a la tercera pregunta. Y esta respuesta no ha de ser otra que los interfaces, de los que ya se ha hablado en capítulos anteriores a lo largo del presente curso. Contestando de este modo la tercera pregunta, la respuesta a la primera pregunta es clara: deberemos aprender a utilizar la interfaz Runnable. Sustituyamos el primer paso que comentábamos anteriormente por este otro: 1. Debemos implementar el interfaz Runnable. public class MiNuevaImpresora extend Impresora implements Runnable{ public void run(){ // Lo que deseemos que caracterice a MiNuevaImpresora } } Código fuente 235 Ahora deberemos crear un proceso. Pero deberemos remitir al constructor de la clase Thread un objeto de la clase MiNuevaImpresora, que va a ser la clase que va a contener el método run() que ejecutará el proceso correspondiente. 338 © Grupo EIDOS 16. Aspectos avanzados de Java: procesos Thread oHiloParaMNI; MiNuevaImpresora oMNI; oMNI = new MiNuevaImpresora(); oHiloParaMNI = new Thread( oMNI ); oHiloParaMNI.start()// Ejecutará el método run() de la clase de oMNI Código fuente 236 Tras declarar un objeto de la clase Thread, otro de la clase MiNuevaImpresora y construir el objeto oMNI a partir de las especificaciones de la clase MiNuevaImpresora procedemos a crear el proceso vinculándolo al objeto oMNI. Este último paso no nos debe dejar indiferentes por dos motivos básicos: primero porque es fundamental para poder implementar clases que se desarrollen dentro de un proceso y que hereden sus características de otra clase diferente a Thread, segundo ya que es de suma importancia para comprender como funciona la característica de las interfaces en Java. Gracias a que indicamos que la clase MiNuevaImpresora implementaba la interfaz Runnable disponemos del método run() para una clase que no heredó de Thread, o mejor dicho, nos obligamos a codificar un método run() para dicha clase. Cuando se ejecute el método start() parte del proceso consistirá en enviar el mensaje run() sobre el objeto que fue remitido como parámetro (recuérdese que oMNI fue remitido como argumento en el momento de crear el oHiloParaMNI). Este objeto oMNI será guardado en una variable interna del objeto Thread, llamémosla por ejemplo oOwner, cuando se esté ejecutando el método start(), éste comprobará si oOwner es un objeto, en caso afirmativo se enviará sobre él el mensaje run(). Apoyándonos en los dos siguientes códigos vamos a mostrar un ejemplo algo más completo que el anterior. El primero de los códigos fuente corresponde al archivo SencilloEjRunnable.java y contiene la definición de la clase SencilloEjRunnable que implementa la interfaz Runnable. En el ejemplo, y en aras de una mejor didáctica, el método run() de esta clase tan sólo muestra un mensaje con el nombre del proceso en la consola (pantalla). public class SencilloEjRunnable implements Runnable{ public void run(){ System.out.println( "En el hilo llamado'" + Thread.currentThread().getName() + "'" ); } } Código fuente 237 Pero si SencilloEjRunnable no hereda de Thread....¿Cómo se averigua el nombre del Thread sobre el que se ejecuta?. Si SencilloEjRunnable fuera una clase que hubiera heredado de Thread la respuesta sería clara: el objeto Thread sería el mismo, this. Pero al no ser una clase que herede de Thread, sino una que implementa la interfaz Runnable, hemos de poder referirnos de alguna manera al objeto Thread dentro del marco del cual se está ejecutando SencilloEjRunnable. Por medio del método estático currentThread() lanzado sobre el objeto genérico, identificado por Thread, accedemos al objeto que refleja al proceso en cuestión. Una vez que tenemos dicho objeto 339 Programación en Java © Grupo EIDOS enviamos sobre éste el mensaje getName(), existente para la clase Thread, que nos devuelve el nombre del hilo de ejecución. El Código fuente 238 corresponde al archivo PruebaSER.java y contiene la definición de la clase PruebaSER. Estamos utilizando una aplicación de consola, por lo que definimos un método main(). Como ya sabemos, en este método main() situamos la lógica que deseamos realice nuestra aplicación al ser lanzada. En PruebaSER la tarea a realizar será la creación indefinida de procesos que sigan el modelo definido en la clase SencilloEjRunnable. public class PruebaSER{ public static void main( String argv[] ){ SencilloEjRunnable oSER; oSER = new SencilloEjRunnable(); while( true ){ Thread oH = new Thread(oSER); if (oH==null) System.out.println( "new Thread() falló."); else System.out.println( "new Thread() correcto."); oH.start(); // Espera a que la hilo finalice su método run() try{ oH.join(); } catch( InterruptedException ex) {} } } } Código fuente 238 Tras declarar la clase el método main(), y crear el objeto oSER, que representa una instancia de la clase SencilloEjRunnable nos introducimos en el bucle infinito. Creamos el proceso para el objeto oSER y lo almacenamos en el objeto oH. Posteriormente visualizamos un mensaje en la consola indicando si el proceso de construcción del objeto Thread fue satisfactorio o no. Acto seguido ponemos a funcionar el proceso e indirectamente también nuestra clase SencilloEjRunnable, se debe recordar, que el método run() de SencilloEjRunnable será ejecutado por el método start() del hilo recién creado. El objeto oSER fue remitido como parámetro en la construcción del objeto oH, acción por la que fueron vinculados. Como última acción el bucle se introduce en una espera por medio del envío del mensaje join() sobre el proceso. El método join() espera a que el proceso termine. El programa queda a la espera de que el método run() del proceso finalice. Cuando esto suceda una excepción InterruptedException será enviada. 340 © Grupo EIDOS 16. Aspectos avanzados de Java: procesos Es importante detenerse a evaluar las implicaciones que puede tener un join() infinito en cada caso. No tenemos ninguna seguridad del tiempo que tomará un método run() en completarse. En el ejemplo es inmediato puesto que lo único que hace es imprimir un literal en pantalla. Si necesitamos hacer un join() temporizado también disponemos de esta posibilidad ya que el mensaje admite como parámetro la temporización en milisegundos y opcionalmente nanosegundos. En la Figura 150 podemos ver parte de la salida que produce esta aplicación. Figura 150 Si ejecutamos este código para detenerlo deberemos abortar su ejecución, ya que hemos construido un bucle infinito. Se propone en este segundo ejemplo una variación sencilla respecto a la clase PruebaSER. Esta variación reside únicamente en que el nombre que tendrá el proceso lo fijamos nosotros en lugar de permitir que sea Java quien lo nombre. Para esta tarea sólo hemos de indicar el nombre que deseamos como parámetro en la línea del fuente donde se construye el proceso. Thread oH = new Thread( oSER, nCuenta + " hilos lanzados."); Código fuente 239 La variable nCuenta es un contador que almacena el número de proceso que se llevan lanzados. Dicha variable debe de incrementarse, lógicamente, dentro del bucle. La ventaja de poder nombrar como deseemos es obvia, identificaremos de modo mucho más fácil un proceso al que seamos nosotros quienes demos nombre. Sabemos como parar un proceso por medio del método stop(). Pero Si necesitamos realizar alguna acción cuando pare un proceso ¿Cómo debemos estar a la escucha de esta eventualidad?. La respuesta se encuentra ligada a la manipulación de excepciones, pues es por medio de ellas como nos enteramos que un proceso a muerto. Cuando se lanza el método stop() sobre un proceso, éste lanza una excepción del tipo ThreadDeath. Nuestro cometido es habilitar la correspondiente construcción try-catch que atienda esta excepción. public class Prueba{ public static void main( String argv[] ){ SencilloEjRunnable oSER; 341 Programación en Java © Grupo EIDOS oSER = new SencilloEjRunnable(); Thread oH = new Thread(oSER); try{ oH.start(); //... // El código que en alguna situación enviará stop() //... } catch( ThreadDeath oTD ){ // lo que deseamos hacer cuando se detenga el hilo // por medio de stop() throw oTD; } } } Código fuente 240 Es importante indicar que esta construcción sólo resuelve la captura del final de un hilo si éste es parado por medio de stop(). Un hilo puede pararse por otros motivos diferentes a la ejecución del método stop(), por ejemplo lanzando alguna excepción. Si necesitamos ejecutar algún código sea cual sea el camino por el que finalice una hilo, lo situaremos en la cláusula finally de la construcción trycatch. Vamos a ver un ejemplo más de la utilización de hilos de ejecución, en este caso va a ser un ejemplo más vistoso, se trata de crear un applet que realice las funciones de un reloj, es decir, va a mostrar la hora actual del sistema y se va a ir actualizando cada segundo. La clase de este ejemplo se va a denominar RelojApplet, esta clase hereda de la clase Applet e implementa el interfaz Runnable. Su código fuente es el Código fuente 241. import java.awt.*; import java.util.Calendar; import java.util.Date; import java.text.DateFormat; import java.applet.*; import java.awt.event.*; //implementamos el interfaz Runnable porque tenemos un hilo de ejecución public class RelojApplet extends Applet implements Runnable { private Thread relojThread=null; private boolean pulsado; public void init(){ pulsado=true; addMouseListener(new AdaptadorRaton()); } public void start() { if (relojThread == null) { relojThread = new Thread(this, "reloj"); //se inicia el hilo de ejecución //que ejecutará el método run() relojThread.start(); } } public void run() { Thread miThread = Thread.currentThread(); while (relojThread == miThread) { repaint(); try { //realizamos una pausa de 1 segundo (1000 milisegundos) relojThread.sleep(1000); 342 © Grupo EIDOS 16. Aspectos avanzados de Java: procesos //Thread.sleep(1000); }catch (InterruptedException e){ } } //finaliza el hilo de ejecución } public void paint(Graphics g) { //para obtener la hora actual Calendar calendario = Calendar.getInstance(); Date fecha = calendario.getTime(); //formato de la hora DateFormat formatoFecha = DateFormat.getTimeInstance(); g.setFont(new Font("Arial",Font.ITALIC+Font.BOLD,14)); g.drawString(formatoFecha.format(fecha), 5, 10); } public void stop() { //se destruye el hilo de ejecución relojThread = null; } class AdaptadorRaton extends MouseAdapter{ //al pulsar sobre el applet se parará o reanudará el hilo public void mousePressed(MouseEvent evento){ AppletContext contexto=getAppletContext(); if (pulsado){ relojThread.suspend(); contexto.showStatus("Hilo en pausa"); }else{ relojThread.resume(); contexto.showStatus("Hilo en ejecución"); } pulsado=!pulsado; } } } Código fuente 241 Como se puede apreciar en el método run() se realiza una pausa de un segundo, ya que como es lógico el reloj se actualizará cada segundo, para ello se lanza el método repaint(), que llamará paint(). El método paint() del applet únicamente recupera la hora actual y le aplica el formato deseado, a continuación se dibuja la cadena en la superficie del applet, que va a representa la hora actual. Al pulsar con el ratón sobre la superficie del applet se parará o se iniciará el hilo de ejecución, atendiendo al valor del atributo de tipo booleano pulsado, que hace las veces de interruptor. En la Figura 151 se puede ver un ejemplo de ejecución de este applet. Figura 151 343 Programación en Java © Grupo EIDOS Coordinando los procesos El planificador de procesos de un sistema operativo que los permita, debe poner orden a la ejecución de los mismos. Igualmente ha de fijar prioridades a la solicitud de los tiempos-máquina solicitados por cada uno de los procesos implicados en la realización de una serie de tareas en un instante determinado. Para esta tarea se han realizado abordajes diferentes dando lugar a dos tipos fundamentales de coordinación de procesos: Sin derecho preferente y con derecho preferente Sin derecho preferente En este tipo de planificadores la decisión de ceder el control a otro proceso recae sobre el hilo en ejecución. Por este motivo la cortesía es el factor clave en este tipo de planificadores, para un reparto equitativo del tiempo. Es ideal para aplicaciones críticas o en tiempo real en las que un proceso no puede estar detenido por mucho tiempo porque haya muchos otros ejecutándose al mismo tiempo. Con derecho preferente En las aplicaciones modernas se ha implantado otro tipo de distribuidor. En estos nuevos planificadores es tarea del mismo decidir que proceso se ejecuta en cada instante. Esta característica facilita enormemente la codificación de programas con procesos. Actualmente también podemos definir una escala de prioridades, para que el planificador ceda más o menos tiempo en función de ésta. El planificador de Java puede ser informado de la prioridad de un hilo por medio del método setPriority() de la clase Thread. Remitiendo al mismo un valor comprendido entre las constantes MIN_PRIORITY (de valor 1) y MAX_PRIORITY (de valor 10). 344 Aspectos avanzados de Java: canales y Sockets Introducción a los canales La comunicación de Java con el "exterior" se realiza a través de canales o flujos, que no son más que caminos que usan los programas para comunicarse con otros programas o con cualquier dispositivo de entrada o salida, y por los que se transmite la información. Gracias a esta manera de enviar información, el origen y el destino de los datos no necesitan conocerse, ni saber qué va a hacer cada uno de ellos con los datos, ni qué tipo de datos se envía, ni por qué medio se está transmitiendo la información. Esto es así debido a que todas las operaciones de lectura y escritura se realizan sobre el canal. Así, leeríamos igual de un canal que viniera de un fichero que de uno que llegara desde Internet. Esta independencia permitiría que si en un programa cambiase la fuente de los datos, sólo habría que especificar el nuevo canal y no haría falta cambiar la forma en que se tratan los datos. Esta independencia que nos dan los canales tiene sus límites, pues, en algún momento, tendremos que decirle al programa dónde queremos que nos escriba los datos o de dónde los queremos leer. Es más, hay canales para el tipo de datos que enviamos o recibimos, ya sean caracteres unicode o bytes; dentro de esa división hay otra dependiendo del origen o destino de los datos: memoria, fichero, ...; y finalmente hay otra clasificación según el tipo de operación que realizan los canales con los datos: los que realizan alguna operación y los que no hacen nada con los datos. Programación en Java © Grupo EIDOS Como vemos, esa supuesta independencia que nos dan los canales es sólo a la hora de tratarlos, ya que cuando tenemos que crearlos la cosa se complica un poco y aquí sí que tenemos que saber por dónde van a ir los datos, qué tipo de datos vamos a tratar y lo que vamos a hacer con ellos. Al tratar con canales de datos (en inglés streams, que literalmente serían corrientes de datos), debemos tener en cuenta dos cosas: • Cuando accedemos a un canal se bloquean los demás procesos. Para evitar que el canal bloquee la ejecución, lo normal será colocarlo en un hilo de ejecución separado. • Si se produce algún error mientras se opera con un canal, se lanzará una excepción del tipo IOException, por lo tanto tenemos que tratar estas posibles excepciones introduciendo la operación con el canal en un bloque try-catch. Ahora que ya sabemos lo que es un canal de datos, vamos a empezar por los más sencillos que seguro ya hemos usado en distintos ejemplos: los canales estándar. Luego continuaremos con los canales especializados del paquete java.io. Canales estándar de entrada/salida Los canales estándar vienen definidos en la clase System y ya los hemos usado aunque no supiéramos que se consideraban canales. Los canales estándar nos comunican, por defecto, con la pantalla y el teclado. La salida o entrada de estos canales se puede redirigir, pero en el caso de que queramos hacerlo, será mejor usar uno de los canales especializados que veremos en el paquete java.io . La clase System implementa tres canales de comunicación: • Salida estándar (System.out): es la salida por pantalla que ya hemos usado desde nuestras aplicaciones. No la usamos desde los applets ya que en ellos no escribimos directamente en la pantalla (no está permitido). Los métodos más habituales son print() y println(). // Muestra ‘Hola’. Deja el cursor al final del texto System.out.print("Hola"); // Muestra ‘Adiós’ y salta de línea System.out.println("Adiós"); Código fuente 242 • Errores estándar (System.err): es un canal especial cuya única diferencia con la salida estándar es que no puede ser redirigido: los datos siempre saldrán por pantalla. Por lo demás es igual que System.out y comparte sus métodos. System.err.println("¡Esto es un error!"); Código fuente 243 346 © Grupo EIDOS • 17. Aspectos avanzados de Java: canales y Sockets Entrada estándar (System.in): es la entrada por teclado. El método más usado es read() con el que leeríamos caracteres hasta que le especifiquemos en una condición. El método read() tiene un comportamiento que merece ser explicado. El método read() devuelve un entero. Para estudiar su funcionamiento, vamos a ver un ejemplo muy simple: un programa que lee del teclado y luego nos muestra por pantalla lo que hemos escrito. import java.io.*; class LecturaEstandar{ public static void main(String args[]) throws IOException{ int caracter; System.out.println("Escribe lo que quieras. Para terminar, pulsa ENTER"); System.out.println(); while ( (caracter = System.in.read()) != 13 ) System.out.println(caracter); } } Código fuente 244 A la vista de la ejecución de este programa nos podemos preguntar dónde guarda los caracteres que vamos introduciendo, ya que no hemos definido ningún array de enteros y sin embargo nos muestra todos los caracteres. Lo que ocurre cuando llamamos a read() es que se crea un canal de entrada que sólo se lee cuando pulsamos la tecla ENTER (en la entrada por teclado no hay fin de fichero). Una vez que lo hemos pulsado, read() leerá el canal secuencialmente, comprobando la condición para dejar de leer. Mientras lee el canal, va mostrando los caracteres por la pantalla, de uno en uno. En la Figura 152 se puede ver un ejemplo del resultado. Figura 152 Si cambiásemos la condición para dejar de leer y le forzáramos a terminar al escribir ‘A’, la condición sería la que se muestra en el Código fuente 245. 347 Programación en Java © Grupo EIDOS while ((caracter = System.in.read()) != 65) // Código ASCII de ‘A’ Código fuente 245 Al ejecutarlo ahora veríamos lo que se muestra en la Figura 153 Figura 153 Esta vez nos deja escribir (llenar el canal) hasta que pulsamos ENTER. Una vez pulsado, es cuando se analizan los datos que hay en el canal, y vemos que sólo lee las cuatro ‘a’ ya que al llegar a la ‘A’, se cumple la condición de salida del bucle e ignora el resto de los datos que hay en el canal. Con estos tres canales podemos realizar operaciones básicas, pero no es suficiente para manejar ficheros, leer de Internet, filtrar datos, etc.... Para estas operaciones más específicas usaremos los canales que nos proporciona el paquete java.io. Canales de java.io Las clases que ofrece el paquete java.io para manejar los distintos tipos de canales se dividen en el tipo de dato que manejan, ya sean caracteres o bytes. La diferencia entre los dos tipos es que Java usa caracteres Unicode de 16 bits y los bytes tienen 8 bits. Normalmente no hay diferencia al usar uno u otro, ya que si usamos un sistema operativo que use caracteres tradicionales de 8 bits, Java, al leer un carácter, leerá 8 bits y no 16. Esto lo hace casi siempre, pero en algunos casos sí puede guardar un carácter de 16 bits cuando nosotros queremos guardar uno de 8. Por esto, lo más recomendable es trabajar con canales de bytes a no ser que estemos seguros de que queremos usar caracteres Unicode. Como los canales de byte y de carácter se comportan prácticamente igual, solo que unos tratando bytes y los otros tratando caracteres, sería más lógico clasificar los canales por la forma de tratar los datos. Atendiendo a esto, tenemos otros dos tipos de canales: 348 • Canales de transmisión (Data Sink Streams): son canales que no realizan ninguna operación con los datos que leen o escriben en el canal. Se limitan a transmitir los datos. • Canales de proceso (Processing Streams): son canales que sí realizan alguna operación con los datos antes de que los reciba el programa. Estos canales actúan sobre un canal de transmisión, es decir, el canal de transmisión establece el flujo de datos y el canal de proceso trabaja sobre los datos que hay en ese canal. Algunos procesos son: filtrado, concatenación de canales, conversión de datos, recuento de líneas, etc... © Grupo EIDOS 17. Aspectos avanzados de Java: canales y Sockets Todas las clases son subclases (clases hijas) de InputStream y de OutputStream que definen un canal cualquiera de entrada y otro de salida. Gracias a estas superclases, el tratamiento de los canales es prácticamente igual, salvando las peculiaridades de cada uno en particular. Estas clases tienen unos métodos comunes para todos los canales. Los más importantes son read() y write() para leer y escribir, pero implementan otros que no todos los canales pueden usar satisfactoriamente. Este uso de métodos comunes es lo que permite la independencia del canal a la hora del tratamiento: podemos usar read() en todos los canales de entrada y write() en los de salida. Luego quedan las operaciones especiales que tenga cada canal que será por lo que le hemos elegido: no vale cualquier canal para cualquier cosa. Los métodos read() de InputStream son: • int read() • int read(byte cadena[]) • int read(byte cadena[], int comienzo, int longitud) El primer método lee un byte y nos devuelve su valor en forma de entero. El segundo lee una cadena de bytes hasta que no haya más datos disponibles y la guarda en la variable cadena. Devuelve el número de bytes leídos. El tercero lee una cadena de bytes desde comienzo hasta que lea los bytes especificados en longitud o no tenga más datos para leer, y los guarda en cadena. Devuelve el número de bytes leídos. Los mismos métodos están disponibles para leer caracteres. Los métodos write() de OutputStream son similares: • write(int c) • write(char cadena[]) • write(char cadena[], int comienzo, int longitud) Estos métodos no creo que necesiten explicación. La jerarquía de clases la vemos en las siguientes imágenes. Aquí están separadas por tipo de dato que manejan, si leen o escriben y las operaciones que realicen con los datos (canales de transmisión en gris y canales de proceso en el color de fondo). Canales que manejan caracteres (Figura 154 y Figura 155) Figura 154 349 Programación en Java © Grupo EIDOS Figura 155 Canales que manejan bytes (Figura 156 y Figura 157). Figura 156 Figura 157 Pasemos, ahora, a explicar los distintos tipos de canales. Canales de transmisión (Data Sink Streams) Los canales de transmisión se usan para leer o escribir cadenas de caracteres, ficheros o pipes. Los pipes son un tipo de canal especial que veremos luego. En la Tabla 24 se muestran los canales de transmisión. 350 © Grupo EIDOS 17. Aspectos avanzados de Java: canales y Sockets Origen/Destino Canales de Carácter Canales de Byte Memoria CharArrayReader, ByteArrayInputStream, CharArrayWriter ByteArrayOutputStream StringReader, StringBufferInputStream StringWriter Pipe Fichero PipedReader, PipedInputStream, PipedWriter PipedOutputStream FileReader, FileInputStream, FileWriter FileOutputStream Tabla 24 Como podemos ver, por cada canal de carácter hay uno de byte y por cada canal de lectura hay uno de escritura (con excepción de StringBufferInputStream). Los canales que escriben o leen de la memoria se crean sobre un array o una cadena existente. Para ver como funcionan los canales que trabajan con ficheros, llamados canales de fichero, veamos el Código fuente 246 que los copia. import java.io.*; public class CopiaFichero{ public static void main(String args[]) throws IOException{ FileReader entrada = new FileReader("entrada.txt"); FileWriter salida = new FileWriter("salida.txt"); int caracter; while ((caracter=entrada.read()) != -1) salida.write(caracter); entrada.close(); salida.close(); } } Código fuente 246 Este programa va a leer el fichero ENTRADA.TXT y lo va a copiar en SALIDA.TXT. ENTRADA.TXT debe existir y puede ser cualquier fichero de texto (un TXT, un BAT, etc...). La lectura del fichero de entrada finaliza cuando se ha alcanzado el final del fichero, esto se identifica mediante el valor –1. Aquí vemos que el uso de los métodos read() y write() no depende de que el canal sea de una clase o de otra. Si leyéramos un String de la memoria y lo escribiéramos en el disco, sólo tendríamos que cambiar el canal, no el método de lectura. Cambiando el Código fuente 247. 351 Programación en Java © Grupo EIDOS FileReader entrada = new FileReader("entrada.txt"); Código fuente 247 Por el Código fuente 248. String cadena="Esta cadena es un ejemplo"; StringReader entrada = new StringReader(cadena); Código fuente 248 Tendremos un programa que lee de la memoria y escribe en un fichero. Y sólo hemos cambiado el canal. Los canales pipe son un tipo especial algo complejo que se usa para pasar información de un hilo de ejecución a otro controlando toda la sincronización automáticamente. Para usar los pipe hay que crear uno de entrada y otro de salida y relacionarlos, como muestra el Código fuente 249 PipedInputStream entrada = new PipedInputStream(); PipedOutputStream salida = new PipedOutputStream(entrada); Código fuente 249 De estos canales no vamos a ver ningún ejemplo porque son bastante complejos y se sale de las expectativas del curso. Canales de proceso (Processing Streams) Los canales de proceso realizan alguna operación con los datos antes de recuperar o de introducir los datos en el canal. Estos canales actúan sobre un canal de transmisión que les proporciona los datos. Proceso Canales de Carácter Canales de Byte Buffering BufferedReader, BufferedInputStream, BufferedWriter BufferedOutputStream Filtrado Conversión entre Bytes y Caracteres Unión de Canales, concatenación 352 FilterReader, FilterInputStream, FilterWriter FilterOutputStream InputStreamReader, OutputStreamWriter SequenceInputStream © Grupo EIDOS 17. Aspectos avanzados de Java: canales y Sockets Serialización de Objetos ObjectInputStream, ObjectOutputStream Conversión de Tipos DataInputStream, DataOutputStream Contador de Líneas LineNumberReader Vuelta Atrás PushbackInputStream Impresión PrintWriter LineNumberInputStream PrintStream Tabla 25 Estos canales, al crearse a partir de uno de transmisión, se usan sobre todo para optimización de accesos o para añadir funcionalidades extra. Vamos a comentar estos canales. Canales de Filtrado Son unas clases abstractas de las que heredan los canales de buffering, conversión de tipos, contador de líneas, vuelta atrás e impresión. Canales de buffer Son uno de los tipos de canales más importantes, ya que optimizan el acceso a los datos guardándolos en memoria, limitando así los accesos a la fuente de los mismos. Son los únicos canales que utilizan adecuadamente los métodos mark() y reset() de la superclase InputStream, el método mark() marca una posición en el canal y el método reset() vuelve a ella aunque nos hayamos movido. Vamos a ver un ejemplo que usa un BufferedReader para leer el código HTML de una página Web y un FileWriter para guardarla en disco. Por cierto, esta página se puede abrir en el explorador. import java.net.*; // En este paquete está la definición de la URL import java.io.*; public class LeerUrl{ public static void main(String args[]) throws Exception{ URL eidos = new URL("http://www.eidos.es/"); FileWriter salida = new FileWriter("salida.htm"); BufferedReader entrada = new BufferedReader (new InputStreamReader (eidos.openStream())); int caracter; while ((caracter=entrada.read()) != -1) salida.write(caracter); entrada.close(); salida.close(); } } Código fuente 250 353 Programación en Java © Grupo EIDOS La forma de usar los canales de proceso es a partir de un canal de transmisión, pero en este caso estamos usando InputStreamReader, que hemos dicho que es un canal de proceso. Esto es porque InputStreamReader proporciona datos: es un canal como los de transmisión pero que pasa los bytes a caracteres al leerlos y convierte los caracteres en bytes al escribirlos. Es el único canal que se comporta como uno de transmisión. También se pueden anidar canales. Por ejemplo, definir un canal PrintWriter a partir de entrada, que es un canal BufferedReader creado a partir de un InputStreamReader. Con esto conseguimos la funcionalidad de los tres canales en uno. Este ejemplo funcionaría igual si no usáramos BufferedReader, pero el acceso al canal es mejor usándolo. Además, los canales de buffer se van a usar mayoritariamente para optimizar las operaciones de lectura y escritura, no porque sean estrictamente necesarios. También podemos apreciar que la forma de leer es siempre la misma aunque cambiemos de canal. Este programa está hecho con canales de carácter, pero se puede hacer igualmente con canales de byte: BufferdedInputStream y FileOutputStream. Canal de Concatenación El canal de concatenación une varios canales en uno sólo. Como parámetros acepta dos canales de transmisión del tipo InputStream, o bien una lista de estos del tipo Enumeration, que es un interfaz especial para hacer listas de datos. Vamos a ver un ejemplo, en el Código fuente 251, de concatenación de dos canales. import java.io.*; public class UnirFicheros{ public static void main(String args[]) throws IOException{ FileInputStream entrada1 = new FileInputStream("hola.txt"); FileInputStream entrada2 = new FileInputStream("adios.txt"); SequenceInputStream ficheros = new SequenceInputStream(entrada1, entrada2); int c; while ((c = ficheros.read()) != -1) System.out.write(c); ficheros.close(); } } Código fuente 251 Este programa abre dos canales que leen de los ficheros de texto HOLA.TXT y ADIOS.TXT, y los une en un solo canal SequenceInputStream cuyo contenido se muestra en la pantalla. Canales de conversión de tipos Hasta ahora hemos leído los datos como carácter o byte. Para leer o escribir datos de otros tipos tenemos los canales DataInputStream y DataOutputStream con los que podemos grabar datos de tipo boolean, short, int, long, float y double, además de char y byte. 354 © Grupo EIDOS 17. Aspectos avanzados de Java: canales y Sockets Para comprobar su funcionamiento, tenemos este ejemplo que graba un fichero con datos de tipo int y String, separados por un tabulador. Luego leemos de ese fichero, sacando la información por pantalla y haciendo un total de uno de sus campos. import java.io.*; public class PruebaTipos{ public static void main(String[] args) throws IOException{ PruebaTipos obj = new PruebaTipos(); obj.escribeDatos(); obj.leeDatos(); } public void escribeDatos() throws IOException{ DataOutputStream salida = new DataOutputStream( new FileOutputStream("datos.dat")); int[] precios = { 3000, 5000, 7000, 6500, 750 }; int[] unidades = { 12, 8, 13, 29, 50 }; String[] descripcion = { "Camisetas","Bañadores","Pantalones", "Zapatillas","Calcetines" }; for (int i = 0; i < precios.length; i ++){ salida.writeInt(precios[i]); salida.writeChar('\t'); salida.writeInt(unidades[i]); salida.writeChar('\t'); salida.writeBytes(descripcion[i]); salida.writeChar('\n'); } salida.close(); } public void leeDatos() throws IOException{ DataInputStream entrada = new DataInputStream( new FileInputStream("datos.dat")); int precio; int unidad; String desc; int total = 0; try{ while (true){ precio = entrada.readInt(); entrada.readChar(); // Ignoramos el tabulador unidad = entrada.readInt(); entrada.readChar(); // Ignoramos el tabulador desc = entrada.readLine(); System.out.println("Has pedido " + unidad + " unidades de " + desc + " a " + precio + " pts."); total = total + unidad * precio; } } catch (EOFException e) {} System.out.println("Por un total de: " + total + " Pts."); entrada.close(); } } Código fuente 252 Lo primero que hacemos es crearnos un canal de salida capaz de tratar tipos a partir de un canal de fichero. Una vez que definimos los datos, los grabamos en columnas (separando los elementos por tabuladores). Creamos el fichero en disco y lo leemos. Si observamos datos.dat veremos que no distinguimos las cantidades: ya no las guarda como caracteres o bytes reconocibles. 355 Programación en Java © Grupo EIDOS El segundo método de la clase, crea un canal de entrada para leer tipos a partir de un fichero. Definimos los campos que contiene el archivo y los leemos ignorando el tabulador que los separa (lo leemos sin hacer nada con él). En el bucle de lectura encontramos algo nuevo: ya no leemos hasta encontrar el final de los datos, sino que tenemos el código de lectura en un bucle infinito dentro de un bloque try-catch. Esto se hace así porque tenemos que leer varios datos diferentes. En este caso no podríamos repetir el bucle de lectura que hemos estado usando siempre, ya que no podemos asignar el resultado de una lectura a un solo dato. En todos los casos hemos estado usando el Código fuente 253. while ((c = ficheros.read()) != -1) Código fuente 253 Con un canal de tipos, no podemos recoger la lectura en una sola variable puesto que tenemos que leer varias (en nuestro caso el precio, la descripción y la cantidad). La manera de poder leerlo todo es poner el proceso de lectura en un bucle infinito dentro de un bloque try-catch, del que sólo se saldrá cuando se alcance el final del fichero y se lance la excepción de fin de fichero que recogemos sin hacer nada. Si compilamos este ejemplo, el compilador nos dará el siguiente mensaje: String readLine() ha sido desaprobado por el autor de java.io.DataInputStream. Este mensaje indica que no es recomendable utilizar este método ya que se ha quedado obsoleto, nosotros lo hemos utilizado y dejado en nuestro código por simplicidad, ya que sino tendríamos que utilizar un canal más del tipo BufferedReader. En la Figura 158 se puede observar la ejecución de este ejemplo. Figura 158 Serialización de Objetos Ya hemos visto cómo escribir o leer tipos de datos en un canal, pero entre esos tipos no están los objetos. La serialización de objetos consiste en poder enviar objetos a un canal y luego reconstruir el objeto y el valor de sus atributos en el otro extremo del canal. Para reconstruir un objeto se tiene que importar el paquete donde está definido en el destino. Para ver cómo funciona, tenemos el Código fuente 254 que escribe un objeto de tipo Date en un canal y luego lo recupera mostrándonos los valores que tenía cuando se creó. 356 © Grupo EIDOS 17. Aspectos avanzados de Java: canales y Sockets import java.io.*; import java.util.Date; public class Serializacion{ public static void main(String args[]) throws Exception{ FileOutputStream fichero = new FileOutputStream("salida.dat"); ObjectOutput salida = new ObjectOutputStream(fichero); salida.writeObject("Hoy"); salida.writeObject(new Date()); salida.flush(); salida.close(); while (System.in.read() != 13); FileInputStream fichero1 = new FileInputStream("salida.dat"); ObjectInputStream entrada = new ObjectInputStream(fichero1); String hoy = (String)entrada.readObject(); Date fecha = (Date)entrada.readObject(); System.out.println(hoy); System.out.println(fecha); entrada.close(); } } Código fuente 254 En la primera parte, creamos un canal de serialización a partir de uno de fichero. Podemos definir el tipo del canal como ObjectOutput ya que es una superclase de ObjectOutputStream, aunque también podríamos haber puesto directamente ObjectOutputStream, como haremos con el canal de entrada. Luego escribimos dos objetos: uno de la clase String y otro de la clase Date. Cuando llamamos al constructor de Date sin parámetros, nos crea un objeto con la fecha y la hora actuales. Una vez escritos, lo guardamos en disco y cerramos el canal. Esperamos a que se pulse la tecla ENTER para apreciar el cambio de hora desde que se creó el objeto Date hasta que mostramos su valor. La segunda parte es muy parecida a la primera: creamos un canal de lectura a partir de uno de fichero. Para leer los objetos usamos el método readObject() de la clase ObjectInputStream. Tenemos que definir un objeto del tipo que vamos a leer y hacer un casting a ese tipo en el readObject(). Los objetos se leen en el orden en que fueron escritos. Una vez leídos, los mostramos comprobando que la hora que vemos es la de creación. Nosotros también podemos serializar nuestras propias clases sólo con que estas implementen el interfaz Serializable. Afortunadamente, este interfaz está vacío y sólo actúa a modo de etiqueta. public class MiClase implements Serializable Código fuente 255 Solamente con esta línea, todos los objetos de esta clase serán serializables. Canal Contador de Líneas En Java hay un canal especial que reconoce los números de línea: LineNumberReader. Este canal sólo se debe usar en canales de carácter. Java mantiene LineNumberInputStream por compatibilidad con 357 Programación en Java © Grupo EIDOS versiones anteriores. LineNumberInputStream no se debe usar porque un canal de bytes puede tener cualquier tipo de dato y no podemos dar por supuesto que vaya a llevar sólo caracteres. Este canal es muy sencillo de usar, como vamos a ver en el Código fuente 256. import java.io.*; public class ContarLineas{ public static void main(String args[]) throws IOException{ FileReader fichero = new FileReader("entrada.txt"); LineNumberReader entrada = new LineNumberReader(fichero); String s; while ((s=entrada.readLine())!=null){ System.out.print("Linea "+(entrada.getLineNumber())+": "); System.out.print(s); System.out.println(); } } } Código fuente 256 La creación del canal es como la de todos los de proceso. ENTRADA.TXT debe existir y es un fichero de texto. Para leer usamos el método ReadLine() que tienen todos los canales de entrada. El método de LineNumberReader que nos proporciona el número de línea en el que estamos es getLineNumber(). Leemos hasta que la cadena que devuelve readLine() sea nula (aquí no se alcanza el final de fichero). Un ejemplo de ejecución del Código fuente 256 se puede observar en la Figura 159. Figura 159 Canales de impresión Estos canales sólo proporcionan métodos de impresión a otros canales. Nos permiten mostrar los datos en una gran cantidad de formatos. Son unos canales muy sencillos de usar, por lo que a menudo se verá un canal de impresión creado a partir de cualquier otro canal para facilitar su uso. Sus métodos principales son print() y println(), que están sobrecargados para soportar cualquier tipo de dato. Estos métodos son los que usan System.out y System.err. 358 © Grupo EIDOS 17. Aspectos avanzados de Java: canales y Sockets Canales de vuelta atrás Este tipo de canal implementa la posibilidad de retroceder en la lectura de un canal. Hasta ahora los canales que hemos visto se tratan con avances secuenciales, exceptuando el uso de los métodos mark() y reset(), que sólo funcionan bien en los canales con buffer. Con PushbackReader y PushbackInputStream podemos retroceder tantos caracteres o bytes como podamos meter en el buffer de PushbackReader y PushbackInputStream. Esto significa que podemos leer datos varias veces, ya que después de retroceder, leeremos otra vez los mismos datos. Como lenguaje orientado a objetos que es Java, hemos visto cómo separa todos los elementos a la hora de tratar con datos de fuentes externas. Esto le proporciona una potencia considerable a la hora de añadir nuevas funcionalidades o nuevos dispositivos de lectura o escritura: en definitiva, nuevos canales. El uso de canales está bastante restringido en los applets, ya que desde éstos no podemos acceder a ningún fichero que esté en la máquina del usuario, y no podemos cargar ni ejecutar ningún programa. Por lo tanto, todos estos apartados dedicados a los canales están pensados para aplicaciones, así mismo se refleja en los ejemplos que se adjuntan. Aplicaciones cliente/servidor en Java Dentro del entorno de las aplicaciones Java vamos a tratar un tema de gran interés: la arquitectura cliente/servidor. Más concretamente se va a comentar dentro de este apartado las herramientas que ofrece Java para poder realizar aplicaciones dentro del entorno de la arquitectura cliente/servidor. Las comunicaciones en Internet se realizan a través de un protocolo llamado TCP/IP ( Transmission Control Protocol/Internet Protocol). TCP/IP es un conjunto de protocolos de comunicaciones que permite a diferentes máquinas conectadas a Internet comunicarse entre sí. El protocolo TCP/IP ofrece comunicaciones fiables mediante servicios orientados a la conexión (protocolo TCP) y no fiables a través de servicios no orientados a la conexión (protocolo UDP, User Datagram Protocol). Un servicio orientado a la conexión significa que permite intercambiar un gran volumen de datos de una manera correcta, es decir, se asegura que los datos llegan en el orden en el que se mandaron y no existen duplicados, además tiene mecanismos que le permiten recuperarse ante errores. Las comunicaciones en Internet utilizando el protocolo TCP/IP se realizan a través de circuitos virtuales de datos llamados sockets. Un socket básicamente es una "tubería" que se crea para comunicar a dos programas. Cada programa posee un extremo de la tubería. La clase Socket del paquete java.net provee una implementación independiente de la plataforma del lado cliente de una conexión entre un programa cliente y un programa servidor a través de un socket. El lado del servidor es implementado por la clase ServerSocket. Por lo tanto para iniciar la conexión desde el lado del cliente se debe instanciar un objeto de la clase Socket, el constructor utilizado de esta clase tiene como parámetros la dirección IP (Internet Protocol) o el nombre de la máquina a la que se quiere conectar y el número de puerto en el que el servidor está esperando las peticiones de los clientes. Una dirección IP es un número de 32 bits para identificar de forma única una máquina conectada a Internet. Se divide en grupos de 8 bits y se identifica con su número en notación decimal separado cada uno de ellos por puntos, a este formato se le denomina tétrada punteada. Java permite manejar direcciones IP a través de la clase InetAddress, que se encuentra también en el paquete java.net. 359 Programación en Java © Grupo EIDOS Debido a que recordar direcciones IP puede ser difícil y poco manejable se suelen identificar con nombres de máquinas, así la dirección IP 206.26.48.100 se corresponde con el nombre de la máquina java.sun.com que resulta más fácil de recordar y significativo. Un mismo nombre puede tener diferentes direcciones IP, en Internet esta correspondencia entre direcciones IP y nombres la gestionan servidores de nombres que se encargan de traducir los nombres fáciles de recordar a sus direcciones de 32 bits. En un programa Java para obtener el nombre de una dirección IP se utiliza el método getHostName() de la clase InetAddress. Una vez comentado el concepto de dirección IP vamos a retomar la explicación justo en el punto en el que se había dejado. Después de crear el socket, a continuación se deberán obtener los canales de entrada y de salida del socket. Los canales los acabamos de comentar en apartados anteriores. Para obtener los canales de entrada y de salida del socket se utilizan los métodos getInputStream() y getOutputStream() de la clase Socket, una vez que se tienen los canales, el de entrada lo podremos tratar, por ejemplo, como un objeto de la clase DataInputStream y el de salida como un objeto de la clase PrintStream. Cuando ya disponemos de los canales de entrada y salida del socket, ya estamos en disposición de poder comunicarnos con el servidor, es decir, la aplicación Java que posee el servicio que queremos utilizar y que tiene el socket del lado del servidor. De todas formas, el servidor se comentará más adelante dentro de este mismo apartado. Si lo que queremos es recibir información desde el servidor a través del socket creado, se leerá del flujo de entrada del socket. Para ello se entra en un bucle del tipo mientras cuya condición es "mientras se siga recibiendo información a través del flujo de entrada del socket". El cuerpo del bucle mientras se encargará de tratar esta información de forma adecuada y utilizarla para la tarea que resulte necesaria. Si deseamos enviar información al servidor lo haremos a través del flujo de salida del socket, escribiendo en él la información deseada. Todo el proceso de comunicaciones se encuentra encerrado en un bloque try{...}catch(...){...} para atrapar los errores que se produzcan. En este caso se deberán atrapar excepciones de entrada/salida, es decir, IOException. Una vez que se ha terminado el proceso de comunicación entre la aplicación cliente y el servidor, se procede a cerrar los canales de entrada y salida del socket, y también el propio socket (siempre se debe hacer en este orden). El siguiente esquema que se muestra es el que suelen presentar los clientes en general: • Abrir un socket. • Abrir el canal de entrada y el canal de salida del socket. • Leer del canal de entrada y escribir en el canal de salida, atendiendo al protocolo del servidor. • Cerrar los canales. • Cerrar el socket. El tercer paso es el que más suele variar de un cliente a otro, dependiendo del servidor, los demás suelen ser iguales. 360 © Grupo EIDOS 17. Aspectos avanzados de Java: canales y Sockets Para construir la segunda aplicación implicada en este proceso de comunicación, es decir, la aplicación que realiza la función de servidor, se deberá instanciar un objeto de la clase ServerSocket. Esta es, como ya se había indicado anteriormente, una clase del paquete java.net que provee una implementación independiente de la plataforma del lado servidor de una conexión cliente/servidor a través de un socket. El constructor del socket del servidor, ServerSocket(), necesita como parámetro un número de puerto en el que debe ponerse a escuchar las peticiones de los clientes, es decir, mediante este número de puerto se identificará el servicio que se debe prestar. Si el puerto especificado está ya ocupado se lanzará una excepción a la hora de crear el socket de servidor. Cuando se ha creado el socket de servidor a continuación se lanza sobre este socket el método accept() de la clase ServerSocket. El método accept() del socket del servidor se bloquea (espera) hasta que un cliente inicia una petición de conexión en el puerto en el que el servidor está escuchando. Cuando el método accept() establece con éxito una conexión con el cliente devuelve un nuevo objeto de la clase Socket al que se le asigna un nuevo puerto local, dejando libre el puerto en el que el servidor espera las peticiones de los clientes. Este socket se utiliza como parámetro para el constructor de la clase encargada de tratar al cliente. Esta clase que implementa el interfaz Runnable, posee un objeto representa un hilo de ejecución paralelo que es el verdadero encargado de servir al cliente, es decir, es un objeto de la clase Thread. Múltiples peticiones de los clientes pueden llegar al mismo puerto. Estas peticiones de conexión se encolan en el puerto, de esta forma el servidor debe aceptar las conexiones secuencialmente. Sin embargo los clientes pueden ser servidos simultáneamente a través del uso de hilos de ejecución. Un hilo para procesar cada una de las conexiones de los clientes. Para cada uno de los clientes que se conectan al servidor se instancia un objeto que implementa el interfaz Runnable y que inicia un nuevo hilo de ejecución a través de un objeto de la clase Thread. Una vez que el servidor ha instanciado un hilo de ejecución para tratar al cliente que se acaba de conectar, vuelve a escuchar en el mismo puerto en el que estaba anteriormente esperando la llegada de conexiones de otros clientes. De esta forma mientras el objeto de la clase Thread se ocupa de servir a un cliente, el servidor puede a la misma vez seguir esperando conexiones de otros clientes, ya que se trata de hilos de ejecución paralelos. Un esquema general que suelen tener los servidores es el siguiente: mientras (true) { aceptar una conexión. crear un hilo de ejecución que se encargue de servir al cliente. } En el caso de que exista algún error en las aceptaciones de conexión de los clientes se saldrá del bucle mientras y se cerrará el socket del servidor, y se deberá notificar este error. La clase que se encarga de servir al cliente, como ya se ha mencionado anteriormente, deberá implementar el interfaz Runnable, por lo tanto deberá implementar el método run() de este interfaz. Dentro del método run() se implementa el protocolo de comunicación que existe entre el cliente y el servidor. Dentro de este método podremos obtener los canales de entrada y de salida del socket en el que está conectado el cliente, para ello utiliza los métodos getInputStream() y getOutputStream() de la clase Socket. En este momento se inicia la comunicación con el cliente, atendiendo al protocolo de 361 Programación en Java © Grupo EIDOS comunicaciones determinado se escribirá en el flujo de salida o se leerá del flujo de entrada del socket la información necesaria, ya sean caracteres o bytes. Una vez servido por completo el cliente se cerrarán el socket y se finalizará la ejecución del hilo. Si se ha produce algún error en el método run() se dará por terminada la comunicación con el cliente, se indicará el error a las aplicaciones cliente y servidor y se dispondrá a cerrar los canales de entrada y de salida y el socket, y finalmente se detendrá la ejecución del hilo. En este apartado hemos visto de forma general la arquitectura cliente/servidor dentro del lenguaje Java, se han visto unos esquemas genéricos que podrán ser utilizados para cualquier aplicación cliente/servidor. Pero para que no quede todo en mera teoría que suele ser un poco aburrida y fácil de olvidar, en el siguiente apartado vamos a ver un ejemplo sencillo. Este ejemplo se puede considerar como un pequeño resumen de todo lo visto en estos dos últimos capítulos, ya que vamos a utilizar multihilos, canales de entrada/salida y sockets. Además se utiliza un applet, una aplicación y también existe tratamiento de eventos, por lo tanto podemos ir un poco más allá y considerar que es un ejemplo sencillo que resume de forma escueta todo el presente curso. Nuestro ejemplo va estar constituido por un cliente y un servidor, el cliente va a ser un applet y el servidor una aplicación. El cliente va a realizar una petición muy sencilla, (que se puede considerar absurda, pero para el ejemplo nos sirve) la hora y fecha del servidor. El applet va a tener una caja de texto en la que va a mostrar la hora y fecha que le devuelva la aplicación servidor. También ofrece un botón, la pulsación de este botón implica una petición del cliente al servidor. En al método actionPerformed() se establece la conexión con el servidor y se obtiene el canal de entrada, del que se obtendrá la fecha y hora. El código del applet cliente es como se muestra en el Código fuente 257. import java.applet.*; import java.awt.*; import java.awt.event.*; import java.io.*; import java.net.*; public class Cliente extends Applet implements ActionListener{ private Button boton; private TextField resultado; //canal de entrada private BufferedReader entrada; //socket del cliente private Socket socket; public void init(){ resultado=new TextField(40); add(resultado); boton=new Button("Obtener Fecha"); add(boton); boton.addActionListener(this); } public void actionPerformed(ActionEvent evento){ try{ //creamos el socket y nos conectamos al servidor socket=new Socket(InetAddress.getLocalHost(),6789); //obtenemos el canal de entrada entrada=new BufferedReader( new InputStreamReader(socket.getInputStream())); resultado.setText(entrada.readLine()); //se cierran los canales y el socket 362 © Grupo EIDOS 17. Aspectos avanzados de Java: canales y Sockets entrada.close(); socket.close(); }catch(UnknownHostException ex){ resultado.setText("Error al establecer la conexión: "+ex); }catch(IOException ex){ resultado.setText("Error de E/S: "+ex); } } } Código fuente 257 El código es bastante sencillo y respeta el esquema que comentamos para los clientes genéricos, pero en este caso no se utiliza ningún canal de salida, ya que no enviamos ningún dato a la aplicación servidor. Para leer del canal de entrada se utiliza el método readLine() de la clase BufferedReader. La aplicación servidor consta de dos clases en dos ficheros fuente distintos. La primera clase llamada Servidor es el servidor propiamente dicho, y su función es la de crear un objeto ServerSocket en el puerto 6789. A este servidor se conecta el applet anteriormente comentado. El servidor estará a la escucha para saber cuando se ha conectado un cliente, y cuando se conecta se crea una instancia de la segunda clase de esta aplicación. La clase Servidor hereda de la clase Frame para dar una representación gráfica a nuestra aplicación. Además en la ventana se muestra el número de clientes que se han servidor hasta el momento, cada vez que pulsemos el botón del applet se creará una nueva petición. El código de esta clase se muestra en el Código fuente 258. import java.awt.*; import java.awt.event.*; import java.io.*; import java.net.*; public class Servidor extends Frame{ private ServerSocket sock; private Label servidos; public static void main(String args[]){ Socket s=null; Servidor serv=new Servidor(); //el servidor se pone a escuchar en el socket //que ha creado boolean escuchando=true; int numCliente=0; while(escuchando){ //espera hasta que un cliente comienza una conexión //en el puerto especificado try { s=serv.sock.accept(); numCliente++; serv.servidos.setText("Clientes Servidos= "+numCliente); }catch (IOException e) { System.out.println("Fallo en la aceptación de llamadas: "+ e.getMessage()); //fuerza la salida del while continue; } //se crea un objeto de la clase que va a tratar la cliente new TrataCliente(s,numCliente); } //se cierra el socket try{ serv.sock.close(); 363 Programación en Java © Grupo EIDOS }catch(IOException err){ System.out.println("Error al cerrar el socket"); System.exit(-1); } //se cierra la ventana del servidor serv.dispose(); System.exit(0); } //constructor public Servidor(){ super(" Ejemplo Servidor"); //se crea el socket del servidor en un puerto que no esté ocupado try{ sock=new ServerSocket(6789); }catch(IOException err){ System.out.println("Error:\n Problemas al crear el socket: "+err); System.exit(-1); } setSize(200,110); show(); addWindowListener(new AdaptadorVentana()); servidos=new Label("Clientes Servidos=0"); setLayout(new BorderLayout()); add("Center",servidos); System.out.println("Iniciado el servidor en el puerto: " +sock.getLocalPort()); } class AdaptadorVentana extends WindowAdapter{ public void windowClosing(WindowEvent evento){ try{ sock.close(); }catch(IOException ex){ System.out.println("Error al cerrar el socket: "+ex); } dispose(); System.out.println("Apagando el servidor..."); System.exit(0); } } } Código fuente 258 La segunda clase de la aplicación se llama TrataCliente y tiene como función servir al cliente que se ha conectado y realizado la petición, es decir, es la clase que calcula la hora del servidor y se la envía por el canal de salida correspondiente al cliente que se encuentre conectado. La clase TrataCliente implementa el interfaz Runnable por lo que contiene el método run(), es precisamente en este método el lugar en el que se va a tratar la petición del cliente. Por cada cliente conectado se tendrá un hilo de ejecución distinto, y este se creará a través del atributo hilo, que pertenece a la clase Thread. En el método run() se crea un canal de salida de la clase PrintWriter, a partir del canal de salida del socket establecido entre el cliente y el servidor, para enviar la fecha y hora al cliente que haya realizado la petición. Su código completo es el que vemos en el Código fuente 259. import import import import 364 java.io.*; java.awt.*; java.net.*; java.util.*; © Grupo EIDOS 17. Aspectos avanzados de Java: canales y Sockets public class TrataCliente implements Runnable{ //socket creado para comunicarse con el cliente private Socket sock=null; //Flujo de salida del socket por el que se envía //la fecha y hora actual al cliente private PrintWriter salida; //Hilo de ejecución private Thread hilo=null; //constructor que crea el hilo y lo ejecuta public TrataCliente(Socket s, int id){ sock=s; hilo=new Thread(this,"Cliente "+id); hilo.start(); System.out.println("Iniciado el proceso: "+hilo.getName()); } public void run(){ try{ //es el canal de salida del socket // lo que debe leer el cliente salida=new PrintWriter(sock.getOutputStream()); salida.println(new Date()); }catch(Exception e){ System.out.println("Error en la transmisión del fichero"); System.exit(-1); } finalizar(); } public void finalizar() { //se cierra el canal de salida y el socket try{ salida.close(); sock.close(); }catch(IOException err){ System.out.println("Error al cerrar el socket"); } System.out.println("Finalizado el proceso: "+hilo.getName()); System.out.println(); //se finaliza y destruye el hilo de ejecución hilo.stop(); hilo=null; } } Código fuente 259 Un ejemplo de una ejecución de este último ejemplo es el que se puede apreciar en la figura 10. Figura 160 365 Programación en Java © Grupo EIDOS Para probar correctamente este ejemplo, la aplicación servidor y el applet cliente se deben encontrar en la misma máquina, es decir, ambos se deben hallar en el mismo servidor Web. 366 Si quiere ver más textos en este formato, visítenos en: http://www.lalibreriadigital.com. Este libro tiene soporte de formación virtual a través de Internet, con un profesor a su disposición, tutorías, exámenes y un completo plan formativo con otros textos. Si desea inscribirse en alguno de nuestros cursos o más información visite nuestro campus virtual en: http://www.almagesto.com. Si quiere información más precisa de las nuevas técnicas de programación puede suscribirse gratuitamente a nuestra revista Algoritmo en: http://www.algoritmodigital.com. No deje de visitar nuestra reviata Alquimia en http://www.eidos.es/alquimia donde podrá encontrar artículos sobre tecnologías de la sociedad del conocimiento. Si quiere hacer algún comentario, sugerencia, o tiene cualquier tipo de problema, envíelo a la dirección de correo electrónico [email protected]. © Grupo EIDOS http://www.eidos.es