java

384
Tutorial de Java por Agustín Froufe ( [email protected]) Editado en PDF por Alonso de la Barra 2001 [email protected]

Upload: jeireuz-ortiz-fdz

Post on 12-May-2017

245 views

Category:

Documents


0 download

TRANSCRIPT

Tutorial de Javapor

Agustín Froufe([email protected])

Editado en PDF por Alonso de la Barra 2001

[email protected]

2 Tutorial de Java

Tutorial de Java 3

IndiceTabla de Contenido

Introducción a Java 15

Origen de Java 16

Características de Java 17

· · Simple 17· · Orientado a Objetos 18· · Distribuido 19· · Robusto 19· · De Arquitectura Neutral 19· · Seguro 21· · Portable 23· · Interpretado 23· · Multithreaded 23· · Dinámico 24

HotJava 25

Java para aplicaciones corporativas 25

· · Desarrollo rápido de aplicaciones 26· · Aplicaciones efectivas y eficientes 26· · Portabilidad para programador y programa 27· · Costes de desarrollo 27· · Mantenimiento y soporte 28· · Aprendizaje 29· · Resumen 30

Instalación del JDK 31

Windows 95 32

Solaris 32

Linux 32

Compilación sin JDK 33

Conceptos Básicos de Java 35

Programación en Java 35

4 Tutorial de Java

· · Comentarios 35· · Identificadores 36

Palabras clave 36

Palabras reservadas 36

· · Literales 36· · Arrays 37· · Operadores 38· · Separadores 39

Control del Flujo 39

· · Sentencias de Salto 39· · Sentencias de Bucles 40· · Excepciones 40· · Control General del Flujo 41

Clases 41

· · Tipos de Clases 42

Variables y Métodos de Instancia 42

· · Ambito de una variable 43· · Métodos y Constructores 43· · Finalizadores 44

Alcance de Objetos y Reciclado de Memoria 44

Herencia 45

Control de Acceso 45

Variables y Métodos Estáticos 46

this y super 47

Clases Abstractas 48

Interfaces 49

Métodos Nativos 50

Paquetes 50

· · Import 51· · Paquetes de Java 51

Referencias 52

· · Punteros 52· · Referencias en C++ 53· · Referencias en Java 53· · Referencias y Arrays 55· · Referencias y Listas 56

Tutorial de Java 5

· · Punteros C/C++ y Referencias Java 57

Programas Básicos en Java 59

Una mínima aplicación en Java 59

· · HolaMundo 59· · Compilación y Ejecución de HolaMundo 60

Ficheros fuente java 60

Compilación 61

Ejecución 61

· · Problemas de compilación 61

El visor de Applets de Sun (appletviewer) 62

· · Applet 62· · Llamadas a Applets con appletviewer 63· · Arquitectura de appletviewer 63· · Ciclo de vida de un Applet 64· · Métodos de appletviewer 64

Sinopsis 66

Ejemplo de uso 67

· · Funciones de menú de appletviewer 67

Un Applet básico en Java 68

· · HolaMundo 68· · Componentes básicos de un Applet 69

Clases incluidas 69

La clase Applet 69

Métodos de Applet 70

· · Compilación de un Applet 70· · Llamada a Applets 70· · Prueba de un Applet 71· · La marca APPLET de html 71

Atributos de APPLET 72

Paso de parámetros a Applets 74

Tokens en parámetros de llamada 75

El parámetro ARCHIVE 77

· · Depuración general 78· · Ciclo de vida de un Applet 79· · Protección de Applets 79

6 Tutorial de Java

Escribir Applets Java 80

· · init 80· · destroy 81· · start 81· · stop 81· · resize 81· · width 81· · height 82· · paint 82· · update 82· · repaint 83· · getParameter 83· · getDocumentBase 83· · getCodeBase 84· · print 84

La aplicación Fecha (Aproximación a OOP) 84

El Depurador de Java - jdb 87

Depurar HolaMundo 87

Comando help 88

Comando threadgroups 89

Comando threads 89

Comando run 89

Comando where 90

Comando use 90

Comando list 90

Comando dump 91

Comando step 91

Clases Java 93

La clase Math 93

· · Funciones matemáticas 93

La clase Character 94

· · Declaraciones 94· · Comprobaciones booleanas 94· · Traslaciones de caracteres 95· · Traslaciones de carácter/dígito 95· · Métodos de la clase Character 95

Tutorial de Java 7

La clase Float 95

· · Declaraciones 95· · Valores de Float 95· · Conversiones de Clase/Cadena 95· · Comprobaciones 96· · Conversiones de Objetos 96· · Otros Métodos 96

La clase Double 96

· · Declaraciones 96· · Valores de Double 96· · Métodos de Double 97

La clase Integer 97

· · Declaraciones 97· · Valores de Integer 97· · Métodos de Integer 97

La clase Long 98

· · Declaraciones 98· · Valores de Long 98· · Métodos de Long 98

La clase Boolean 98

· · Declaraciones 99· · Valores de Boolean 99· · Métodos de Boolean 99

La clase String 99

· · Funciones Básicas 100

Funciones de Comparación de Strings 100

Funciones de Comparación de Subcadenas 100

· · Funciones ValueOf 101

Funciones de Conversión 101

La clase StringBuffer 101

· · Cambio de Tamaño 102· · Modificación del Contenido 102· · Operadores de Concatenación 103

Uso de Conversiones 104

AWT 105

Introducción al AWT 105

Interface de Usuario 105

8 Tutorial de Java

Estructura del AWT 106

Componentes y Contenedores 106

· · Tipos de Componentes 108

Componentes 109

· · Botones 110

Eventos Button 110

De pulsación (Push) 111

De lista (Choice) 111

De marcación (CheckBox) 112

De selección (Radio) 113

Autocontenidos 113

· · Etiquetas 114· · Listas 115· · Campos de Texto 117· · Areas de Texto 119· · Canvas 120· · Barras de Desplazamiento 122· · Diseño de Componentes propios 124

Contenedores 127

· · Window 128· · Frame 128· · Dialog 128· · Panel 128· · Crear un Contenedor 128· · Añadir Componentes a un Contendor 130

Creación de Aplicaciones con AWT 130

· · Crear el Marco de la Aplicación 131· · Inicializar Fuentes, Colores y Recursos 132· · Crear Menús y Barras de Menús 134· · Diálogos y Ventanas 136

Paneles 138

Layouts 140

· · FlowLayout 142· · BorderLayout 142· · GridLayout 143· · GridBagLayout 144· · CardLayout 147· · Crear un Layout propio 148

Tutorial de Java 9

Control de Eventos 150

· · La clase Event 151· · Tipos de Eventos 152

Eventos de Ventana 152

Eventos de Teclado 152

Eventos de Ratón 152

Eventos de Barras 153

Eventos de Lista 153

Eventos Varios 153

· · Generación y Propagación de Eventos 155· · Métodos de Control de Eventos 158· · ACTION_EVENT 159

Mejorar el Diseño de Interfaces 160

· · Cambio de Font de Caracteres 160· · Colores de Fondo y Texto 162· · Fijar el Tamaño Preferido 163· · Uso de Insets 164· · Habilitar y Deshabilitar Componentes 165· · Botón Gráfico 166

Gráficos 169

Objetos Gráficos 169

Métodos para Dibujos 170

· · Líneas 171· · Rectángulos 172· · Círculos, Elipses 175· · Polígonos 179· · Funciones Gráficas 180· · Fractales 182· · Líneas Flotantes 184

Métodos para Imágenes 186

Doble Buffering de Gráficos 187

· · Contextos Gráficos 187· · Creación de Contextos Gráficos 188· · Utilización de Contextos Gráficos 188

Nuevas Clases para Dibujo 189

La Clase MediaTracker 191

· · Manejo de Imágenes para Animación 191

10 Tutorial de Java

· · Creación de un Objeto MediaTracker 192· · Ejemplo de Animación 192

Sonido en Java 194

· · Reproducción de Sonido 194· · Reproducción Repetitiva 195

Entrada por Ratón 196

Excepciones en Java 197

Manejo de Excepciones 197

Generar Excepciones en Java 198

Excepciones Predefinidas 199

Crear Excepciones Propias 201

Capturar Excepciones 202

· · try 202· · catch 202· · finally 203

Propagación de Excepciones 204

Threads y Multithreading 209

Flujo en Programas 209

· · Programas de Flujo Unico 209· · Programas de Flujo Múltiple 210

Creación y Control de Threads 211

· · Creación de un Thread 211· · Arranque de un Thread 213· · Manipulación de un Thread 213· · Suspensión de un Thread 214· · Parada de un Thread 214

Arrancar y Parar Threads 217

Suspender y Reanudar Threads 216

Estados de un Thread 217

· · Nuevo Thread 217· · Ejecutable 218· · Parado 218· · Muerto 219· · El método isAlive() 220

Scheduling 220

Tutorial de Java 11

Prioridades, demonios... 221

· · Prioridades 221· · Threads Demonio 221· · Diferencia de Threads con fork() 221

Ejemplo de Animación 222

Comunicación entre Threads 224

· · Productor 224· · Consumidor 225· · Monitor 226· · Monitorización del Productor 228

Código Nativo 229

Escribir Código Java 229

Compilar el Código Java 230

Crear el fichero de Cabecera 230

Crear el fichero de Stubs 232

Escribir la función C 232

Crear la Librería Dinámica 233

· · Unix 233· · Windows ‘95 233

Ejecutar el Programa 234

Entrada/Salida Estándar 235

La clase System 235

· · Stdin 235· · Stdout 235· · Stderr 236

Clases comunes de Entrada/Salida 236

Ficheros en Java 237

Ficheros 237

· · Creación de un objeto File 237· · Comprobaciones y Utilidades 238

Streams de Entrada 239

· · Objetos FileInputStream 239

Apertura de un FileInputStream 239

Lectura de un FileInputStream 240

12 Tutorial de Java

Cierre de FileInputStream 240

Ejemplo: Visualización de un fichero 240

· · Objetos DataInputStream 241

Apertura y cierre de DataInputStream 241

Lectura de un DataInputStream 241

· · Streams de entrada de URLs 242

Apertura de un Stream de entrada 242

Streams de Salida 243

· · Objetos FileOutputStream 243

Apertura de un FileOutputStream 243

Escritura en un FileOutputStream 243

Cierre de FileOutputStream 244

Ejemplo: Almacenamiento de Información 244

· · Streams de salida con buffer 245

Creación de Streams de salida con buffer 245

Volcado y Cierre de Streams de salida con buffer 245

· · Streams DataOutput 246

Apertura y cierre de objetos DataOutputStream 246

Escritura en un objeto DataOutputStream 246

Contabilidad de la salida 247

Ficheros de Acceso Aleatorio 247

· · Creación de un Fichero de Acceso Aleatorio 247· · Acceso a la Información 247· · Actualización de Información 248

Comunicaciones en Java 249

Comunicaciones en Unix 249

Sockets 250

· · Sockets Stream 250· · Sockets Datagrama 250· · Sockets Raw 250

Diferencias entre Sockets Stream y Datagrama 251

Uso de Sockets 251

Tutorial de Java 13

· · Puertos y Servicios 251· · La clase URL 252

Dominios de Comunicaciones 253

· · Dominio Unix 254· · Dominio Internet 254

Modelo de Comunicaciones con Java 254

· · Apertura de Sockets 255· · Creación de Streams de Entrada 256· · Creación de Streams de Salida 257· · Cierre de Sockets 258· · Mínimo Cliente SMTP 258· · Servidor de Eco 258· · Mínimo Servidor TCP/IP 260· · Mínimo Cliente TCP/IP 261· · Servidor Simple de HTTP 262· · Red en Windows ’95 (sin conexión) 266

Configuración del TCP/IP de Windows ‘95 267

Crear una entrada en la Red 267

Comprobación de la Red 268

Problemas más frecuentes 268

Clases Utiles en Comunicaciones 269

Arquitectura MVC en Java 273

La Arquitectura MVC 273

· · Definición de las partes 274

Observador y Observable 275

· · Funciones de Observer y Observable 276

Observer 276

Observable 276

· · Cómo utilizar Observer y Observable 276

Extender un Observable 277

Implementar un Observador 277

Usando Observador y Observable 278

Ejemplo de aplicación MVC 279

14 Tutorial de Java

Aplicaciones en Java 281

Etiqueta 281

Reloj Digital 290

Persiana 303

Solapas 310

Transparencia 329

Calculadora 341

Cuenta-Kilómetros 350

Potenciómetro 354

Cartel (de Luis Angel Ortega) 369

Final y Agradecimientos 383

Agradecimientos 383

Copyrights 383

Tutorial de Java 15

Introducción a Java

El uso principal que se hace de Internet e incluso de las redes internas (corporativas) escorreo electrónico (e-mail), aunque actualmente hay un auge sorprendente de la navega-ción web. Los documentos web pueden contener variedad de texto, gráficos de todas clasesy proporcionar enlaces hipertexto hacia cualquier lugar de la red. Los navegadores utilizandocumentos escritos en lenguaje HTML. La combinación actual de navegadores HTML/WWW están limitados pues, a texto y gráficos. Si se quiere reproducir un sonido o ejecutarun programa de demostración, primero hemos de bajarnos (download) el fichero en cuestióny luego utilizar un programa en nuestro ordenador capaz de entender el formato de esefichero, o bien cargar un módulo (plug-in) en nuestro navegador para que pueda interpretarel fichero que hemos bajado.

Hasta ahora, la única forma de realizar una página web con contenido interactivo, era me-diante la interfaz CGI (Common Gateway Interface), que permite pasar parámetros entreformularios definidos en lenguaje HTML y programas escritos en Perl o en C. Esta interfazresulta muy incómoda de programar y es pobre en sus posibilidades.

El lenguaje Java y los navegadores con soporte Java, proporcionan una forma diferente dehacer que ese navegador sea capaz de ejecutar programas. Con Java se puede reproducirsonido directamente desde el navegador, se pueden visitar home pages con animaciones,se puede enseñar al navegador a manejar nuevos formatos de ficheros, e incluso, cuandose pueda transmitir video por las líneas telefónicas, nuestro navegador estará preparadopara mostrar esas imágenes.

Utilizando Java, se pueden eliminar los inconvenientes de la interfaz CGI y también sepueden añadir aplicaciones que vayan desde experimentos científicos interactivos de pro-pósito educativo a juegos o aplicaciones especializadas para la televenta. Es posibleimplementar publicidad interactiva y periódicos personalizados. Por ejemplo, alguien podríaescribir un programa Java que implementara una simulación química interactiva (una cade-na de adn). Utilizando un navegador con soporte Java, un usuario podría recibir fácilmenteesa simulación e interaccionar con ella, en lugar de conseguir simplemente un dibujo está-tico y algo de texto. Lo recibido cobra vida. Además, con Java podemos estar seguros deque el código que hace funcionar el experimento químico no contiene ningún trozo de códi-go malicioso que dañe al sistema. El código que intente actuar destructivamente o quecontenga errores, no podrá traspasar los muros defensivos colocados por las característi-cas de seguridad y robustez de Java.

Además, Java proporciona una nueva forma de acceder a las aplicaciones. El software viaja

Capítulo 1

16 Tutorial de Java

transparentemente a través de la red. No hay necesidad de instalar las aplicaciones, ellasmismas vienen cuando se necesitan. Por ejemplo, la mayoría de los navegadores del Webpueden procesar un reducido número de formatos gráficos (típicamente GIF y JPEG). Si seencuentran con otro tipo de formato, el navegador estándar no tiene capacidad para proce-sarlo, tendría que ser actualizado para poder aprovechar las ventajas del nuevo formato. Sinembargo, un navegador con soporte Java puede enlazar con el servidor que contiene elalgoritmo que procesa ese nuevo formato y mostrar la imagen. Por lo tanto, si alguien inven-ta un nuevo algoritmo de compresión para imágenes, el inventor sólo necesita estar segurode que hay una copia en código Java de ese algoritmo instalada en el servidor que contienelas imágenes que quiere publicar. Es decir, los navegadores con soporte Java se actualizana sí mismos sobre la marcha, cuando encuentran un nuevo tipo de fichero o algoritmo.

Origen de Java

Sun Microsystems, líder en servidores para Internet, uno de cuyos lemas desde hace muchotiempo es «the network is the computer» (lo que quiere dar a entender que el verdaderoordenador es la red en su conjunto y no cada máquina individual), es quien ha desarrolladoel lenguaje Java, en un intento de resolver simultáneamente todos los problemas que se leplantean a los desarrolladores de software por la proliferación de arquitecturas incompati-bles, tanto entre las diferentes máquinas como entre los diversos sistemas operativos ysistemas de ventanas que funcionaban sobre una misma máquina, añadiendo la dificultadde crear aplicaciones distribuidas en una red como Internet.

He podido leer más de cinco versiones distintas sobre el origen, concepción y desarrollo deJava, desde la que dice que este fue un proyecto que rebotó durante mucho tiempo pordistintos departamentos de Sun sin que nadie le prestara ninguna atención, hasta que final-mente encontró su nicho de mercado en la aldea global que es Internet; hasta la más difun-dida, que justifica a Java como lenguaje de pequeños electrodomésticos.

Hace algunos años, Sun Microsystems decidió intentar introducirse en el mercado de laelectrónica de consumo y desarrollar programas para pequeños dispositivos electrónicos.Tras unos comienzos dudosos, Sun decidió crear una filial, denominada FirstPerson Inc.,para dar margen de maniobra al equipo responsable del proyecto.

El mercado inicialmente previsto para los programas de FirstPerson eran los equipos do-mésticos: microondas, tostadoras y, fundamentalmente, televisión interactiva. Este merca-do, dada la falta de pericia de los usuarios para el manejo de estos dispositivos, requeríaunos interfaces mucho más cómodos e intuitivos que los sistemas de ventanas que prolife-raban en el momento.

Otros requisitos importantes a tener en cuenta eran la fiabilidad del código y la facilidad dedesarrollo. James Gosling, el miembro del equipo con más experiencia en lenguajes deprogramación, decidió que las ventajas aportadas por la eficiencia de C++ no compensabanel gran coste de pruebas y depuración. Gosling había estado trabajando en su tiempo libreen un lenguaje de programación que él había llamado Oak, el cual, aún partiendo de lasintaxis de C++, intentaba remediar las deficiencias que iba observando.

Tutorial de Java 17

Los lenguajes al uso, como C o C++, deben ser compilados para un chip, y si se cambia elchip, todo el software debe compilarse de nuevo. Esto encarece mucho los desarrollos y elproblema es especialmente acusado en el campo de la electrónica de consumo. La apari-ción de un chip más barato y, generalmente, más eficiente, conduce inmediatamente a losfabricantes a incluirlo en las nuevas series de sus cadenas de producción, por pequeña quesea la diferencia en precio ya que, multiplicada por la tirada masiva de los aparatos, suponeun ahorro considerable. Por tanto, Gosling decidió mejorar las características de Oak yutilizarlo.

El primer proyecto en que se aplicó este lenguaje recibió el nombre de proyecto Green yconsistía en un sistema de control completo de los aparatos electrónicos y el entorno de unhogar. Para ello se construyó un ordenador experimental denominado *7 (Star Seven). Elsistema presentaba una interfaz basada en la representación de la casa de forma animaday el control se llevaba a cabo mediante una pantalla sensible al tacto. En el sistema aparecíaDuke, la actual mascota de Java. Posteriormente se aplicó a otro proyecto denominadoVOD (Video On Demand) en el que se empleaba como interfaz para la televisión interactiva.Ninguno de estos proyectos se convirtió nunca en un sistema comercial, pero fueron desa-rrollados enteramente en un Java primitivo y fueron como su bautismo de fuego.

Una vez que en Sun se dieron cuenta de que a corto plazo la televisión interactiva no iba aser un gran éxito, urgieron a FirstPerson a desarrollar con rapidez nuevas estrategias queprodujeran beneficios. No lo consiguieron y FirstPerson cerró en la primavera de 1994.

Pese a lo que parecía ya un olvido definitivo, Bill Joy, cofundador de Sun y uno de losdesarrolladores principales del Unix de Berkeley, juzgó que Internet podría llegar a ser elcampo de juego adecuado para disputar a Microsoft su primacía casi absoluta en el terrenodel software, y vio en Oak el instrumento idóneo para llevar a cabo estos planes. Tras uncambio de nombre y modificaciones de diseño, el lenguaje Java fue presentado en sociedaden agosto de 1995.

Lo mejor será hacer caso omiso de las historias que pretenden dar carta de naturaleza a laclarividencia industrial de sus protagonistas; porque la cuestión es si independientementede su origen y entorno comercial, Java ofrece soluciones a nuestras expectativas. Porquetampoco vamos a desechar la penicilina aunque haya sido su origen fruto de la casualidad.

Caracteristicas de Java

Las características principales que nos ofrece Java respecto a cualquier otro lenguaje deprogramación, son:

Es SIMPLE:

Java ofrece toda la funcionalidad de un lenguaje potente, pero sin las características menosusadas y más confusas de éstos. C++ es un lenguaje que adolece de falta de seguridad,pero C y C++ son lenguajes más difundidos, por ello Java se diseñó para ser parecido a C++y así facilitar un rápido y fácil aprendizaje.

Java elimina muchas de las características de otros lenguajes como C++, para mantener

18 Tutorial de Java

reducidas las especificaciones del lenguaje y añadir características muy útiles como el garbagecollector (reciclador de memoria dinámica). No es necesario preocuparse de liberar memo-ria, el reciclador se encarga de ello y como es un thread de baja prioridad, cuando entra enacción, permite liberar bloques de memoria muy grandes, lo que reduce la fragmentación dela memoria.

Java reduce en un 50% los errores más comunes de programación con lenguajes como C yC++ al eliminar muchas de las características de éstos, entre las que destacan:

· aritmética de punteros· no existen referencias· registros (struct)· definición de tipos (typedef)· macros (#define)· necesidad de liberar memoria (free)

Aunque, en realidad, lo que hace es eliminar las palabras reservadas (struct, typedef), yaque las clases son algo parecido.

Además, el intérprete completo de Java que hay en este momento es muy pequeño, sola-mente ocupa 215 Kb de RAM.

Es ORIENTADO A OBJETOS :

Java implementa la tecnología básica de C++ con algunas mejoras y elimina algunas cosaspara mantener el objetivo de la simplicidad del lenguaje. Java trabaja con sus datos comoobjetos y con interfaces a esos objetos. Soporta las tres características propias del paradig-ma de la orientación a objetos: encapsulación, herencia y polimorfismo. Las plantillas deobjetos son llamadas, como en C++, clases y sus copias, instancias. Estas instancias, comoen C++, necesitan ser construidas y destruidas en espacios de memoria.

Java incorpora funcionalidades inexistentes en C++ como por ejemplo, la resolución dinámi-ca de métodos. Esta característica deriva del lenguaje Objective C, propietario del sistemaoperativo Next. En C++ se suele trabajar con librerías dinámicas (DLLs) que obligan arecompilar la aplicación cuando se retocan las funciones que se encuentran en su interior.Este inconveniente es resuelto por Java mediante una interfaz específica llamada RTTI(RunTime Type Identification) que define la interacción entre objetos excluyendo variablesde instancias o implementación de métodos. Las clases en Java tienen una representaciónen el runtime que permite a los programadores interrogar por el tipo de clase y enlazardinámicamente la clase con el resultado de la búsqueda.

Es DISTRIBUIDO:

Java se ha construido con extensas capacidades de interconexión TCP/IP. Existen libreríasde rutinas para acceder e interactuar con protocolos como http y ftp. Esto permite a losprogramadores acceder a la información a través de la red con tanta facilidad como a losficheros locales.

La verdad es que Java en sí no es distribuido, sino que proporciona las librerías y herra-mientas para que los programas puedan ser distribuidos, es decir, que se corran en varias

Tutorial de Java 19

máquinas, interactuando.

Es ROBUSTO:

Java realiza verificaciones en busca de problemas tanto en tiempo de compilación como entiempo de ejecución. La comprobación de tipos en Java ayuda a detectar errores, lo antesposible, en el ciclo de desarrollo. Java obliga a la declaración explícita de métodos, redu-ciendo así las posibilidades de error. Maneja la memoria para eliminar las preocupacionespor parte del programador de la liberación o corrupción de memoria. También implementalos arrays auténticos, en vez de listas enlazadas de punteros, con comprobación de límites,para evitar la posibilidad de sobreescribir o corromper memoria resultado de punteros queseñalan a zonas equivocadas. Estas características reducen drásticamente el tiempo dedesarrollo de aplicaciones en Java.

Además, para asegurar el funcionamiento de la aplicación, realiza una verificación de losbyte-codes, que son el resultado de la compilación de un programa Java. Es un código demáquina virtual que es interpretado por el intérprete Java. No es el código máquina directa-mente entendible por el hardware, pero ya ha pasado todas las fases del compilador: análi-sis de instrucciones, orden de operadores, etc., y ya tiene generada la pila de ejecución deórdenes.

Java proporciona, pues:

· Comprobación de punteros· Comprobación de límites de arrays· Excepciones· Verificación de byte-codes

Es de ARQUITECTURA NEUTRAL :

Para establecer Java como parte integral de la red, el compilador Java compila su código aun fichero objeto de formato independiente de la arquitectura de la máquina en que seejecutará. Cualquier máquina que tenga el sistema de ejecución (run-time) puede ejecutarese código objeto, sin importar en modo alguno la máquina en que ha sido generado. Ac-tualmente existen sistemas run-time para Solaris 2.x, SunOs 4.1.x, Windows 95, WindowsNT, Linux, Irix, Aix, Mac, Apple y probablemente haya grupos de desarrollo trabajando en elporting a otras plataformas.

20 Tutorial de Java

El código fuente Java se «compila» a un código de bytes de alto nivel independiente de lamáquina. Este código (byte-codes) está diseñado para ejecutarse en una máquina hipotéti-ca que es implementada por un sistema run-time, que sí es dependiente de la máquina.

En una representación en que tuviésemos que indicar todos los elementos que forman partede la arquitectura de Java sobre una plataforma genérica, obtendríamos una figura como lasiguiente:

En ella podemos ver que lo verdaderamente dependiente del sistema es la Máquina Virtual

Tutorial de Java 21

Java (JVM) y las librerías fundamentales, que también nos permitirían acceder directamenteal hardware de la máquina. Además, habrá APIs de Java que también entren en contactodirecto con el hardware y serán dependientes de la máquina, como ejemplo de este tipo deAPIs podemos citar:

· Java 2D: gráficos 2D y manipulación de imágenes· Java Media Framework : Elementos críticos en el tiempo: audio, video...· Java Animation: Animación de objetos en 2D· Java Telephony: Integración con telefonía· Java Share: Interacción entre aplicaciones multiusuario· Java 3D: Gráficos 3D y su manipulación

Es SEGURO:

La seguridad en Java tiene dos facetas. En el lenguaje, características como los punteros oel casting implícito que hacen los compiladores de C y C++ se eliminan para prevenir elacceso ilegal a la memoria. Cuando se usa Java para crear un navegador, se combinan lascaracterísticas del lenguaje con protecciones de sentido común aplicadas al propio navegador.

El lenguaje C, por ejemplo, tiene lagunas de seguridad importantes, como son los errores dealineación. Los programadores de C utilizan punteros en conjunción con operaciones arit-méticas. Esto le permite al programador que un puntero referencie a un lugar conocido de lamemoria y pueda sumar (o restar) algún valor, para referirse a otro lugar de la memoria. Siotros programadores conocen nuestras estructuras de datos pueden extraer informaciónconfidencial de nuestro sistema. Con un lenguaje como C, se pueden tomar números ente-ros aleatorios y convertirlos en punteros para luego acceder a la memoria:

printf( «Escribe un valor entero: « ); scanf( «%u»,&puntero ); printf( «Cadena de memoria: %s\n»,puntero );

Otra laguna de seguridad u otro tipo de ataque, es el Caballo de Troya. Se presenta unprograma como una utilidad, resultando tener una funcionalidad destructiva. Por ejemplo,en UNIX se visualiza el contenido de un directorio con el comando ls. Si un programadordeja un comando destructivo bajo esta referencia, se puede correr el riesgo de ejecutarcódigo malicioso, aunque el comando siga haciendo la funcionalidad que se le supone,después de lanzar su carga destructiva. Por ejemplo, después de que el caballo de Troyahaya enviado por correo el /etc/shadow a su creador, ejecuta la funcionalidad de ls persentandoel contenido del directorio. Se notará un retardo, pero nada inusual.

El código Java pasa muchos tests antes de ejecutarse en una máquina. El código se pasa através de un verificador de byte-codes que comprueba el formato de los fragmentos decódigo y aplica un probador de teoremas para detectar fragmentos de código ilegal -códigoque falsea punteros, viola derechos de acceso sobre objetos o intenta cambiar el tipo oclase de un objeto-.

Si los byte-codes pasan la verificación sin generar ningún mensaje de error, entonces sabe-mos que:

· El código no produce desbordamiento de operandos en la pila· El tipo de los parámetros de todos los códigos de operación son conocidos ycorrectos

22 Tutorial de Java

· No ha ocurrido ninguna conversión ilegal de datos, tal como convertir enterosen punteros· El acceso a los campos de un objeto se sabe que es legal: public, private,protected· No hay ningún intento de violar las reglas de acceso y seguridad establecidas

El Cargador de Clases también ayuda a Java a mantener su seguridad, separando el espa-cio de nombres del sistema de ficheros local, del de los recursos procedentes de la red. Estolimita cualquier aplicación del tipo Caballo de Troya, ya que las clases se buscan primeroentre las locales y luego entre las procedentes del exterior.

Las clases importadas de la red se almacenan en un espacio de nombres privado, asociadocon el origen. Cuando una clase del espacio de nombres privado accede a otra clase, prime-ro se busca en las clases predefinidas (del sistema local) y luego en el espacio de nombresde la clase que hace la referencia. Esto imposibilita que una clase suplante a una predefinida.

En resumen, las aplicaciones de Java resultan extremadamente seguras, ya que no acce-den a zonas delicadas de memoria o de sistema, con lo cual evitan la interacción de ciertosvirus. Java no posee una semántica específica para modificar la pila de programa, la memo-ria libre o utilizar objetos y métodos de un programa sin los privilegios del kernel del sistemaoperativo. Además, para evitar modificaciones por parte de los crackers de la red, implementaun método ultraseguro de autentificación por clave pública. El Cargador de Clases puedeverificar una firma digital antes de realizar una instancia de un objeto. Por tanto, ningúnobjeto se crea y almacena en memoria, sin que se validen los privilegios de acceso. Esdecir, la seguridad se integra en el momento de compilación, con el nivel de detalle y deprivilegio que sea necesario.

Dada, pues la concepción del lenguaje y si todos los elementos se mantienen dentro delestándar marcado por Sun, no hay peligro. Java imposibilita, también, abrir ningún ficherode la máquina local (siempre que se realizan operaciones con archivos, éstas trabajan so-bre el disco duro de la máquina de donde partió el applet), no permite ejecutar ningunaaplicación nativa de una plataforma e impide que se utilicen otros ordenadores como puen-te, es decir, nadie puede utilizar nuestra máquina para hacer peticiones o realizar operacio-nes con otra. Además, los intérpretes que incorporan los navegadores de la Web son aúnmás restrictivos. Bajo estas condiciones (y dentro de la filosofía de que el único ordenadorseguro es el que está apagado, desenchufado, dentro de una cámara acorazada en unbunker y rodeado por mil soldados de los cuerpos especiales del ejército), se puede consi-derar que Java es un lenguaje seguro y que los applets están libres de virus.

Respecto a la seguridad del código fuente, no ya del lenguaje, JDK proporciona undesemsamblador de byte-code, que permite que cualquier programa pueda ser convertido acódigo fuente, lo que para el programador significa una vulnerabilidad total a su código.Utilizando javap no se obtiene el código fuente original, pero sí desmonta el programa mos-trando el algoritmo que se utiliza, que es lo realmente interesante. La protección de losprogramadores ante esto es utilizar llamadas a programas nativos, externos (incluso en C oC++) de forma que no sea descompilable todo el código; aunque así se pierda portabilidad.Esta es otra de las cuestiones que Java tiene pendientes.

Tutorial de Java 23

Es PORTABLE :

Más allá de la portabilidad básica por ser de arquitectura independiente, Java implementaotros estándares de portabilidad para facilitar el desarrollo. Los enteros son siempre ente-ros y además, enteros de 32 bits en complemento a 2. Además, Java construye sus interfacesde usuario a través de un sistema abstracto de ventanas de forma que las ventanas puedanser implantadas en entornos Unix, Pc o Mac.

Es INTERPRETADO:

El intérprete Java (sistema run-time) puede ejecutar directamente el código objeto. Enlazar(linkar) un programa, normalmente, consume menos recursos que compilarlo, por lo que losdesarrolladores con Java pasarán más tiempo desarrollando y menos esperando por elordenador. No obstante, el compilador actual del JDK es bastante lento. Por ahora, quetodavía no hay compiladores específicos de Java para las diversas plataformas, Java esmás lento que otros lenguajes de programación, como C++, ya que debe ser interpretado yno ejecutado como sucede en cualquier programa tradicional.

Se dice que Java es de 10 a 30 veces más lento que C, y que tampoco existen en Javaproyectos de gran envergadura como en otros lenguajes. La verdad es que ya hay compara-ciones ventajosas entre Java y el resto de los lenguajes de programación, y una ingentecantidad de folletos electrónicos que supuran fanatismo en favor y en contra de los distintoslenguajes contendientes con Java. Lo que se suele dejar de lado en todo esto, es queprimero habría que decidir hasta que punto Java, un lenguaje en pleno desarrollo y todavíasin definición definitiva, está maduro como lenguaje de programación para ser comparadocon otros; como por ejemplo con Smalltalk, que lleva más de 20 años en cancha.

La verdad es que Java para conseguir ser un lenguaje independiente del sistema operativoy del procesador que incorpore la máquina utilizada, es tanto interpretado como compilado.Y esto no es ningún contrasentido, me explico, el código fuente escrito con cualquier editorse compila generando el byte-code. Este código intermedio es de muy bajo nivel, pero sinalcanzar las instrucciones máquina propias de cada plataforma y no tiene nada que ver conel p-code de Visual Basic. El byte-code corresponde al 80% de las instrucciones de la apli-cación. Ese mismo código es el que se puede ejecutar sobre cualquier plataforma. Para ellohace falta el run-time, que sí es completamente dependiente de la máquina y del sistemaoperativo, que interpreta dinámicamente el byte-code y añade el 20% de instrucciones quefaltaban para su ejecución. Con este sistema es fácil crear aplicaciones multiplataforma,pero para ejecutarlas es necesario que exista el run-time correspondiente al sistema opera-tivo utilizado.

Es MULTITHREADED :

Al ser multithreaded (multihilvanado, en mala traducción), Java permite muchas actividadessimultáneas en un programa. Los threads (a veces llamados, procesos ligeros), son básica-mente pequeños procesos o piezas independientes de un gran proceso. Al estar los threadscontruidos en el lenguaje, son más fáciles de usar y más robustos que sus homólogos en Co C++.

El beneficio de ser miltithreaded consiste en un mejor rendimiento interactivo y mejor com-

24 Tutorial de Java

portamiento en tiempo real. Aunque el comportamiento en tiempo real está limitado a lascapacidades del sistema operativo subyacente (Unix, Windows, etc.), aún supera a losentornos de flujo único de programa (single-threaded) tanto en facilidad de desarrollo comoen rendimiento.

Cualquiera que haya utilizado la tecnología de navegación concurrente, sabe lo frustranteque puede ser esperar por una gran imagen que se está trayendo. En Java, las imágenes sepueden ir trayendo en un thread independiente, permitiendo que el usuario pueda acceder ala información en la página sin tener que esperar por el navegador.

Es DINAMICO:

Java se beneficia todo lo posible de la tecnología orientada a objetos. Java no intenta co-nectar todos los módulos que comprenden una aplicación hasta el tiempo de ejecución. Laslibrería nuevas o actualizadas no paralizarán las aplicaciones actuales (siempre que man-tengan el API anterior).

Java también simplifica el uso de protocolos nuevos o actualizados. Si su sistema ejecutauna aplicación Java sobre la red y encuentra una pieza de la aplicación que no sabe mane-jar, tal como se ha explicado en párrafos anteriores, Java es capaz de traer automáticamentecualquiera de esas piezas que el sistema necesita para funcionar.

Tutorial de Java 25

Java, para evitar que los módulos de byte-codes o los objetos o nuevas clases, haya queestar trayéndolos de la red cada vez que se necesiten, implementa las opciones de persis-tencia, para que no se eliminen cuando de limpie la caché de la máquina.

HotJavaHotJava, en pocas palabras, es un navegador con soporte Java (Java-enabled), desarrolla-do en Java. Como cualquier navegador de Web, HotJava puede decodificar HTML estándary URLs estándares, aunque no soporta completamente el estándar HTML 3.0. La ventajasobre el resto de navegadores, sin soporte Java, es que puede ejecutar programas Javasobre la red. La diferencia con Netscape, es que tiene implementado completamente lossistemas de seguridad que propone Java, esto significa que puede escribir y leer en el discolocal, aunque esto hace disminuir la seguridad, ya que se pueden grabar en nuestro discoprogramas que contengan código malicioso e introducirnos un virus, por ejemplo. No obs-tante, el utilizar esta característica de HotJava es decisión del usuario.

Java para aplicaciones corporativasJava actualmente está en boca de todos, Java e Intranet son las palabras de moda. Pero,surge la pregunta de si esta es una buena tecnología para desarrollar aplicaciones corpora-tivas. Y la respuesta es afirmativa y voy a proponer argumentos para esa afirmación. Endonde la red sea algo crítico, Java facilita tremendamente la vida de la programación corpo-rativa.

Durante años, las grandes empresas se han convencido de que la «red» corporativa es laarteria por donde fluye la sangre que mantiene vivo su negocio. Desde el gran servidor desus oficinas centrales, hasta los servidores de las delegaciones, las estaciones de trabajode los programadores y la marabunta de PCs, la información va fluyendo de unos a otros.Para muchas compañías, la Red es la Empresa.

Si esta red no se mantiene sana, los pedidos no llegan, el inventario no se actualiza, elsoftware no se desarrolla adecuadamente, los clientes no están satisfechos y, fundamental-mente, el dinero no entra. La necesidad de diagnosticar y reducir la arterioesclerosis de lared, hace que se estén inyectando continuamente nuevas metodologías que subsanen estegrave problema.

¿Es Java la medicina? Está claro que cuando vemos un cepillo animado limpiando los dien-tes, cubos moviéndose en 3-D, o una banda de gatos locos en applets de Java, nos conven-cemos de que es el lenguaje idóneo para Internet. Pero, qué pasa con las aplicacionescorporativas, ¿sería una buena tecnología allí donde la red es el punto crítico? Vamos aintentar responder comparando las capacidades de Java contra la lista de necesidades dela red corporativa.

26 Tutorial de Java

Desarrollo rápido de aplicaciones

Hace años, se decía que los programadores pronto desaparecerían. Los generadores auto-máticos de programas, eliminarían a los generadores humanos y el mundo sería un lugarmejor para vivir. Desafortunadamente, quienes decían esto no tuvieron en cuenta una ace-lerada demanda de software de calidad para muy diferentes aplicaciones. Sin embargo, latecnología de objetos pronto vino a intentar facilitar la tarea, adoptando el modelo de «gene-rar parte de un programa», así, generando la parte básica de un programa (los objetos), sepodría conectar con otras partes para proporcionar diferentes utilidades al usuario.

El lenguaje C++ es una buena herramienta, pero no cumple totalmente la premisa. VisualBasic y NextStep, se acercan cada vez más al poder de los objetos. Java facilita la creaciónde entornos de desarrollo-aplicaciones de modo similar, pero además es flexible, poderosoy efectivo. Los programadores ahora disponen de herramientas de programación de calidadbeta, que apuntan hacia esa meta, como son el Java WorkShop de SunSoft, el entorno Javade Borland, el Café de Symantec, y pronto, herramientas más sofisticadas como Netcode oFutureTense. Esto proporciona una gran progresión a los entornos de desarrollo Java.

Aplicaciones efectivas y eficientes

Las aplicaciones que se crean en grandes empresas deben ser más efectivas que eficien-tes; es decir, conseguir que el programa funcione y el trabajo salga adelante es más impor-tante que el que lo haga eficientemente. Esto no es una crítica, es una realidad de la progra-mación corporativa. Al ser un lenguaje más simple que cualquiera de los que ahora están enel cajón de los programadores, Java permite a éstos concentrarse en la mecánica de laaplicación, en vez de pasarse horas y horas incorporando APIs para el control de las venta-nas, controlando minuciosamente la memoria, sincronizando los ficheros de cabecera ycorrigiendo los agónicos mensajes del linker. Java tiene su propio toolkit para interfaces,maneja por sí mismo la memoria que utilice la aplicación, no permite ficheros de cabeceraseparados (en aplicaciones puramente Java) y solamente usa enlace dinámico.

Muchas de las implementaciones de Java actuales son puros intérpretes. Los byte-codesson interpretados por el sistema run-time de Java, la Máquina Virtual Java (JVM), sobre elordenador del usuario. Aunque ya hay ciertos proveedores que ofrecen compiladores nati-vos Just-In-Time (JIT). Si la Máquina Virtual Java dispone de un compilador instalado, lassecciones (clases) del byte-code de la aplicación se compilarán hacia la arquitectura nativadel ordenador del usuario. Los programas Java en ese momento rivalizarán con el rendi-miento de programas en C++. Los compiladores JIT no se utilizan en la forma tradicional deun compilador; los programadores no compilan y distribuyen binarios Java a los usuarios.La compilación JIT tiene lugar a partir del byte-code Java, en el sistema del usuario, comouna parte (opcional) del entorno run-time local de Java.

Muchas veces, los programadores corporativos, ansiosos por exprimir al máximo la eficien-cia de su aplicación, empiezan a hacerlo demasiado pronto en el ciclo de vida de la aplica-ción. Java permite algunas técnicas innovadoras de optimización. Por ejemplo, Java esinherentemente multithreaded, a la vez que ofrece posibilidades de multithread como la

Tutorial de Java 27

clase Thread y mecanismos muy sencillos de usar de sincronización; Java en sí utilizathreads. Los desarrolladores de compiladores inteligentes pueden utilizar esta característi-ca de Java para lanzar un thread que compruebe la forma en que se está utilizando laaplicación. Más específicamente, este thread podría detectar qué métodos de una clase seestán usando con más frecuencia e invocar a sucesivos niveles de optimización en tiempode ejecución de la aplicación. Cuanto más tiempo esté corriendo la aplicación o el applet,los métodos estarán cada vez más optimizados (Guava de Softway es de este tipo).

Si un compilador JIT está embebido en el entorno run-time de Java, el programador no sepreocupa de hacer que la aplicación se ejecute óptimamente. Siempre he pensado que enlos Sistemas Operativos tendría que aplicarse esta filosofía; un optimizador progresivo esun paso más hacia esta idea.

Portabilidad para programador y programa

En una empresa de relativo tamaño hay una pléyade diferente de ordenadores. Probable-mente nos encontremos con estaciones de trabajo Sun para el desarrollo de software, hor-das de PCs para cada empleado, algún Mac en el departamento de documentación, unaestación de trabajo HP en administración y una estación SGI en la sala de demos. Desarro-llar aplicaciones corporativas para un grupo tan diferente de plataformas en excesivamentecomplejo y caro. Hasta ahora era complicado convencer a los programadores de cada arqui-tectura que utilizasen un API común para reducir el coste de las aplicaciones.

Con un entorno run-time de Java portado a cada una de las arquitecturas de las plataformaspresentes en la empresa y una buena librería de clases («packages» en Java), los progra-madores pueden entenderse y encontrar muy interesante trabajar con Java. Esta posibilidadhará tender a los programadores hacia Java, justo donde otros intentos anteriores conentornos universales (como Galaxy o XVT) han fracasado. Estos APIs eran simplementeinadecuados, no orientados a redes y, verdaderamente, pesados.

Una vez que los programas estén escritos en Java, otro lado interesante del asunto es quelos programadores también son portables. El grupo de programadores de la empresa puedeahora enfrentarse a un desarrollo para cualquiera de las plataformas. La parte del cliente ydel servidor de una aplicación estarán ahora escritas en el mismo lenguaje. Ya no seránecesario tener un grupo que desarrolle en Solaris en del departamento de I+D, programa-dores trabajando sobre Visual Basic en el departamento de documentación y programado-res sobre GNU en proyectos especiales; ahora todos ellos podrán estar juntos y formar elgrupo de software de la empresa.

Costes de desarrollo

En contraste con el alto coste de los desarrollos realizados sobre estaciones de trabajo, elcoste de creación de una aplicación Java es similar al de desarrollar sobre un PC.

Desarrollar utilizando un software caro para una estación de trabajo (ahora barata) es un

28 Tutorial de Java

problema en muchas empresas. La eficiencia del hardware y el poco coste de mantenimien-to de una estación de trabajo Sun, por ejemplo, resulta muy atractivo para las empresas;pero el coste adicional del entorno de desarrollo con C++ es prohibitivo para la gran mayoríade ellas. La llegada de Java e Intranet reducen considerablemente estos costes. Las herra-mientas Java ya no están en el entorno de precios de millones de pesetas, sino a los nivelesconfortables de precio de las herramientas de PCs. Y con el crecimiento cada día mayor dela comunidad de desarrolladores de software freeware y shareware que incluso proporcio-nan el código fuente, los programadores corporativos tienen un amplio campo donde mover-se y muchas oportunidades de aprender y muchos recursos a su disposición.

El éxito que Internet ha proporcionado a los equipos de software corporativos es un regalo.El precio del software es ahora el mismo para un poderoso equipo corriendo Unix que paraun PC. Incluso Netscape tiene al mismo precio la versión Unix de su servidor Web SuiteSpotque la versión PC/NT. Esta es la filosofía de precios que parece ser será la que se siga conlas herramientas basadas en Java.

Mantenimiento y soporte

Un problema bien conocido que ocurre con el software corporativo es la demanda de cuida-dos y realimentación. Java no es, ciertamente, la cura para la enfermedad del mantenimien-to, pero tiene varias características que harán la vida del enfermero más fácil.

Uno de los componentes del JDK es javadoc. Si se usan ciertas convenciones en el códigofuente Java (como comenzar un comentario con /** y terminarlo con */), javadoc se puedefácilmente generar páginas HTML con el contenido de esos comentarios, que puedenvisualizarse en cualquier navegador. La documentación del API de Java ha sido creada deeste modo. Esto hace que el trabajo de documentar el código de nuevas clases Java seatrivial.

Otro gran problema del desarrollador corporativo es la creación y control de makefiles. Leer-se un makefile es como estar leyendo la historia de empresa. Normalmente se pasan deprogramador a programador, quitando la información que no es esencial, siempre que sepuede. Esto hace que muchos de los makefiles de las aplicaciones contengan docenas delibrerías, una miríada de ficheros de cabecera y ultra-confusos macros. Es como mirar en elestómago de la ballena de Jonás.

Java reduce las dependencia de complejos makefiles drásticamente. Primero, no hay fiche-ros de cabecera. Java necesita que todo el código fuente de una clase se encuentre en unsolo fichero. Java tiene la inteligencia de make en el propio lenguaje para simplificar lacompilación de byte-codes. Por ejemplo:

public class pepe { // Fichero: pepe.java Guitarra flamenca ; } public class guitarra { // Fichero: guitarra.java }% javac -verbose pepe.java[parsed pepe.java in 720ms][loaded C:\JAVA\BIN\..\classes\java\lang\Object.class in 220ms]

Tutorial de Java 29

[checking class pepe][parsed .\\Guitarra.java in 50ms][wrote pepe.class][checking class Guitarra][wrote .\\Guitarra.class][done in 2300ms]

El compilador Java se da cuenta de que necesita compilar el fichero guitarra.java. Ahoravamos a forzarlo a que recompile pepe.java sin cambiar guitarra.java, podremos comprobarque el compilador de byte-code Java no recompila innecesariamente el fichero guitarra.java.

% javac -verbose pepe.java[parsed pepe.java in 440ms][loaded C:\JAVA\BIN\..\classes\java\lang\Object.class in 160ms][checking class pepe][loaded .\\Guitarra.java in 0ms][wrote pepe.class][done in 1860ms]

Ahora, si modificamos guitarra.java (añadiendo, por ejemplo, otro miembro a la clase) ycompilamos pepe.java, el compilador Java se dará cuenta de que debe recompilar tantopepe.java como guitarra.java

% javac -verbose pepe.java[parsed pepe.java in 710ms][loaded C:\JAVA\BIN\..\classes\java\lang\Object.class in 220ms][checking class pepe][parsed .\\Guitarra.java in 0ms][wrote pepe.class][checking class Guitarra][wrote .\\Guitarra.class][done in 2640ms]

En el libro Just Java de Peter van der Linden hay un capítulo excelente acerca del compiladorde Java, si tienes oportunidad, no dejes de leerlo.

Aprendizaje

Si la empresa está llena de programadores de C++ con alguna experiencia en el manejo delibrería gráficas, aprenderán rápidamente lo esencial de Java. Si el equipo de ingenieros noconoce C++, pero maneja cualquier otro lenguaje de programación orientada a objetos, lesllevará pocas semanas dominar la base de Java. Lo que sí que no es cierto es que haya queaprender C++ antes de aprender Java.

Si los ingenieros de la empresa no conocen ningún lenguaje orientado a objetos, sí quetienen que aprender los fundamentos de esta tecnología antes de nada, y luego aplicarlos ala programación con Java. El análisis y diseño orientado a objetos debe ser comprendidoantes de intentar nada con Java. Los programadores de Java sin un fondo de conocimientosde OOA/D producirán código pobre. Además, los libros sobre Java crecen como la espuma,ya hay más de 25 publicados, y si buscas «Progamming in Java» en la Red, encontrarás 312Web sites, y 30 más dedicados a «Learning Java». Y si esto, evidentemente, no es el susti-tuto de un instructor humano, hay ya varias empresas que ofrecen enseñanza de Java, entreellas, Sun.

30 Tutorial de Java

Resumen

En base a los argumentos que acabamos de exponer, ¿podría una empresa utilizar Javapara sus aplicaciones críticas? En este instante, sería suficiente un acercamiento a Java.Porque más importante que la elección de Java o cualquier otro lenguaje de programaciónes un buen diseño de la arquitectura de la aplicación. Diseñar la aplicación para que estédistribuida entre servidores y clientes, y la línea de partida debe ser el diseño modular.

Algunas sugerencias para adoptar Java como tecnología corporativa, serían:

Usar Java en el desarrollo de la interface del cliente; Java es suficientemente establepara desarrollar una interface portable. Utilizar herramientas de programación másestables en los servidores, porque son la parte crítica.

Portar o crear un servidor no-crítico en Java, de forma que tanto cliente como servi-dor estén escritos en Java.

Utilizar Java en proyectos de envergadura tanto en el cliente como en el servidor,para valorar la efectividad de Java.

Intranet está creciendo actualmente más rápido que Internet. Las organizaciones corporati-vas están adoptando la metodología Internet para proporcionar soluciones a sus usuarios yclientes. Java tiene todas las cartas para ser una herramienta de inestimable valor en eldesarrollo de aplicaciones corporativas.

Tutorial de Java 31

Capítulo 2Instalación del JDK

En el instante en que estoy escribiendo esto, Java todavía no es un producto comercial. Notodas las máquinas disponen de la versión del Java Development Kit para ejecutarse enellas. Por ello, solamente comentaré la instalación de JDK en Solaris, Windows 95 y Linux.

Actualmente ya hay entornos de desarrollo integrados completos para Java, diferentes delJDK de Sun. Symantec dispone de un compilador de Java para Windows 95 y Windows NT,con las ventajas del aumento de velocidad de proceso y capacidades multimedia que estoproporciona, Symantec Café. Borland también está trabajando en ello y la nueva versión desu entorno de desarrollo soporta Java. Sun ha lanzado la versión comercial de su propioentorno de desarrollo para Java, el Java Workshop, enteramente escrito en Java. Y Microsoftha puesto en el mercado Visual J++, que sigue el estilo de todas sus herramientas de desa-rrollo.

No obstante, trataremos solamente el JDK, que hasta el momento es lo más conocido. Elentorno básico del JDK de Java que proporciona Sun está formado por herramientas enmodo texto, que son: java, intérprete que ejecuta programas en byte-code. javac, compiladorde Java que convierte el código fuente en byte-code. javah, crea ficheros de cabecera paraimplementar métodos para cualquier clase. javap, es un descompilador de byte-code a códi-go fuente Java. javadoc, es un generador automático de documentos HTML a partir delcódigo fuente Java. javaprof, es un profiler para aplicaciones de un solo thread. HotJava, esun navegador Web escrito completamente en Java.

El entorno habitual pues, consiste en un navegador que pueda ejecutar applets, un compiladorque convierta el código fuente Java a byte-code y el intérprete Java para ejecutar los progra-mas. Estos son los componenetes básicos para desarrollar algo en Java. No obstante senecesita un editor para escribir el código fuente, y no son estrictamente necesarias otrasherramientas como el debugger, un entorno visual, la documentación o un visualizador dejerarquía de clases. Tan es así, que disponiendo del navegador Netscape 2.0 no se necesitani tan siquiera el JDK (a petición de varios amigos que disfrutan del uso de Linux pero nodisponen de soporte ELF para poder utilizar el JDK portado por Randy Chapman, les indica-ré como conseguir utilizar el compilador embebido en Netscape).

32 Tutorial de Java

Windows

La versión del JDK para Windows es un archivo autoextraible. Se necesitan alrededor de 6Mb de espacio libre en disco. Ejecutar el fichero, que desempaquetará el contenido delarchivo. El directorio donde se instale no es importante, pero supondremos que se instala enel raiz del disco C:, en cuyo caso los archivos colgarán de c:\java. Es necesario añadir c:\java\bina la variable de entorno PATH.

Además de los ficheros java, el JDK incluye dos librerías dinámicas, MSVCRT20.DLL yMFC30.DLL, que se instalarán en el directorio de Java. Si tienes ninguna copia de estosficheros en tu ordenador (probablemente en el directorio system de Windows) copia estosficheros en el directorio c:\java\bin. Si estos ficheros ya están en tu ordenador, elimina lascopias extra que instala el JDK.

Solaris

La versión del JDK para Solaris es un fichero tar comprimido. Se necesitan alrededor de 9Mb de disco para descomprimir el JDK, aunque el doble de espacio sería una cifra máscómoda. Ejecutar los siguientes comandos:

% uncompress JDK-beta-solaris2-sparc.tar.Z% tar xvf JDK-beta-solaris2-sparc-tar

Puedes descomprimir el archivo en tu directorio home, o, si tienes privilegios de supervisor,en algún sitio más conveniente de /usr/local para que todos los usuarios tengan acceso a losficheros. Sin embargo, los privilegios del supervisor no son necesarios para instalar y ejecu-tar Java. Por simplicidad, supondré que has descomprimido el JDK en /usr/local, aunque elpath completo donde se haga no tiene relevancia (también es posible colocarlo en /opt quees donde residen todas las aplicaciones de Solaris). Si lo has colocado en un sitio diferente,simplemente sustituye /usr/local por ese directorio (si lo has descomprimido en tu home, pue-des utilizar ~/java y ~/hotjava, en vez del path completo).

Es necesario añadir /usr/local/java/bin a la variable de entorno PATH. Utiliza el siguiente co-mando (suponiendo que tengas el shell csh o tcsh):

set path=($PATH /usr/local/java/bin)

También puedes añadir esta línea al final del fichero .profile y .cshrc, y ya tienes el sistemalisto para ejecutar applets. Si quieres desembarazarte de la ventana que aparece cada vezque lances el appletviewer con la licencia de Sun, crea un directorio que se llame .hotjava enel directorio java/bin y ya no volverás a verla.

Linux

Necesitas un kernel que soporte binarios ELF, por lo tanto tu Linux debe ser la versión1.2.13 o superior, las anteriores tienen un bug que hacen que javac no funcione. Necesitastambién Netscape, versión 2.0b4 o posterior. Sobre la versión 1.2.13 del kernel de Linux,hay que seguir los pasos que indico para conseguir que JDK funcione:

Tutorial de Java 33

· Bajarse el JDK, · linux.jdk-1.0-try4.static-motif.tar.gz y · l i nux . j dk -1 .0 -try1.common.tar.gz a /usr/local, descomprimirlo y hacer ‘tar xvf’· En el fichero .java_wrapper (si no existe, crearlo) cambiar las variable J_HOMEy PRG, para que queden como:

J_HOME=/usr/local/javaPRG=/usr/local/java/bin

· Bajarse la librería · libc.5.2.18.bin.tar.gz a /, descomprimirla, hacer ‘tar xvf’.Asegurarse de que /lib/libc.so.5 es un link simbólico a este nuevo fichero. Si no lo es,hacer el /lib ‘ln -s libc.so.5.2.18 libc.so.5’· Bajarse · ld-so.1.7.14.tar.gz a un directorio temporal, descomprimirlo y hacer‘tar xvf’. Ejecutar ‘instldso.sh’ y eliminar el directorio temporal.· Añadir /usr/local/java a la variable de entorno PATH. Si se desea que estéfijada para todos los usuarios, incorporar el directorio a la varible PATH que se fija enel fichero /etc/profile.· Bajarse netscape-v202-export.i486-unknown-linux.tar.z a /usr/local/netscape,descomprimirlo y hacer ‘tar xvf’· Crear un link en /usr/local/bin a /usr/local/netscape/netscape

Esto debería ser suficiente para compilar cualquier cosa en Java/Linux. En caso de tenerproblemas, es el momento de recurrir a las FAQ.

Siguiendo los pasos indicados ya se puede ejecutar el ejemplo del Tic-Tac-Toe que proponela hoja de instalación que Sun ha incluido en todas sus versiones y que en Linux consistiríaen cambiarse al directorio de la demo:

% cd /usr/local/java/demo/TicTacToe

ejecutar el visualizador de applets sobre la página html:

% appletviewer example1.html

y a jugar a las tres en raya. Por cierto, que el algoritmo que usa el ordenador está falseadopor lo que es posible ganarle.

Compilación sin JDK

Parece raro, pero se puede conseguir. Lo único necesario es el navegador Netscape 2.0.Este navegador, junto con la máquina virtual Java (JVM) y el sistema run-time, tiene uncompilador Java. Si no se dispone del Java Development Kit (JDK), que no está disponiblepara todas las plataformas, pero sí de la versión de Netscape para nuestra plataforma, aquívan los pasos a seguir para utilizar el compilador de Java embebido en Netscape.

Como necesito partir de algún punto para tomarlo como referencia, voy a suponer que esta-mos sobre Linux y que vamos a prescindir del JDK de Randy Chapman. Lo que habría quehacer sería lo siguiente.

Primero . Instalar Netscape en el ordenador. Asegurarse de entender perfectamente y leer-se hasta el final el fichero README, para seguir las instrucciones específicas de la instala-ción de Netscape en la plataforma y que Netscape funcione perfectamente. En nuestro caso,en que vamos a intentar compilar código Java con Netscape sobre Linux, la pieza clave es

34 Tutorial de Java

la situación del fichero moz2_0.zip, que en mi máquina está en /usr/local/netscape/java/classes.

Segundo . Extraer de una copia cualquiera del JDK (aunque sea de otra plataforma), elfichero java/lib/classes.zip y guardarlo en el mismo sitio que el fichero moz2_0.zip; estalocalización no es necesaria, pero simplifica la estructura.

Tercero . Fijar la variable de entorno CLASSPATH para que Netscape pueda encontrar suspropias clases además de las clases del Java de Sun. Asegurarse de incluir el «directorioactual», para poder compilar a la vez que se usan los ficheros .zip de Netscape y Sun. Porejemplo:

setenv CLASSPATH .:/usr/local/netscape/java/classes/moz2_0.zip : /usr/local/netscape/java/classes/classes.zip

Cuarto . Compilar el código Java (applet o aplicación) con el comando:

netscape -java sun.tools.javac.Main [fichero].java

(sustituir el nombre del fichero con el código Java en vez de [fichero]). Esto convertirá elcódigo fuente Java en byte-code, generándose el archivo [fichero].class.

Quinto . Comprobar si se puede ejecutar la aplicación con el comando:

netscape -java [clase]

(sustituir el nombre de la clase de la aplicación -la que contiene la rutina main- en vez de[clase]).

Sexto . Si se ha compilado un applet Java, construir una página html que lo utilice paravisualizarlo con el navegador en su forma normal. O también se puede visualizar utilizandoel appletviewer, ejecutando:

netscape -java sun.applet.AppletViewer [clase]

Desgraciadamente, la sentencia anterior no parece funcionar en todos los sistemas. Hayamigos míos que no han sido capaces de visualizar applets con este método.

Para aprovechar el tiempo, se puede crear un script que recoja los pasos 3, 4 y 6. Si esta-mos utilizando el csh, el contenido del script sería:

#/bin/csh -f setenv CLASSPATH .:/usr/local/netscape/java/classes/moz2_0.zip: /usr/local/netscape/java/classes/classes.zipnetscape -java sun.tools.javac.Main $1

y lo almacenaríamos como javac. Se ha de hacer el script ejecutable y cambiar /bin/csh porel path completo donde esté situado el csh. De forma semejante podemos definir el intérpre-te java y el appletviewer, sustituyendo la línea adecuada de llamada a Netscape.

Tutorial de Java 35

Capítulo 3Conceptos básicos de Java

Ahora que ya hemos visto a grandes rasgos lo que Java puede ofrecernos, y antes de entrara saco en la generación de nuestro primer código Java, vamos a echar un vistazo al lengua-je Java en sí. Lo básico resultará muy familiar a los que tengan conocimientos de C/C++.Los programadores con experiencia en otros lenguajes procedurales reconocerán la mayorparte de las construcciones. Esperemos que este capítulo no resulte demasiado intenso, noobstante, sí debe estar presente, porque más de una vez recurriremos a él como referencia.En posteriores capítulos profundizaremos sobre aspectos de la programación en Java porlos que aquí pasaremos de puntillas e iremos presentando ejemplos de código de cada unode esos aspectos de la programación en Java.

Programación en JavaCuando se programa en Java, se coloca todo el código en métodos, de la misma forma quese escriben funciones en lenguajes como C.

Comentarios

En Java hay tres tipos de comentarios:

// comentarios para una sola línea /* comentarios de una o más líneas */ /** comentario de documentación, de una o más líneas */

Los dos primeros tipos de comentarios son los que todo programador conoce y se utilizandel mismo modo. Los comentarios de documentación, colocados inmediatamente antes deuna declaración (de variable o función), indican que ese comentario ha de ser colocado enla documentación que se genera automáticamente cuando se utiliza la herramienta de Java,javadoc. Dichos comentarios sirven como descripción del elemento declarado permitiendogenerar una documentación de nuestras clases escrita al mismo tiempo que se genera elcódigo.

En este tipo de comentario para documentación, se permite la introducción de algunos tokens

36 Tutorial de Java

o palabras clave, que harán que la información que les sigue aparezca de forma diferente alresto en la documentación.

Identificadores

Los identificadores nombran variables, funciones, clases y objetos; cualquier cosa que elprogramador necesite identificar o usar.

En Java, un identificador comienza con una letra, un subrayado (_) o un símbolo de dólar($). Los siguientes caracteres pueden ser letras o dígitos. Se distinguen las mayúsculas delas minúsculas y no hay longitud máxima.

Serían identificadores válidos:

identificador nombre_usuario Nombre_Usuario _variable_del_sistema $transaccion

y su uso sería, por ejemplo:

int contador_principal; char _lista_de_ficheros; float $cantidad_en_Ptas;

Palabras claveLas siguientes son las palabras clave que están definidas en Java y que no se puedenutilizar como indentificadores:

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

Palabras ReservadasAdemás, el lenguaje se reserva unas cuantas palabras más, pero que hasta ahora notienen un cometido específico. Son:

cast future generic inner operator outer rest var

Literales

Un valor constante en Java se crea utilizando una representación literal de él. Java utilizacinco tipos de elementos: enteros, reales en coma flotante, booleanos, caracteres y cade-nas, que se pueden poner en cualquier lugar del código fuente de Java. Cada uno de estos

Tutorial de Java 37

literales tiene un tipo correspondiente asociado con él.

Enteros:

byte 8 bits complemento a dos short 16 bits complemento a dos int 32 bits complemento a dos long 64 bits complemento a dos Por ejemplo: 21 077 0xDC00

Reales en coma flotante:

float 32 bits IEEE 754 double 64 bits IEEE 754 Por ejemplo: 3.14 2e12 3.1E12

Booleanos:

true false

Caracteres:

Por ejemplo: a \t \u???? [????] es un número unicode

Cadenas:

Por ejemplo: «Esto es una cadena literal»

Arrays

Se pueden declarar en Java arrays de cualquier tipo:

char s[]; int iArray[];

Incluso se pueden construir arrays de arrays:

int tabla[][] = new int[4][5];

Los límites de los arrays se comprueban en tiempo de ejecución para evitar desbordamien-tos y la corrupción de memoria.

En Java un array es realmente un objeto, porque tiene redefinido el operador []. Tiene unafunción miembro: length. Se puede utilizar este método para conocer la longitud de cual-quier array.

int a[][] = new int[10][3]; a.length; /* 10 */ a[0].length; /* 3 */

Para crear un array en Java hay dos métodos básicos. Crear un array vacío:

int lista[] = new int[50];

o se puede crear ya el array con sus valores iniciales:

String nombres[] = { «Juan»,»Pepe»,»Pedro»,»Maria» };

Esto que es equivalente a:

38 Tutorial de Java

String nombres[]; nombres = new String[4]; nombres[0] = new String( «Juan» ); nombres[1] = new String( «Pepe» ); nombres[2] = new String( «Pedro» ); nombres[3] = new String( «Maria» );

No se pueden crear arrays estáticos en tiempo de compilación:

int lista[50]; // generará un error en tiempo de compilación

Tampoco se puede rellenar un array sin declarar el tamaño con el operador new:

int lista[]; for( int i=0; i < 9; i++ ) lista[i] = i;

Es decir, todos los arrays en Java son estáticos. Para convertir un array en el equivalente aun array dinámico en C/C++, se usa la clase vector, que permite operaciones de inserción,borrado, etc. en el array.

Operadores

Los operadores de Java son muy parecidos en estilo y funcionamiento a los de C. En lasiguiente tabla aparecen los operadores que se utilizan en Java, por orden de precedencia:

. [] () ++ — ! ~ instanceof * / % + - << >> >>> < > <= >= == != & ^ | && || ? : = op= (*= /= %= += -= etc.) ,

Los operadores numéricos se comportan como esperamos:

int + int = int

Los operadores relacionales devuelven un valor booleano.

Para las cadenas, se pueden utilizar los operadores relacionales para comparaciones ade-más de + y += para la concatenación:

String nombre = «nombre» + «Apellido»;

El operador = siempre hace copias de objetos, marcando los antiguos para borrarlos, y ya seencargará el garbage collector de devolver al sistema la memoria ocupada por el objetoeliminado.

Tutorial de Java 39

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 separadoresadmitidos en Java son:

() - paréntesis. Para contener listas de parámetros en la definición y llamada a méto-dos. También se utiliza para definir precedencia en expresiones, contener expresio-nes para control de flujo y rodear las conversiones de tipo.

{} - llaves. Para contener los valores de matrices inicializadas automáticamente. Tam-bién se utiliza para definir un bloque de código, para clases, métodos y ámbitos loca-les.

[] - corchetes. Para declarar tipos matriz. También se utiliza cuando se referencianvalores de matriz.

; - punto y coma. Separa sentencias.

, - coma. Separa identificadores consecutivos en una declaración de variables. Tam-bién se utiliza para encadenar sentencias dentro de una sentencia for.

. - punto. Para separar nombres de paquete de subpaquetes y clases. También seutiliza para separar una variable o método de una variable de referencia.

Control de FlujoMuchas de las sentencias de control del flujo del programa se han tomado del C:

Sentencias de Salto

if/else if( Boolean ) { sentencias; } else { sentencias; }

switch switch( expr1 ) { case expr2: sentencias; break; case expr3: sentencias; break; default:

40 Tutorial de Java

sentencias; break; }

Sentencias de Bucle

Bucles for for( expr1 inicio; expr2 test; expr3 incremento ) { sentencias; }

El siguiente trocito de código Java que dibuja varias líneas en pantalla alternando sus colo-res entre rojo, azul y verde. Este fragmento sería parte de una función Java (método):

int contador; for( contador=1; contador <= 12; contador++ ) { switch( contador % 3 ) { case 0: setColor( Color.red ); break; case 1: setColor( Color.blue ); break; case 2: setColor( Color.green ); break; } g.drawLine( 10,contador*10,80,contador*10 ); }

También se soporta el operador coma (,) en los bucles for

for( a=0,b=0; a < 7; a++,b+=2 )

Bucles while while( Boolean ) { sentencias; }

Bucles do/while do { sentencias; }while( Boolean );

Excepciones

try-catch-throw try { sentencias; } catch( Exception ) { sentencias; }

Java implementa excepciones para facilitar la construcción de código robusto. Cuando ocu-

Tutorial de Java 41

rre un error en un programa, el código que encuentra el error lanza una excepción, que sepuede capturar y recuperarse de ella. Java proporciona muchas excepciones predefinidas.

Control General del Flujo break [etiqueta] continue [etiqueta] return expr; etiqueta: sentencia;

En caso de que nos encontremos con bucles anidados, se permite el uso de etiquetas parapoder salirse de ellos, por ejemplo:

uno: for( ) { dos: for( ) { continue; // seguiría en el bucle interno continue uno; // seguiría en el bucle principal break uno; // se saldría del bucle principal } }

En el código de una función siempre hay que ser consecuentes con la declaración que sehaya hecho de ella. Por ejemplo, si se declara una función para que devuelva un entero, esimprescindible que se coloque un return final para salir de esa función, independientementede que haya otros en medio del código que también provoquen la salida de la función. Encaso de no hacerlo se generará un Warning, y el código Java no se puede compilar conWarnings.

int func() { if( a == 0 ) return 1; return 0; // es imprescindible porque se retorna un entero }

ClasesLas clases son lo más simple de Java. Todo en Java forma parte de una clase, es una claseo describe como funciona una clase. El conocimiento de las clases es fundamental parapoder entender los programas Java.

Todas las acciones de los programas Java se colocan dentro del bloque de una clase o unobjeto. Todos los métodos se definen dentro del bloque de la clase, Java no soporta funcio-nes o variables globales. Esto puede despistar a los programadores de C++, que puedendefinir métodos fuera del bloque de la clase, pero esta posibilidad es más un intento de nosepararse mucho y ser compatible con C, que un buen diseño orientado a objetos. Así pues,el esqueleto de cualquier aplicación Java se basa en la definición de una clase.

Todos los datos básicos, como los enteros, se deben declarar en las clases antes de hacer

42 Tutorial de Java

uso de ellos. En C la unidad fundamental son los ficheros con código fuente, en Java son lasclases. De hecho son pocas las sentencias que se pueden colocar fuera del bloque de unaclase. La palabra clave import (equivalente al #include) puede colocarse al principio de unfichero, fuera del bloque de la clase. Sin embargo, el compilador reemplazará esa sentenciacon el contenido del fichero que se indique, que consistirá, como es de suponer, en másclases.

Tipos de Clases

Hasta ahora sólo se ha utilizado la palabra clave public para calificar el nombre de las clasesque hemos visto, pero hay tres modificadores más. Los tipos de clases que podemos definirson:

abstract

Una clase abstract tiene al menos un método abstracto. Una clase abstracta no seinstancia, sino que se utiliza como clase base para la herencia.

final

Una clase final se declara como la clase que termina una cadena de herencia. No sepuede heredar de una clase final. Por ejemplo, la clase Math es una clase final.

public

Las clases public son accesibles desde otras clases, bien sea directamente o porherencia. Son accesibles dentro del mismo paquete en el que se han declarado. Paraacceder desde otros paquetes, primero tienen que ser importadas.

synchronizable

Este modificador especifica que todos los métodos definidos en la clase sonsincronizados, es decir, que no se puede acceder al mismo tiempo a ellos desdedistintos threads; el sistema se encarga de colocar los flags necesarios para evitarlo.Este mecanismo hace que desde threads diferentes se puedan modificar las mismasvariables sin que haya problemas de que se sobreescriban.

Variables y métodos de instanciaUna clase en Java puede contener variables y métodos. Las variables pueden ser tiposprimitivos como int, char, etc. Los métodos son funciones.

Por ejemplo, en el siguiente trozo de código podemos observarlo:

public MiClase { int i; public MiClase() { i = 10;

Tutorial de Java 43

} public void Suma_a_i( int j ) { i = i + j; } }

La clase MiClase contiene una variable (i) y dos métodos, MiClase que es el constructor dela clase y Suma_a_i( int j ).

Ambito de una variable

Los bloques de sentencias compuestas en Java se delimitan con dos llaves. Las variablesde Java sólo son válidas desde el punto donde están declaradas hasta el final de la senten-cia compuesta que la engloba. Se pueden anidar estas sentencias compuestas, y cada unapuede contener su propio conjunto de declaraciones de variables locales. Sin embargo, nose puede declarar una variable con el mismo nombre que una de ámbito exterior.

El siguiente ejemplo intenta declarar dos variables separadas con el mismo nombre. En C yC++ son distintas, porque están declaradas dentro de ámbitos diferentes. En Java, esto esilegal.

Class Ambito { int i = 1; // ámbito exterior { // crea un nuevo ámbito int i = 2; // error de compilación } }

Métodos y Constructores

Los métodos son funciones que pueden ser llamadas dentro de la clase o por otras clases.El constructor es un tipo específico de método que siempre tiene el mismo nombre que laclase.

Cuando se declara una clase en Java, se pueden declarar uno o más constructores opcio-nales que realizan la inicialización cuando se instancia (se crea una ocurrencia) un objetode dicha clase.

Utilizando el código de ejemplo anterior, cuando se crea una nueva instancia de MiClase, secrean (instancian) todos los métodos y variables, y se llama al constructor de la clase:

MiClase mc; mc = new MiClase();

La palabra clave new se usa para crear una instancia de la clase. Antes de ser instanciadacon new no consume memoria, simplemente es una declaración de tipo. Después de serinstanciado un nuevo objeto mc, el valor de i en el objeto mc será igual a 10. Se puedereferenciar la variable (de instancia) i con el nombre del objeto:

mc.i++; // incrementa la instancia de i de mc

Al tener mc todas las variables y métodos de MiClase, se puede usar la primera sintaxis para

44 Tutorial de Java

llamar al método Suma_a_i() utilizando el nuevo nombre de clase mc :

mc.Suma_a_i( 10 );

y ahora la variable mc.i vale 21.

Finalizadores

Java no utiliza destructores (al contrario que C++) ya que tiene una forma de recogerautomáticamente todos los objetos que se salen del alcance. No obstante proporciona unmétodo que, cuando se especifique en el código de la clase, el reciclador de memoria (garbagecollector) llamará:

// Cierra el canal cuando este objeto es reciclado protected void finalize() { close(); }

Alcance de objetos y reciclado de memoriaLos objetos tienen un tiempo de vida y consumen recursos durante el mismo. Cuando unobjeto no se va a utilizar más, debería liberar el espacio que ocupaba en la memoria deforma que las aplicaciones no la agoten (especialmente las grandes).

En Java, la recolección y liberación de memoria es responsabilidad de un thread llamadoautomatic garbage collector (recolector automático de basura). Este thread monitoriza elalcance de los objetos y marca los objetos que se han salido de alcance. Veamos un ejem-plo:

String s; // no se ha asignado todavia s = new String( «abc» ); // memoria asignada s = «def»; // se ha asignado nueva memoria // (nuevo objeto)

Más adelante veremos en detalle la clase String , pero una breve descripción de lo que haceesto es; crear un objeto String y rellenarlo con los caracteres «abc» y crear otro (nuevo)String y colocarle los caracteres «def».

En esencia se crean dos objetos:

Objeto String «abc» Objeto String «def»

Al final de la tercera sentencia, el primer objeto creado de nombre s que contiene «abc» seha salido de alcance. No hay forma de acceder a él. Ahora se tiene un nuevo objeto llamados y contiene «def». Es marcado y eliminado en la siguiente iteración del thread reciclador dememoria.

Tutorial de Java 45

HerenciaLa Herencia es el mecanismo por el que se crean nuevos objetos definidos en términos deobjetos ya existentes. Por ejemplo, si se tiene la clase Ave, se puede crear la subclase Pato,que es una especialización de Ave.

class Pato extends Ave { int numero_de_patas; }

La palabra clave extends se usa para generar una subclase (especialización) de un objeto.Una Pato es una subclase de Ave. Cualquier cosa que contenga la definición de Ave serácopiada a la clase Pato, además, en Pato se pueden definir sus propios métodos y variablesde instancia. Se dice que Pato deriva o hereda de Ave.

Además, se pueden sustituir los métodos proporcionados por la clase base. Utilizando nuestroanterior ejemplo de MiClase, aquí hay un ejemplo de una clase derivada sustituyendo a lafunción Suma_a_i():

import MiClase; public class MiNuevaClase extends MiClase { public void Suma_a_i( int j ) { i = i + ( j/2 ); } }

Ahora cuando se crea una instancia de MiNuevaClase, el valor de i también se inicializa a10, pero la llamada al método Suma_a_i() produce un resultado diferente:

MiNuevaClase mnc; mnc = new MiNuevaClase(); mnc.Suma_a_i( 10 );

En Java no se puede hacer herencia múltiple. Por ejemplo, de la clase aparato con motor yde la clase animal no se puede derivar nada, sería como obtener el objeto toro mecánico apartir de una máquina motorizada (aparato con motor) y un toro (aminal). En realidad, lo quese pretende es copiar los métodos, es decir, pasar la funcionalidad del toro de verdad al toromecánico, con lo cual no sería necesaria la herencia múltiple sino simplemente la comparticiónde funcionalidad que se encuentra implementada en Java a través de interfaces.

Control de accesoCuando se crea una nueva clase en Java, se puede especificar el nivel de acceso que sequiere para las variables de instancia y los métodos definidos en la clase:

public

public void CualquieraPuedeAcceder(){}

Cualquier clase desde cualquier lugar puede acceder a las variables y métodos deinstacia públicos.

protected

46 Tutorial de Java

protected void SoloSubClases(){}

Sólo las subclases de la clase y nadie más puede acceder a las variables y métodosde instancia protegidos.

privateprivate String NumeroDelCarnetDeIdentidad;

Las variables y métodos de instancia privados sólo pueden ser accedidos desdedentro de la clase. No son accesibles desde las subclases.

friendly (sin declaración específica)

void MetodoDeMiPaquete(){}

Por defecto, si no se especifica el control de acceso, las variables y métodos deinstancia se declaran friendly (amigas), lo que significa que son accesibles por todoslos objetos dentro del mismo paquete, pero no por los externos al paquete. Es lomismo que protected.

Los métodos protegidos (protected) pueden ser vistos por las clases derivadas, como enC++, y también, en Java, por los paquetes (packages). Todas las clases de un paquetepueden ver los métodos protegidos de ese paquete. Para evitarlo, se deben declarar comoprivate protected, lo que hace que ya funcione como en C++ en donde sólo se puede acce-der a las variables y métodos protegidos de las clases derivadas.

Variables y métodos estáticosEn un momento determinado se puede querer crear una clase en la que el valor de unavariable de instancia sea el mismo (y de hecho sea la misma variable) para todos los objetosinstanciados a partir de esa clase. Es decir, que exista una única copia de la variable deinstancia. Se usará para ello la palabra clave static .

class Documento extends Pagina { static int version = 10; }

El valor de la variable version será el mismo para cualquier objeto instanciado de la claseDocumento . Siempre que un objeto instanciado de Documento cambie la variable version,ésta cambiará para todos los objetos.

De la misma forma se puede declarar un método como estático, lo que evita que el métodopueda acceder a las variables de instancia no estáticas:

class Documento extends Pagina { static int version = 10; int numero_de_capitulos; static void annade_un_capitulo() { numero_de_capitulos++; // esto no funciona } static void modifica_version( int i ) { version++; // esto si funciona }

Tutorial de Java 47

}

La modificación de la variable numero_de_capitulos no funciona porque se está violando unade las reglas de acceso al intentar acceder desde un método estático a una variable noestática.

Todas las clases que se derivan, cuando se declaran estáticas, comparten la misma páginade variables; es decir, todos los objetos que se generen comparten la misma zona de memo-ria. Las funciones estáticas se usan para acceder solamente a variables estáticas.

class UnaClase { int var; UnaClase() { var = 5; } UnaFuncion() { var += 5; } }

En el código anterior, si se llama a la función UnaFuncion a través de un puntero a función,no se podría acceder a var, porque al utilizar un puntero a función no se pasa implícitamenteel puntero al propio objeto (this). Sin embargo, sí se podría acceder a var si fuese estática,porque siempre estaría en la misma posición de memoria para todos los objetos que secreasen de UnaClase.

this Y superAl acceder a variables de instancia de una clase, la palabra clave this hace referencia a losmiembros de la propia clase. Volviendo al ejemplo de MiClase, se puede añadir otro cons-tructor de la forma siguiente:

public class MiClase { int i; public MiClase() { i = 10; } // Este constructor establece el valor de i public MiClase( int valor ) { this.i = valor; // i = valor } public void Suma_a_i( int j ) { i = i + j; } }

Aquí this.i se refiere al entero i en la clase MiClase .

Si se necesita llamar al método padre dentro de una clase que ha reemplazado ese método,se puede hacer referencia al método padre con la palabra clave super:

import MiClase;

48 Tutorial de Java

public class MiNuevaClase extends MiClase { public void Suma_a_i( int j ) { i = i + ( j/2 ); super.Suma_a_i( j ); } }

En el siguiente código, el constructor establecerá el valor de i a 10, después lo cambiará a15 y finalmente el método Suma_a_i() de la clase padre (MiClase) lo dejará en 25:

MiNuevaClase mnc; mnc = new MiNuevaClase();

Clases abstractasUna de las características más útiles de cualquier lenguaje orientado a objetos es la posibi-lidad de declarar clases que definen como se utiliza solamente, sin tener que implementarmétodos. Esto es muy útil cuando la implementación es específica para cada usuario, perotodos los usuarios tienen que utilizar los mismos métodos. Un ejemplo de clase abstracta enJava es la clase Graphics :

public abstract class Graphics { public abstract void drawLine( int x1,int y1,int x2, int y2 ); public abstract void drawOval( int x,int y,int width, int height ); public abstract void drawArc( int x,int y,int width, int height,int startAngle,int arcAngle ); . . . }

Los métodos se declaran en la clase Graphics , pero el código que ejecutará el método estáen algún otro sitio:

public class MiClase extends Graphics { public void drawLine( int x1,int y1,int x2,int y2 ) { <código para pintar líneas -específico de la arquitectura-> } }

Cuando una clase contiene un método abstracto tiene que declararse abstracta. No obstan-te, no todos los métodos de una clase abstracta tienen que ser abstractos. Las clases abs-tractas no pueden tener métodos privados (no se podrían implementar) ni tampoco estáti-cos. Una clase abstracta tiene que derivarse obligatoriamente, no se puede hacer un newde una clase abstracta.

Una clase abstracta en Java es lo mismo que en C++ virtual func() = 0; lo que obliga a queal derivar de la clase haya que implementar forzosamente los métodos de esa clase abstrac-ta.

Tutorial de Java 49

InterfacesLos métodos abstractos son útiles cuando se quiere que cada implementación de la claseparezca y funcione igual, pero necesita que se cree una nueva clase para utilizar los méto-dos abstractos.

Los interfaces proporcionan un mecanismo para abstraer los métodos a un nivel superior.

Un interface contiene una colección de métodos que se implementan en otro lugar. Losmétodos de una clase son public, static y final.

La principal diferencia entre interface y abstract es que un interface proporciona un meca-nismo de encapsulación de los protocolos de los métodos sin forzar al usuario a utilizar laherencia.

Por ejemplo:

public interface VideoClip { // comienza la reproduccion del video void play(); // reproduce el clip en un bucle void bucle(); // detiene la reproduccion void stop(); }

Las clases que quieran utilizar el interface VideoClip utilizarán la palabra implements yproporcionarán el código necesario para implementar los métodos que se han definido parael interface:

class MiClase implements VideoClip { void play() { <código> } void bucle() { <código> } void stop() { <código> }

Al utilizar implements para el interface es como si se hiciese una acción de copiar-y-pegar delcódigo del interface, con lo cual no se hereda nada, solamente se pueden usar los métodos.

La ventaja principal del uso de interfaces es que una clase interface puede ser implementadapor cualquier número de clases, permitiendo a cada clase compartir el interfaz de programa-ción sin tener que ser consciente de la implementación que hagan las otras clases queimplementen el interface .

class MiOtraClase implements VideoClip { void play() { <código nuevo> } void bucle() { <código nuevo>

50 Tutorial de Java

} void stop() { <código nuevo> }

Métodos nativosJava proporciona un mecanismo para la llamada a funciones C y C++ desde nuestro códigofuente Java. Para definir métodos como funciones C o C++ se utiliza la palabra clave native.

public class Fecha { int ahora; public Fecha() { ahora = time(); } private native int time(); static { System.loadLibrary( «time» ); } }

Una vez escrito el código Java, se necesitan ejecutar los pasos siguientes para poder inte-grar el código C o C++:

· Utilizar javah para crear un fichero de cabecera (.h)· Utilizar javah para crear un fichero de stubs, es decir, que contiene la declara-ción de las funciones· Escribir el código del método nativo en C o C++, es decir, rellenar el código dela función, completando el trabajo de javah al crear el fichero de stubs· Compilar el fichero de stubs y el fichero .c en una librería de carga dinámica(DLL en Windows ’95 o libXX.so en Unix)· Ejecutar la aplicación con el appletviewer

Más adelante trataremos en profundidad los métodos nativos, porque añaden una gran po-tencia a Java, al permitirle integrar a través de librería dinámica cualquier algoritmo desarro-llado en C o C++, lo cual, entre otras cosas, se utiliza como método de protección contra ladescompilación completa del código Java.

PaquetesLa palabra clave package permite agrupar clases e interfaces. Los nombres de los paquetesson palabras separadas por puntos y se almacenan en directorios que coinciden con esosnombres.

Por ejemplo, los ficheros siguientes, que contienen código fuente Java:

Applet.java, AppletContext.java, AppletStub.java, AudioClip.java

contienen en su código la línea:

package java.applet;

Tutorial de Java 51

Y las clases que se obtienen de la compilación de los ficheros anteriores, se encuentran conel nombre nombre_de_clase.class, en el directorio:

java/applet

Import

Los paquetes de clases se cargan con la palabra clave import, especificando el nombre delpaquete como ruta y nombre de clase (es lo mismo que #include de C/C++). Se puedencargar varias clases utilizando un asterisco.

import java.Date; import java.awt.*;

Si un fichero fuente Java no contiene ningún package, se coloca en el paquete por defectosin nombre. Es decir, en el mismo directorio que el fichero fuente, y la clase puede sercargada con la sentencia import:

import MiClase;

Paquetes de Java

El lenguaje Java proporciona una serie de paquetes que incluyen ventanas, utilidades, unsistema de entrada/salida general, herramientas y comunicaciones. En la versión actual delJDK, los paquetes Java que se incluyen son:

java.applet

Este paquete contiene clases diseñadas para usar con applets. Hay una clase Applety tres interfaces: AppletContext, AppletStub y AudioClip.

java.awt

El paquete Abstract Windowing Toolkit (awt) contiene clases para generar widgets ycomponentes GUI (Interfaz Gráfico de Usuario). Incluye las clases Button, Checkbox,Choice, Component, Graphics, Menu, Panel, TextArea y TextField.

java.io

El paquete de entrada/salida contiene las clases de acceso a ficheros: FileInputStreamy FileOutputStream.

java.lang

Este paquete incluye las clases del lenguaje Java propiamente dicho: Object, Thread,Exception, System, Integer, Float, Math, String, etc.

java.net

Este paquete da soporte a las conexiones del protocolo TCP/IP y, además, incluyelas clases Socket, URL y URLConnection.

52 Tutorial de Java

java.util

Este paquete es una miscelánea de clases útiles para muchas cosas en programa-ción. Se incluyen, entre otras, Date (fecha), Dictionary (diccionario), Random (núme-ros aleatorios) y Stack (pila FIFO).

ReferenciasJava se asemeja mucho a C y C++. Esta similitud, evidentemente intencionada, es la mejorherramienta para los programadores, ya que facilita en gran manera su transición a Java.Desafortunadamente, tantas similitudes hacen que no nos paremos en algunas diferenciasque son vitales. La terminología utilizada en estos lenguajes, a veces es la misma, pero haygrandes diferencias subyacentes en su significado.

C tiene tipos de datos básicos y punteros. C++ modifica un poco este panorama y le añadelos tipos referencia. Java también especifica sus tipos primitivos, elimina cualquier tipo depunteros y tiene tipos referencia mucho más claros.

Todo este maremágnum de terminología provoca cierta consternación, así que vamos aintentar aclarar lo que realmente significa.

Conocemos ya ampliamente todos los tipos básicos de datos: datos base, integrados, primi-tivos e internos; que son muy semejantes en C, C++ y Java; aunque Java simplifica un pocosu uso a los desarrolladores haciendo que el chequeo de tipos sea bastante más rígido.Además, Java añade los tipos boolean y hace imprescindible el uso de este tipo booleanoen sentencias condicionales.

Punteros y referencias C++

Punteros

C y C++ permiten la declaración y uso de punteros, que pueden ser utilizados en cualquierlugar. Esta tremenda flexibilidad resulta muy útil, pero también es la causa de que podamoscolgar todo el sistema.

La intención principal en el uso de los punteros es comunicarnos más directamente con elhardware, haciendo que el código se acelere. Desafortunadamente, este modelo de tan bajonivel hace que perdamos robustez y seguridad en la programación y hace muy difícilestareas como la liberación automática de memoria, la defragmentación de memoria, o reali-zar programación distribuida de forma clara y eficiente.

Tutorial de Java 53

Referencias en C++

Las referencias se incorporaron a C++ en un intento de manejar punteros de C de forma máslimpia y segura. Sin embargo, como no elimina los punteros, la verdad es que su propósitolo consigue a medias. Es más, podríamos decir que con las referencias C++, el lenguaje sevuelve más complicado y no es más poderoso que antes.

Las referencias deben ser inicializadas cuando se declaran y no se pueden alterar poste-riormente. Esto permite incrementar la eficiencia en tiempo de ejecución sobre la soluciónbasada en punteros, pero es más por las deficiencias de los punteros que por las ventajasde las referencias.

Referencias en JavaLas referencias en Java no son punteros ni referencias como en C++. Este hecho crea unpoco de confusión entre los programadores que llegan por primera vez a Java. Las refe-

rencias en Java son identificadores de instancias de las clases Java. Una referenciadirige la atención a un objeto de un tipo específico. No tenemos por qué saber cómo lo

hace ni necesitamos saber qué hace ni, por supuesto, su implementación.

Pensemos en una referencia como si se tratase de la llave electrónica de la habitación deun hotel. Vamos a utilizar precisamente este ejemplo del Hotel para demostrar el uso y lautilización que podemos hacer de las referencias en Java. Primero crearemos la claseHabitacion , implementada en el fichero Habitacion.java, mediante instancias de la cualconstruiremos nuestro Hotel:

public class Habitacion { private int numHabitacion; private int numCamas;

public Habitacion() { habitacion( 0 ); }

public Habitacion( int numeroHab ) { habitacion( numeroHab ); }

public Habitacion( int numeroHab,int camas ) { habitacion( numeroHab ); camas( camas ); }

public synchornized int habitacion() { return( numHabitacion ); }

public synchronized void habitacion( int numeroHab ) { numHabitacion = numeroHab; }

public synchronized int camas() { return( camas );

54 Tutorial de Java

}

public syncronized void camas( int camas ) { numCamas = camas; } }

El código anterior sería el corazón de la aplicación. Vamos pues a construir nuestro Hotelcreando Habitaciones y asignándole a cada una de ellas su llave electrónica; tal comomuestra el código siguiente, Hotel1.java:

public class Hotel1 { public static void main( String args[] ) { Habitacion llaveHab1; // paso 1 Habitacion llaveHab2;

llaveHab1 = new Habitacion( 222 ); // pasos 2, 3, 4 y 5 llaveHab2 = new Habitacion( 1144,3 );// ^^^^^^^^^ ^^^^^^^^^^^^^^ ^^^^^^// A B y D C } }

Para explicar el proceso, dividimos las acciones en los cinco pasos necesarios para poderentrar en nuestra habitación. Aunque no se incluye, podemos también considerar el casode que necesitemos un cerrajero, para que cuando perdamos la llave, nos abra la puerta;y que en nuestro caso sería el garbage collector, que recicla la habitación una vez que sehayan perdido todas las llaves.

El primer paso es la creación de la llave, es decir, definir la variable referencia, por defec-to nula.

El resto de los pasos se agrupan en una sola sentencia Java. La parte B en el códigoanterior indica al gerente del Hotel que ya dispone de una nueva habitación. La parte Cllama al decorador de interiores para que «vista» la habitación según un patrón determi-nado, para que no desentonen unas habitaciones con otras y no se pierdan las señas deidentidad del hotel. El código electrónico que nos permitirá acceder a la habitación segenera en la parte D, una vez conocido el interior de la habitación y se programa en lallave en la parte A.

Si dejamos el ejemplo real a un lado y nos vamos a lo que ocurre en la ejecución delcódigo, vemos que el operador new busca espacio para una instancia de un objeto deuna clase determinada e inicializa la memoria a los valores adecuados. Luego invoca almétodo constructor de la clase, proporcionándole los argumentos adecuados. El operadornew devuelve una referencia a sí mismo, que es inmediatamente asignada a la variablereferencia.

Podemos tener múltiples llaves para una misma habitación:

. . .Habitacion llaveHab3,llaveHab4;llaveHab3 = llaveHab1;llaveHab4 = llavehab2;

De este modo conseguimos copias de las llaves. Las habitaciones en sí mismas no se

Tutorial de Java 55

han tocado en este proceso. Así que, ya tenemos dos llaves para la habitación 222 y otrasdos para la habitación 1144.

Una llave puede ser programada para que funcione solamente con una habitación encualquier momento, pero podemos cambiar su código electrónico para que funcione conalguna otra habitación; por ejemplo, para cambiar una habitación anteriormente utilizadapor un empedernido fumador por otra limpia de olores y con vistas al mar. Cambiemospues la llave duplicada de la habitación del fumador (la 222) por la habitación con olor asal marina, 1144:

. . .llaveHab3 = llaveHab2;

Ahora tenemos una llave para la habitación 222 y tres para la habitación 1144. Manten-dremos una llave para cada habitación en la conserjería, para poder utilizarla como llavemaestra, en el caso de que alguien pierda su llave propia.

Alguien con la llave de una habitación puede hacer cambios en ella, y los compañerosque tengan llave de esa misma habitación, no tendrán conocimiento de esos cambioshasta que vuelvan a entrar en la habitación. Por ejemplo, vamos a quitar una de las ca-mas de la habitación, entrando en ella con la llave maestra:

. . .llaveHab2.camas( 2 );

Ahora cuando los inquilinos entren en la habitación podrán comprobar el cambio realiza-do:

. . .llaveHab4.printData();

Referencias y arraysComo en C y C++, Java dispone de arrays de tipos primitivos o de clases. Los arrays en C yC++ son básicamente un acompañante para los punteros. En Java, sin embargo, son ciuda-danos de primera clase.

Vamos a expandir nuestro hotel creando todo un ala de habitaciones, Hotel2.java. Creare-mos un juego de llaves maestras y luego construiremos las habitaciones:

public class Hotel2 { // Número de habitaciones por ala public static final int habPorAla = 12;

public static void main( String args[] ) { Habitacion llavesMaestras[]; // paso 1 llavesMaestras = new Habitacion[ habPorAla ]; // pasos 2-5// ̂ ^^^^^^^^^^^^^ ̂ ^^^^^^^^^^^^^^^^^^^^^^^^^^// A B, C, D y E int numPiso = 1; for( int i=0; i < habPorAla; i++ ) // pasos 6-9 llavesMaestras[ i ] = new Habitacion( numPiso * 100 + i, ( 0 == (i%2)) ? 2 : 1 ); for( int i=0; i < habPorAla; i++ ) // pasos 10-11 llavesMaestras[i].printData();

56 Tutorial de Java

} }

Cada paso en el ejemplo es semejante al que ya vimos antes. El paso 1 especifica que eljuego de llaves maestras es un grupo de llaves de habitaciones.

Los pasos 2 a 5 son, en este caso, la parte principal. En lugar de crear una habitación, elgerente ordena construir un grupo contiguo de habitaciones. El número de llaves se especi-fica entre corchetes y todas se crean en blanco.

Los pasos 6 a 9 son idénticos a los pasos 2 a 5 del ejemplo anterior, excepto en que en estecaso todas las llaves pasan a formar parte del juego maestro. Los números de piso se danen miles para que cuando se creen las habitaciones, todas tengan el mismo formato. Tam-bién todas las habitaciones de número par tienen una sola cama, mientras que las habita-ciones impares tendrán dos camas.

Los pasos 10 y 11 nos permiten obtener información de cada una de las habitaciones.

Referencias y listasHay gente que piensa que como Java no dispone de punteros, resulta demasiado complejoconstruir listas enlazadas, árboles binarios y grafos. Vamos a demostrar que quien así pien-se está bastante equivocado.

Retomemos el ejemplo de los arrays, y en vez de éstos vamos a usar una lista doblementeenlazada. El paquete de la lista simple se compone de dos clases. Cada elemento de la listaes un NodoListaEnlazada, NodoListaEnlazada.java:

public class NodoListaEnlazada { private NodoListaEnlazada siguiente; private NodoListaEnlazada anterior; private Object datos; // . . . }

Cada NodoListaEnlazada contiene una referencia a su nodo precedente en la lista y una refe-rencia al nodo que le sigue. También contiene una referencia genérica a cualquier clase quese use para proporcionar acceso a los datos que el usuario proporcione.

La lista enlazada, ListaEnlazada.java, contiene un nodo principio-fin y un contador para elnúmero de nodos en la lista:

public class ListaEnlazada { public NodoListaEnlazada PrincipioFin; private int numNodos; // . . . }

El nodo especial PrincipioFin es sencillo, para simplificar el código. El contador se usa paraoptimizar los casos más habituales.

Revisemos pues el código de nuestro Hotel, ahora Hotel3.java, que será prácticamente el

Tutorial de Java 57

mismo que en el caso de los arrays:

public class Hotel3 { // Número de habitaciones por ala public static final int habPorAla = 12;

public static void main( String args[] ) { ListaEnlazada llaveMaestra; // paso 1 llaveMaestra = new ListaEnlazada(); // pasos 2-5

int numPiso = 1; for( int i=0; i < habPorAla; i++ ) // pasos 6-9 llaveMaestra.insertAt( i, new Habitacion( numPiso * 100 + i, ( 0 == (i%2)) ? 2 : 1 ); for( int i=0; i < habPorAla; i++ ) // pasos 10-12 ( (Habitacion)llaveMaestra.getAt(i) ).printData(); } }

El paso 1 es la llave maestra de la lista. Está representada por una lista genérica; es decir,una lista de llaves que cumple la convención que nosotros hemos establecido. Podríamosacelerar el tiempo de compilación metiendo la lista genérica ListaEnlazada dentro de unaListaEnlazadaHabitacion.

Los pasos 2 a 5 son equivalentes a los del primer ejemplo. Construimos e inicializamos unanueva ListaEnlazada, que usaremos como juego de llaves maestras.

Los pasos 6 a 9 son funcionalmente idénticos a los del ejemplo anterior con arrays, pero condiferente sintaxis. En Java, los arrays y el operador [] son internos del lenguaje. Como Javano soporta la sobrecarga de operadores por parte del usuario, tenemos que usarlo siempreen su forma normal.

La ListaEnlazada proporciona el método insertAt() que coge el índice en la lista, donde elnuevo nodo ha de ser insertado, como primer argumento. El segundo argumento es el objetoque será almacenado en la lista. Obsérvese que no es necesario colocar moldeo algunopara hacer algo a una clase descendiente que depende de uno de sus padres.

Los pasos 10 a 12 provocan la misma salida que los pasos 10 y 11 del ejemplo con arrays.El paso 10 coge la llave del juego que se indica en el método getAt(). En este momento, elsistema no sabe qué datos contiene la llave, porque el contenido de la habitación es gené-rico. Pero nosotros sí sabemos lo que hay en la lista, así que informamos al sistema hacien-do un moldeado a la llave de la habitación (este casting generará un chequeo en tiempo deejecución por el compilador, para asegurarse de que se trata de una Habitacion). El paso 12usa la llave para imprimir la información.

Punteros C/C++ y referencias JavaAhora que ya sabemos un poco más sobre las referencias en Java, vamos a compararlascon los punteros de C y C++.

58 Tutorial de Java

Los punteros en C y C++ están orientados hacia un modelo físico de funcionamiento. Esdecir, que el modelo de punteros se mapea directamente sobre el modelo hardware. Estemodelo asume cosas como el no movimiento, lo que hace que mecanismos como la libera-ción automática resulten mucho menos eficientes o simplemente imposibles. Cosas como ladistribución en redes y la persistencia de objetos son muy difíciles de conseguir en C y C++.Aunque no hay implementaciones en Java, por ahora, para la persistencia y la distribución,la característica opaca de las referencias en Java hace que el soporte para estas prestacio-nes sea mucho más simple.

C y C++ permiten el uso de punteros de tal forma que podemos corromper el sistema, cosaque no puede suceder con las referencias en Java. Cualquier intento de hacer esto seríaabortado por el compilador o por el sistema en ejecución (lanzando una excepción). C y C++dejan la protección de memoria al sistema operativo, que solamente tiene el recurso degenerar un error del sistema cuando un puntero accede a una posición no válida. Por elcontrario, con el uso de las referencias, Java nos protege contra nuestras propias tenden-cias autodestructivas.

Tutorial de Java 59

Capítulo 4Programas Básicos en Java

Como cualquier otro lenguaje, Java se usa para crear aplicaciones. Pero, también Javatiene la particularidad especial de poder crear aplicaciones muy especiales, son los applets,que es una mini (let ) aplicación (app ) diseñada para ejecutarse en un navegador. Vamos aver en detalle lo mínimo que podemos hacer en ambos casos.

Una mínima aplicación en JavaLa aplicación más pequeña posible es la que simplemente imprimir un mensaje en la panta-lla. Tradicionalmente, el mensaje suele ser «Hola Mundo!». Esto es justamente lo que haceel siguiente fragmento de código:

// // Aplicación HolaMundo de ejemplo // class HolaMundoApp { public static void main( String args[] ) { System.out.println( «Hola Mundo!» ) ; } }

HolaMundo

Vamos ver en detalle la aplicación anterior, línea a línea. Esas líneas de código contienenlos componenetes mínimos para imprimir Hola Mundo! en la pantalla.

// // Aplicación HolaMundo de ejemplo //

Estas tres primera líneas son comentarios. Hay tres tipos de comentarios en Java, // es uncomentario orientado a línea.

class HolaMundoApp {

Esta línea declara la clase HolaMundoApp. El nombre de la clase especificado en el ficherofuente se utiliza para crear un fichero nombredeclase.class en el directorio en el que secompila la aplicación. En nuestro caso, el compilador creará un fichero llamadoHolaMundoApp.class.

public static void main( String args[] ) {

Esta línea especifica un método que el intérprete Java busca para ejecutar en primer lugar.Igual que en otros lenguajes, Java utiliza una palabra clave main para especificar la primera

60 Tutorial de Java

función a ejecutar. En este ejemplo tan simple no se pasan argumentos.

public significa que el método main puede ser llamado por cualquiera, incluyendo el intér-prete Java.

static es una palabra clave que le dice al compilador que main se refiere a la propia claseHolaMundoApp y no a ninguna instancia de la clase. De esta forma, si alguien intentahacer otra instancia de la clase, el método main no se instanciaría.

void indica que main no devuelve nada. Esto es importante ya que Java realiza una estrictacomprobación de tipos, incluyendo los tipos que se ha declarado que devuelven los méto-dos.

args[] es la declaración de un array de Strings. Estos son los argumentos escritos tras elnombre de la clase en la línea de comandos:

%java HolaMundoApp arg1 arg2 ... System.out.println( «Hola Mundo!» );

Esta es la funcionalidad de la aplicación. Esta línea muestra el uso de un nombre de clase ymétodo. Se usa el método println() de la clase out que está en el paquete System.

El método println() toma una cadena como argumento y la escribe en el stream de salidaestándar; en este caso, la ventana donde se lanza la aplicación.

} }

Finalmente, se cierran las llaves que limitan el método main() y la clase HolaMundoApp .

Compilación y ejecución de HolaMundoVamos a ver a continuación como podemos ver el resultado de nuestra primera aplicaciónJava en pantalla. Generaremos un fichero con el código fuente de la aplicación, lo compila-remos y utilizaremos el intérprete java para ejecutarlo.

Ficheros Fuente Java

Los ficheros fuente en Java terminan con la extensión «.java». Crear un fichero utilizandocualquier editor de texto ascii que tenga como contenido el código de las ocho líneas denuestra mínima aplicación, y salvarlo en un fichero con el nombre de HolaMundoApp.java.Para crear los ficheros con código fuente Java no es necesario un procesador de textos,aunque puede utilizarse siempre que tenga salida a fichero de texto plano o ascii, sino quees suficiente con cualquier otro editor.

Tutorial de Java 61

Compilación

El compilador javac se encuentra en el directorio bin por debajo del directorio java, donde sehaya instalado el JDK. Este directorio bin, si se han seguido las instrucciones de instalación,debería formar parte de la variable de entorno PATH del sistema. Si no es así, tendría querevisar la Instalación del JDK. El compilador de Java traslada el código fuente Java a byte-codes, que son los componentes que entiende la Máquina Virtual Java que está incluida enlos navegadores con soporte Java y en appletviewer.

Una vez creado el fichero fuente HolaMundoApp.java, se puede compilar con la línea si-guiente:

%javac HolaMundoApp.java

Si no se han cometido errores al teclear ni se han tenido problemas con el path al ficherofuente ni al compilador, no debería aparecer mensaje alguno en la pantalla, y cuando vuelvaa aparecer el prompt del sistema, se debería ver un fichero HolaMundoApp.class nuevo enel directorio donde se encuentra el fichero fuente.

Si ha habido algún problema, en Problemas de compilación al final de esta sección, hemosintentado reproducir los que más frecuentemente se suelen dar, se pueden consultar por sipueden aportar un poco de luz al error que haya aparecido.

Ejecución

Para ejecutar la aplicación HolaMundoApp, hemos de recurrir al intérprete java , que tam-bién se encuentra en el directorio bin, bajo el directorio java. Se ejecutará la aplicación conla línea:

%java HolaMundoApp

y debería aparecer en pantalla la respuesta de Java:

%Hola Mundo!

El símbolo % representa al prompt del sistema, y lo utilizaremos para presentar las respues-tas que nos ofrezca el sistema como resultado de la ejecución de los comandos que seindiquen en pantalla o para indicar las líneas de comandos a introducir.

Problemas de compilación

A continuación presentamos una lista de los errores más frecuentes que se presentan a lahora de compilar un fichero con código fuente Java, nos basaremos en errores provocadossobre nuestra mínima aplicación Java de la sección anterior, pero podría generalizarse sindemasiados problemas.

%javac: Command not found

No se ha establecido correctamente la variable PATH del sistema para el compiladorjavac. El compilador javac se encuentra en el directorio bin, que cuelga del directoriojava, que cuelga del directorio donde se haya instalado el JDK (Java Development

62 Tutorial de Java

Kit).

%HolaMundoApp.java:3: Method printl(java.lang.String) not found in class java.io.PrintStream. System.out.printl( «HolaMundo!); ^

Error tipográfico, el método es println no printl.

%In class HolaMundoApp: main must be public and static

Error de ejecución, se olvidó colocar la palabra static en la declaración del métodomain de la aplicación.

%Can´t find class HolaMundoApp

Este es un error muy sutil. Generalmente significa que el nombre de la clase esdistinto al del fichero que contiene el código fuente, con lo cual el ficheronombre_fichero.class que se genera es diferente del que cabría esperar. Por ejem-plo, si en nuestro fichero de código fuente de nuestra aplicación HolaMundoApp.javacolocamos en vez de la declaración actual de la clase HolaMundoApp , la línea:

class HolaMundoapp {

se creará un fichero HolaMundoapp.class, que es diferente del HolaMundoApp.class,que es el nombre esperado de la clase; la diferencia se encuentra en la a minúsculay mayúscula.

El visor de Applets de Sun (appletviewer)El visualizador de applets (appletviewer) es una aplicación que permite ver en funciona-miento applets, sin necesidad de la utilización de un navegador World-Wide-Web comoHotJava, Microsoft Explorer o Nescape. En adelante, recurriremos muchas veces a él, yaque el objetivo del tutorial es el lenguaje Java.

Applet

La definición más extendida de applet , muy bien resumida por Patrick Naughton, indica queun applet es «una pequeña aplicación accesible en un servidor Internet, que se transportapor la red, se instala automáticamente y se ejecuta in situ como parte de un documentoweb». Claro que así la definición establece el entorno (Internet, Web, etc.). En realidad, unapplet es una aplicación pretendidamente corta (nada impide que ocupe más de un gigabyte,a no ser el pensamiento de que se va a transportar por la red y una mente sensata) basadaen un formato gráfico sin representación independiente: es decir, se trata de un elemento aembeber en otras aplicaciones; es un componente en su sentido estricto.

Un ejemplo en otro ámbito de cosas podría ser el siguiente: Imaginemos una empresa, quecansada de empezar siempre a codificar desde cero, diseña un formulario con los datosbásicos de una persona (nombre, dirección, etc.). Tal formulario no es un diálogo por símismo, pero se podría integrar en diálogos de clientes, proveedores, empleados, etc. Elhecho de que se integre estática (embebido en un ejecutable) o dinámicamente (intérpretes,

Tutorial de Java 63

DLLs, etc.) no afecta en absoluto a la esencia de su comportamiento como componente conque construir diálogos con sentido autónomo.

Pues bien, así es un applet. Lo que ocurre es que, dado que no existe una base adecuadapara soportar aplicaciones industriales Java en las que insertar nuestras miniaplicaciones(aunque todo se andará), los applets se han construido mayoritariamente, y con gran aciertocomercial (parece), como pequeñas aplicaciones interactivas, con movimiento, luces y soni-do... en Internet.

Llamadas a Applets con appletviewer

Un applet es una mínima aplicación Java diseñada para ejecutarse en un navegador Web.Por tanto, no necesita preocuparse por un método main() ni en dónde se realizan las llama-das. El applet asume que el código se está ejecutando desde dentro de un navegador. Elappletviewer se asemeja al mínimo navegador. Espera como argumento el nombre del fiche-ro html que debe cargar, no se le puede pasar directamente un programa Java. Este ficherohtml debe contener una marca que especifica el código que cargará el appletviewer:

<HTML> <APPLET CODE=HolaMundo.class WIDTH=300 HEIGHT=100> </APPLET> </HTML>

El appletviewer crear un espacio de navegación, incluyendo un área gráfica, donde se eje-cutará el applet, entonces llamará a la clase applet apropiada. En el ejemplo anterior, elappletviewer cargará una clase de nombre HolaMundo y le permitirá trabajar en su espaciográfico.

Arquitectura de AppletviewerEl appletviewer representa el mínimo interfaz de navegación. En la figura se muestran lospasos que seguiría appletviewer para presentarnos el resultado de la ejecución del códigode nuestra clase.

64 Tutorial de Java

Esta es una visión simplificada del appletviewer. La función principal de esta aplicación esproporcionar al usuario un objeto de tipo Graphics sobre el que dibujar, y varias funcionespara facilitar el uso del objeto Graphics.

Ciclo de vida de un Applet

Cuando un applet se carga en el appletviewer, comienza su ciclo de vida, que pasaría porlas siguientes fases:

· Se crea una instancia de la clase que controla el applet. En el ejemplo de lafigura anterior, sería la clase HolaMundo .· El applet se incializa.· El applet comienza a ejecutarse.· El applet empieza a recibir llamadas. Primero recibe una llamada init (inicializar),seguida de un mensaje start (empezar) y paint (pintar). Estas llamadas pueden serrecibidas asíncronamente.

Métodos de AppletviewerVamos a utilizar como excusa la función asociada al appletviewer de los siguientes métodospara adentrarnos en su presentación, aunque a lo largo de secciones posteriores, volvere-mos a referirnos a ellos, porque también son los métodos propios de la clase Applet .

init()

El método init() se llama cada vez que el appletviewer carga por primera vez la clase.Si el applet llamado no lo sobrecarga, init() no hace nada. Fundamentalmente en estemétodo se debe fijar el tamaño del applet, aunque en el caso de Netscape el tamañoque vale es el que se indique en la línea del fichero html que cargue el applet. Tam-bién se deben realizar en este método las cargas de imágenes y sonidos necesariospara la ejecución del applet. Y, por supuesto, la asignación de valores a las variablesglobales a la clase que se utilicen. En el caso de los applet, este método únicamentees llamado por el sistema al cargar el applet.

start()

start() es la llamada para arrancar el applet cada vez que es visitado. La clase Appletno hace nada en este método. Las clases derivadas deben sobrecargarlo para co-menzar la animación, el sonido, etc. Esta función es llamada automáticamente cadavez que la zona de visualización en que está ubicado el applet se expone a la visión,a fin de optimizar en uso de los recursos del sistema y no ejecutar algo que no puedeser apreciado (aunque el programador puede variar este comportamiento y hacerque un applet siga activo aun cuando esté fuera del área de visión). Esto es, imagine-mos que cargamos un applet en un navegador minimizado; el sistema llamará almétodo init(), pero no a start(), que sí será llamado cuando restauremos el navegadora un tamaño que permita ver el applet. Naturalmente, start() se puede ejecutar varias

Tutorial de Java 65

veces: la primera tras init() y las siguientes (porque init() se ejecuta solamente unavez) tras haber aplicado el método stop().

stop()

stop() es la llamada para detener la ejecución del applet. Se llama cuando el appletdesaparece de la pantalla. La clase Applet tampoco hace nada en este método, quedebería ser sobrecargado por las clases derivadas para detener la animación, elsonido, etc. Esta función es llamada cuando el navegador no incluye en su campo devisión al applet; por ejemplo, cuando abandona la página en que está insertado, deforma que el programador puede paralizar los threads que no resulten necesariosrespecto de un applet no visible, y luego recuperar su actividad mediante el métodostart().

destroy()

El método destroy() se llama cuando ya no se va a utilizar más el applet, cuando senecesita que sean liberados todos los recursos dispuestos por el applet, por ejemplo,cuando se cierra el navegador. La clase Applet no hace nada en este método. Lasclases derivadas deberían sobrecargarlo para hacer una limpieza final. Los appletmultithread deberían utilizar destroy() para detener los threads que quedasen acti-vos.

El appletviewer también contiene la clase Component (componente), que usa dosmétodos para ayudas al applet a escribir en el espacio gráfico que el appletviewer leproporciona para su ejecución.

paint( Graphics g )

Es la función llamada cada vez que el área de dibujo del applet necesita ser refresca-da. La clase Applet simplemente dibuja un rectángulo gris en el área, es la clasederivada, obviamente, la que debería sobrecargar este método para representar algointeligente en la pantalla. Cada vez que la zona del applet es cubierta por otra venta-na, se desplaza el applet fuera de la visión o el applet cambia de posición debido a unredimensionamiento del navegador, el sistema llama automáticamente a este méto-do, pasando como argumento un objeto de tipo Graphics que delimita la zona a serpintada; en realidad se pasa una referencia al contexto gráfico en uso, y que repre-senta la ventana del applet en la página web.

update( Graphics g )

Esta es la función que realmente se llama cuando se necesita una actualización de lapantalla. La clase Applet simplemente limpia el área y llama al método paint(). Estafuncionalidad es suficiente para la mayoría de los casos; aunque, de cualquier forma,las clases derivadas pueden sustituir esta funcionalidad para sus propósitos espe-ciales. Es decir, en las situaciones detalladas anteriormente que dañan la zona deexposición del applet, el sistema llama al método paint(), pero en realidad la llamadase realiza al método update(), cuyo comportamiento establecido en la claseComponent es llamar al método paint(), tras haber rellenado la zona del applet con

66 Tutorial de Java

su color de fondo por defecto. Pudiera parecer así que se trata de un método deefecto neutro, pero si la función paint() cambiara el color del fondo, podríamos perci-bir un flick de cambio de colores nada agradable. Por tanto, habrá que cuidarse por locomún, de eliminar este efecto de limpia primero, sobrecargando el método update(),para que llame únicamente a paint(). Otra solución sería insertar el código de pintadoen una sobrecarga del método update() y escribir un método paint() que sólo llame aupdate(). La última solución pasaría por usar el mismo método setBackground( Color ),en el método init() para así evitar el efecto visual sin tener que sobrecargar el métodoupdate(). Estas son las mismas razones que aconsejan usar el método resize() inser-to en init(), para evitar el mismo desagradable efecto.

repaint

Llamando a este método se podrá forzar la actualización de un applet, la llamada aupdate(). Pero hay que tener cierto cuidado, porque AWT posee cierta inteligencia(combinación casi siempre nefasta), de forma que si se llama a update() medianterepaint() con una frecuencia muy corta, AWT ignorará las llamadas a update() queestime oportuno, pues considera a esta función como un bien escaso.

Sinopsis

La llamada a appletviewer es de la forma:

appletviewer [-debug] urls...

El appletviewer toma como parámetro de ejecución, o bien el nombre del un fichero htmlconteniendo el tag (marca) <APPLET>, o bien un URL hacia un fichero HTML que contengaesa marca.

Si el fichero html no contiene un tag <APPLET> válido, el appletviewer no hará nada. Elappletviewer no muestra otras marcas html.

La única opción válida que admite la llamada a appletviewer es -debug, que arranca el appleten el depurador de Java, jdb. Para poder ver el código fuente en el depurador, se tiene quecompilar el fichero .java con la opción -g.

Ejemplo de uso

En el ejemplo de llamada al appletviewer siguiente, hacemos que se ejecute el applet básicoque crearemos en la sección siguiente y que lanzaremos desde un fichero html del mismonombre que nuestro fichero de código fuente Java.

%appletviewer HolaMundo.html

Esta llamada lanzaría la ejecución de HolaMundo.class en el appletviewer, abriéndose enpantalla la ventana siguiente:

Tutorial de Java 67

Funciones de menú de AppletviewerEl appletviewer tiene un único menú mostrado en la imagen siguiente y que vamos a expli-car en cada una de sus opciones, ya que lo usaremos a menudo cuando vayamos avanzan-do en nuestros conocimientos de Java.

· Restart

La función Restart llama al método stop() y seguidamente llama de nuevo a start(),que es el método que ha lanzado inicialmente la ejecución del applet. Se puedeutilizar Restart para simular el movimiento entre páginas en un documento html.

· Reload

La función Reload llama al método stop() y luego al método destroy() en el appletactual. A continuación carga una nueva copia del applet y la arranca llamando almétodo start().

· Clone

La función Clone crea una copia del applet actual en una ventana de appletviewernueva. En realidad es un appletviewer idéntico con el mismo URL.

· Tag

68 Tutorial de Java

La función Tag muestra en una ventana hija del appletviewer el código html cargadopara su ejecución. Es similar a la función View Source que figura en la mayoría de losnavegadores, Netscape, Internet Explorer y HotJava incluidos.

· Info

La función Info lee los comentarios de documentación contenidos en el fichero html ymuestra la información de los parámetros (si la hay).

· Properties

El appletviewer tiene las funciones básicas de presentación de un navegador y lafunción Properties (propiedades de la red) permite cambiar o establecer el modo deseguridad o fijar los servidores de proxy o firewall.

· Close

La función Close llama al método destroy() de la ventana actual del appletviewer,teminando su ejecución.

· Quit

La función Quit llama al método destroy() de cada una de las copias del appletviewerque se encuentren lanzadas, concluyendo la ejecución de todas ellas y terminandoentonces el appletviewer.

Un applet básico en JavaVamos a comenzar la creación del código fuente del un applet que satisfaga nuestras nece-sidades. Recordamos que Java utiliza la extensión .java para designar los ficheros fuente.

HolaMundo

A continuación está el código fuente del applet HolaMundo, que es la versión applet de lamínima aplicación Java que antes habíamos escrito. Guardar este código en un ficherofuente Java como HolaMundo.java.

// // Applet HolaMundo de ejemplo // import java.awt.Graphics; import java.applet.Applet;

public class HolaMundo extends Applet { public void paint( Graphics g ) { g.drawString( «Hola Mundo!»,25,25 ) ; } }

Tutorial de Java 69

Componentes básicos de un Applet

El lenguaje Java implementa un modelo de Programación Orientada a Objetos. Los objetossirven de bloques centrales de construcción de los programas Java. De la misma forma queotros lenguajes de programación, Java tiene variables de estado y métodos.

Veamos como se descompone un applet en sus piezas/objetos:

/* Sección de importaciones */

public class NombreDelNuevoApplet extends Applet { /* Aquí se declaran las variables de estado (public y private) */

/* Los métodos para la interacción con los objetos se declaran y definen aquí */ public void MetodoUno( parámetros ) { /* Aquí viene para cada método, el código Java que desempeña la tarea. Qué código se use depende del applet */ } }

Para HolaMundo, se importan las dos clases que necesita. No hay variables de estado, ysólo se tiene que definir un método para que el applet tenga el comportamiento esperado.

Clases incluidas

El comando import carga otras clases dentro de nuestro código fuente. El importar una clasedesde un paquete de Java hace que esa clase importada esté disponible para todo el códigoincluido en el fichero fuente Java que la importa. Por ejemplo, en el applet HolaMundo seimporta la clase java.awt.Graphics, y podremos llamar a los métodos de esta clase desdecualquier método de nuestro programa que se encuentre en el fichero HolaMundo.java. Estaclase define una área gráfica y métodos para poder dibujar dentro de ella. La función paint()declara a g como un objeto de tipo Graphics; luego, paint() usa el método drawString() de laclase Graphics para generar su salida.

La clase Applet

Se puede crear una nueva clase, en este caso HolaMundo , extendiendo la clase básica deJava: Applet . De esta forma, se hereda todo lo necesario para crear un applet. Modificandodeterminados métodos del applet, podemos lograr que lleve a cabo las funciones que de-seamos.

import java.applet.Applet; . . . public class HolaMundo extends Applet {

70 Tutorial de Java

Métodos de Applet

La parte del applet a modificar es el método paint(). En la clase Applet , se llama al métodopaint() cada vez que el método arranca o necesita ser refrescado, pero no hace nada. Ennuestro caso, lo que hacemos es:

public void paint( Graphics g ) { g.drawString( «Hola Mundo!»,25,25 ); }

De acuerdo a las normas de sobrecarga, se ejecutará este último paint() y no el paint() vacíode la clase Applet . Luego, aquí se ejecuta el método drawString(), que le dice al appletcómo debe aparecer un texto en el área de dibujo.

Otros métodos básicos para dibujar son:

drawLine( int x1,int y1,int x2,int y2 ) drawRect( int x,int y,int ancho,int alto ) drawOval( int x,int y,int ancho,int alto )

Tanto para drawRect() como para drawOval(), las coordenadas (x,y) son la esquina superiorizquierda del rectángulo (para drawOval, el óvalo es encajado en el rectángulo que locircunscribe).

Compilación de un appletAhora que tenemos el código de nuestro applet básico y el fichero fuente Java que lo contie-ne, necesitamos compilarlo y obtener un fichero .class ejecutable. Se utiliza el compiladorJava, javac, para realizar la tarea. El comando de compilación será:

%javac HolaMundo.java

Eso es todo. El compilador javac generará un fichero HolaMundo.class que podrá ser llama-do desde cualquier navegador con soporte Java y, por tanto, capaz de ejecutar appletsJava.

Llamada a Applets

¿Qué tienen de especial HotJava, Microsoft Explorer o Netscape con respecto a otrosnavegadores? Con ellos se puede ver html básico y acceder a todo el texto, gráfico, sonidoe hipertexto que se pueda ver con cualquier otro navegador. Pero además, pueden ejecutarapplets, que no es html estándar. Ambos navegadores entienden código html que lleve lamarca <APPLET>:

<APPLET CODE=»SuCodigo.class» WIDTH=100 HEIGHT=50> </APPLET>

Esta marca html llama al applet SuCodigo.class y establece su ancho y alto inicial. Cuandose acceda a la página Web donde se encuentre incluida la marca, se ejecutará el byte-codecontenido en SuCodigo.class, obteniéndose el resultado de la ejecución del applet en laventana del navegador, con soporte Java, que estemos utilizando.

Tutorial de Java 71

Prueba de un Applet

71El JDK, Kit de Desarrollo de Java, incluye el visor de applets básico, appletviewer, quepuede utilizarse para la visualización rápida y prueba de nuestros applets, tal como se havisto ya. La ejecución de un applet sobre appletviewer se realiza a través de la llamada:

%appletviewer fichero.html

En nuestro caso el fichero con el código html que ejecutará nuestro applet HolaMundo esHolaMundo.html que generará la salida que se mostraba en la sección sobre el Ejemplo deuso de appletviewer.

La marca APPLET de HTMLDado que los applets están mayormente destinados a ejecutarse en navegadores Web,había que preparar el lenguaje HTML para soportar Java, o mejor, los applets. El esquemade marcas de HTML, y la evolución del estándar marcado por Netscape hicieron fácil laadición de una nueva marca que permitiera, una vez añadido el correspondiente códigogestor en los navegadores, la ejecución de programas Java en ellos.

La sintaxis de las etiquetas <APPLET> y <PARAM> es la que se muestra a continuación yque iremos explicando en párrafos posteriores:

<APPLET CODE= WIDTH= HEIGTH= [CODEBASE=] [ALT=][NAME=] [ALIGN=] [VSPACE=] [HSPACE=]><PARAM NAME= VALUE= ></APPLET>

Atributos obligatorios :

CODE : Nombre de la clase principal

WIDTH : Anchura inicial

HEIGHT : Altura inicial

Atributos opcionales :

CODEBASE : URL base del applet

ALT : Texto alternativo

NAME : Nombre de la instancia

ALIGN : Justificación del applet

VSPACE : Espaciado vertical

HSPACE : Espaciado horizontal

Los applets se incluyen en las páginas Web a través de la marca <APPLET>, que paraquien conozca html resultará muy similar a la marca <IMG>. Ambas necesitan la referencia

72 Tutorial de Java

a un fichero fuente que no forma parte de la página en que se encuentran embebidos. IMGhace esto a través de SRC=parámetro y APPLET lo hace a través CODE=parámetro. Elparámetro de CODE indica al navegador dónde se encuentra el fichero con el código Javacompilado .class. Es una localización relativa al documento fuente.

Por razones que no entiendo muy bien, pero posiblemente relacionadas con los packages yclasspaths, si un applet reside en un directorio diferente del que contiene a la página en quese encuentra embebido, entonces no se indica un URL a esta localización, sino que seapunta al directorio del fichero .class utilizando el parámetro CODEBASE, aunque todavíase puede usar CODE para proporcionar el nombre del fichero .class.

Al igual que IMG, APPLET tiene una serie de parámetros que lo posicionan en la página.WIDTH y HEIGHT especifican el tamaño del rectángulo que contendrá al applet, se indicanen pixels. ALIGN funciona igual que con IMG (en los navegadores que lo soportan), defi-niendo cómo se posiciona el rectángulo del applet con respecto a los otros elementos de lapágina. Los valores posibles a especificar son: LEFT, RIGHT, TOP, TEXTTOP, MIDDLE,ABSMIDDLE, BASELINE, BOTTOM y ABSBOTTOM. Y, finalmente, lo mismo que con IMG,se puede especificar un HSPACE y un VSPACE en pixels para indicar la cantidad de espa-cio vacío que habrá de separación entre el applet y el texto que le rodea.

APPLET tiene una marca ALT. La utilizaría un navegador que entendiese la marca APPLET,pero que por alguna razón, no pudiese ejecutarlo. Por ejemplo, si un applet necesita escribiren el disco duro de nuestro ordenador, pero en las características de seguridad tenemosbloqueada esa posibilidad, entonces el navegador presentaría el texto asociado a ALT.

ALT no es utilizado por los navegadores que no entienden la marca APPLET, por ello se hadefinido la marca </APPLET>, que finaliza la descripción del applet. Un navegador consoporte Java ignorará todo el texto que haya entre las dos marcas <APPLET> y </APPLET>,sin embargo, un navegador que no soporte Java ignorará las marcas y presentará el textoque se encuentre entre ellas.

Atributos de APPLETLos atributos que acompañan a la etiqueta <APPLET>, algunos son obligatorios y otros sonopcionales. Todos los atributos, siguiendo la sintaxis de html, se especifican de la forma:atributo=valor. Los atributos obligatorios son:

CODE

Indica el fichero de clase ejecutable, que tiene la extensión .class. No se permite unURL absoluto, como ya se ha dicho, aunque sí puede ser relativo al atributo opcionalCODEBASE.

WIDTH

Indica la anchura inicial que el navegador debe reservar para el applet en pixels.

HEIGHT

Tutorial de Java 73

Indica la altura inicial en pixels. Un applet que disponga de una geometría fija no severá redimensionado por estos atributos. Por ello, si los atributos definen una zonamenor que la que el applet utiliza, únicamente se verá parte del mismo, como si sevisualiza a través de una ventana, eso sí, sin ningún tipo de desplazamiento.

Los atributos opcionales que pueden acompañar a la marca APPLET comprenden los quese indican a continuación:

CODEBASE

Se emplea para utilizar el URL base del applet. En caso de no especificarse, seutilizará el mismo que tiene el documento.

ALT

Como ya se ha dicho, funciona exactamente igual que el ALT de la marca <IMG>, esdecir, muestra un texto alternativo, en este caso al applet, en navegadores en modotexto o que entiendan la etiqueta APPLET pero no implementen una máquina virtualJava.

NAME

Otorga un nombre simbólico a esta instancia del applet en la página que puede serempleado por otros applets de la misma página para localizarlo. Así, un applet puedeser cargado varias veces en la misma página tomando un nombre simbólico distintoen cada momento.

ALIGN

Se emplea para alinear el applet permitiendo al texto fluir a su alrededor. Puedetomas los siguientes valores: LEFT, RIGHT, TOP, TEXTTOP, MIDDLE, ABSMIDDLE,BASELINE, BOTTOM y ABSBOTTOM.

VSPACE

Indica el espaciado vertical entre el applet y el texto, en pixels. Sólo funciona cuandose ha indicado ALIGN = LEFT o RIGHT.

HSPACE

Funciona igual que el anterior pero indicando espaciamiento horizontal, en pixels.Sólo funciona cuando se ha indicado ALIGN = LEFT o RIGHT.

Es probable encontrar en algunas distribuciones otras etiquetas para la inclusión de applets,como <APP>. Esto se debe a que estamos ante la tercera revisión de la extensión de HTMLpara la incrustación de applets y ha sido adoptada como la definitiva. Por ello, cualquier otromedio corresponde a implementaciones obsoletas que han quedado descartadas.

74 Tutorial de Java

Paso de parámetros a los appletsEl espacio que queda entre las marcas de apertura y cierre de la definición de un applet, seutiliza para el paso de parámetros al applet. Para ello se utiliza la marca PARAM en lapágina HTML para indicar los parámetros y el método getParameter() de la clasejava.applet.Applet para leerlos en el código interno del applet. La construcción puede repe-tirse cuantas veces se quiera, una tras otra.

Los atributos que acompañan a la marca PARAM son los siguientes:

NAME

Nombre del parámetro que se desea pasar al applet.

VALUE

Valor que se desea transmitir en el parámetro que se ha indicado antes.

Texto HTML

Texto HTML que será interpretado por los navegadores que no entienden la marcaAPPLET en sustitución del applet mismo.

Para mostar esta posibilidad vamos a modificar nuestro applet básico HolaMundo para quepueda saludar a cualquiera. Lo que haremos será pasarle al applet el nombre de la personaa quien queremos saludar. Generamos el código para ello y lo guardamos en el ficheroHolaTal.java

import java.awt.Graphics; import java.applet.Applet;

public class HolaTal extends Applet { String nombre;

public void init() { nombre = getParameter( «Nombre» ); }

public void paint( Graphics g ) { g.drawString( «Hola «+nombre+»!»,25,25 ); } }

Si compilamos el ejemplo obtendremos el fichero HolaTal.class que incluiremos en nuestrapágina Web. Vamos a generar el fichero HolaTal.html, en el que incluiremos nuestro applet,y que debería tener el siguiente contenido:

<HTML> <APPLET CODE=HolaTal.class WIDTH=300 HEIGHT=100> <PARAM NAME=»Nombre» VALUE=»Agustin»> </APPLET> </HTML>

Por supuesto, que puedes sustituir mi nombre por el tuyo. Este cambio no afectará al códigoJava, no será necesario recompilarlo para que te salude a ti el applet.

Tutorial de Java 75

Los parámetros no se limitan a uno solo. Se puede pasar al applet cualquier número deparámetros y siempre hay que indicar un nombre y un valor para cada uno de ellos.

El método getParameter() es fácil de entender. El único argumento que necesita es el nom-bre del parámetro cuyo valor queremos recuperar. Todos los parámetros se pasan comoStrings, en caso de necesitar pasarle al applet un valor entero, se ha de pasar como String,recuperarlo como tal y luego convertirlo al tipo que deseemos. Tanto el argumento de NAMEcomo el de VALUE deben ir colocados entre dobles comillas («) ya que son String.

El hecho de que las marcas <APPLET> y <PARAM> sean ignoradas por los navegadoresque no entienden Java, es inteligentemente aprovechado a la hora de definir un contenidoalternativo a ser mostrado en este último caso. Así la etiqueta es doble:

<APPLET atributos> parámetros contenido alternativo </APPLET>

Nuestro fichero para mostrar el applet de ejemplo lo modificaremos para que pueda servisualizado en cualquier navegador y en unos casos presente la información alternativa y enotros, ejcute nuestro applet:

<HTML> <APPLET CODE=HolaTal.class WIDTH=300 HEIGHT=100> <PARAM NAME=»Nombre» VALUE=»Agustin»> No verás lo bueno hasta que consigas un navegador <I>Java Compatible</I> </APPLET> </HTML>

Tokens en parámetros de llamadaYa de forma un poco más avanzada vamos a ver como también se pueden pasar variosparámetros en la llamada utilizando separadores, o lo que es lo mismo, separando mediantedelimitadores los parámetros, es decir, tokenizando la cadena que contiene el valor delparámetro, por ejemplo:

<PARAM NAME=Nombre VALUE=»Agustin|Antonio»>

En este caso el separador es la barra vertical «|», que delimita los dos tokens, pero tambiénpodemos redefinirlo y utilizar cualquier otro símbolo como separador:

<PARAM NAME=Separador VALUE=»#»> <PARAM NAME=Nombre VALUE=»Agustin#Antonio»>

Si ahora intentamos cambiar de color de fondo en que aparecen los textos en el applet,utilizando el mismo método, podríamos tener:

<PARAM NAME=Nombre VALUE=»Agustin|Antonio»> <PARAM NAME=Color VALUE=»green|red»>

Es más, podríamos hacer que parpadeasen los mensajes en diferentes colores, cambiandoel color de fondo y el del texto:

<PARAM NAME=Nombre1 VALUE=»Agustin|green|yellow»> <PARAM NAME=Nombre2 VALUE=»Antonio|red|white»>

76 Tutorial de Java

Para recoger los parámetros pasados en este último caso, bastaría con hacer un pequeñobucle de lectura de los parámetros que deseamos:

for( int i=1; ; i++ ) p = getParameter( «Nombre»+i ); if( p == null ) break; . . . }

incluso podríamos utilizar un fichero para pasar parámetros al applet. La llamada sería delmismo tipo:

<PARAM NAME=Fichero VALUE=»FicheroDatos»>

y el FicheroDatos debería tener un contenido, en este caso, que sería el siguiente:

Agustin fondoColor=green textoColor=yellow fuente=Courier fuenteTam=14 Antonio fondoColor=red textocolor=white

E incluso ese FicheroDatos, podríamos hacer que se encontrase en cualquier URL, de for-ma que utilizando el método getContent() podríamos recuperar el contenido del fichero quecontiene los parámetros de funcionamiento del applet:

String getContent( String url ) { URL url = new URL( null,url );

return( (String).url.getContent() ); }

Para recuperar los parámetros que están incluidos en la cadena que contiene el valor pode-mos utilizar dos métodos:

StringTokenizer( string,delimitadores ) treamTokenizer( streamentrada )

Así en la cadena Agustin|Antonio si utilizamos el método:

StringTokenizer( cadena,»|» );

obtenemos el token Agustin, el delimitador «|» y el token Antonio. El código del método seríael que se muestra a continuación:

// Capturamos el parámetro p = getParameter( «p» );

// Creamos el objeto StringTokenizer st = new StringTokenizer( p,»|» );

// Creamos el almacenamiento cads = new String[ st.countTokens() ];

// Separamos los tokens de la cadena del parámetro for( i=0; i < cads.length; i++ ) cadenas[i] = st.nextToken();

Tutorial de Java 77

En el caso de que utilicemos un fichero como verdadera entrada de parámetros al applet y elfichero se encuentre en una dirección URL, utilizamos el método StreamTokenizer() paraobtener los tokens que contiene ese fichero:

// Creamos el objeto URL para acceder a él url = new URL( «http://www.prueba.es/Fichero» );

// Creamos el canal de entrada ce = url.openStream();

// Creamos el objeto StreamTokenizer st = new StreamTokenizer( ce );

// Capturamos los tokens st.nextToken();

El parámetro ARCHIVEUna de las cosas que se achacan a Java es la rapidez. El factor principal en la percepciónque tiene el usuario de la velocidad y valor de los applets es el tiempo que tardan en cargar-se todas las clases que componen el applet. Algunas veces tenemos que estar esperandomás de un minuto para ver una triste animación, ni siquiera buena. Y, desafortunadamente,esta percepción de utilidad negativa puede recaer también sobre applets que realmente síson útiles.

Para entender el porqué de la necesidad de un nuevo método de carga para acelerarla,necesitamos comprender porqué el método actual es lento. Normalmente un applet se com-pone de varias clases, es decir, varios ficheros .class. Por cada uno de estos ficheros .class,el cargador de clases debe abrir una conexión individual entre el navegador y el servidordonde reside el applet. Así, si un applet se compone de 20 ficheros .class, el navegadornecesitará abrir 20 sockets para transmitir cada uno de los ficheros. La sobrecarga querepresenta cada una de estas conexiones es relativamente significante. Por ejemplo, cadaconexión necesita un número de paquetes adicionales que incrementan el tráfico en la Red.

Me imagino que ya el lector habrá pensado la solución al problema: poner todos los ficherosen uno solo, con lo cual solamente sería necesaria una conexión para descargar todo elcódigo del applet. Bien pensado. Esto es lo mismo que han pensado los dos grandes com-petidores en el terreno de los navegadores, Netscape y Microsoft.

Desafortunadamente, las soluciones que han implementado ambas compañías no son di-rectamente compatibles. Microsoft, en su afán de marcar diferencia, crea su propio formatode ficheros CAB. La solución de Netscape es utilizar el archiconocido formato ZIP. Por suer-te, nosotros podemos escribir nuestro código HTML de forma que maneje ambos formatos,en caso necesario. Esto es así porque podemos especificar cada uno de estos formatos deficheros especiales en extensiones separadas de la marca <APPLET>.

No vamos a contar la creación de ficheros CAB; quien esté interesado puede consultar ladocumentación de Java que proporciona Microsoft con su SDK para Java, que es bastanteexhaustiva al respecto. Una vez que disponemos de este fichero, podemos añadir un

78 Tutorial de Java

parámetro CABBASE a la marca <APPLET>:

<APPLET NAME=»Hola» CODE=»HolaMundo» WIDTH=50 HEIGHT=50 > <PARAM NAME=CODEBASE VALUE=»http://www.ejemplo.es/classes»> <PARAM NAME=CABBASE VALUE=»hola.cab»> </APPLET>

El VALUE del parámetro CABBASE es el nombre del fichero CAB que contiene los ficheros.class que componen el conjunto de applet.

Crear un archivo ZIP para utilizarlo con Netscape es muy fácil. Se deben agrupar todos losficheros .class necesarios en un solo fichero .zip. Lo único a tener en cuenta es que sola-mente hay que almacenar los ficheros .class en el archivo; es decir, no hay que comprimir.

Si se está utilizando pkzip, se haría:

Pkzip -e0 archivo.zip listaFicherosClass

El parámetro de la línea de comandos es el número cero, no la «O» mayúscula.

Para utilizar un fichero .zip hay que indicarlo en la marca ARCHIVE de la sección <APPLET>:

<APPLET NAME=»Hola» CODE=»HolaMundo» WIDTH=50 HEIGHT=50 CODEBASE VALUE=»http://www.ejemplo.es/classes» ARCHIVE=»hola.zip»> </APPLET>

Pero hay más. Podemos crear ambos tipos de ficheros y hacer que tanto los usuarios deNetscape Navigator como los de Microsoft Internet Explorer puedan realizar descargas rápi-das del código del applet. No hay que tener en cuenta los usuarios de otros navegadores, ode versiones antiguas de estos dos navegadores, porque ellos todavía podrán seguir car-gando los ficheros a través del método lento habitual. Para compatibilizarlo todo, ponemoslas piezas anteriores juntas:

<APPLET NAME=»Hola» CODE=»HolaMundo» WIDTH=50 HEIGHT=50 CODEBASE VALUE=»http://www.ejemplo.es/classes» ARCHIVE=»hola.zip»> <PARAM NAME=CABBASE VALUE=»hola.cab»> <PARAM NAME=CODEBASE VALUE=»http://www.ejemplo.es/classes»> <PARAM NAME=CABBASE VALUE=»hola.cab»> </APPLET>

Ahora que se puede hacer esto con ficheros .cab y .zip, JavaSoft ha definido un nuevoformato de ficheros, que incorporará en del JDK 1.1, para incluir juntos todos los ficheros deimágenes, sonido y class. JavaSoft llama a esto formato JAR (Java Archive). La marca<APPLET> de HTML se modificará para manejar este nuevo formato JAR a través delparámetro ARCHIVES. Y dejamos al lector el trabajo de poner los tres formatos juntos bajoel mismo paraguas de la marca <APPLET>.

Depuración generalCompilar y ejecutar el programa HolaMundo.java a través del fichero HolaMundo.html nodebería suponer ningún problema, pero alguna vez nos encontraremos frente a programas

Tutorial de Java 79

más difíciles y se necesitará el truco de depuración al que se recurre en el desarrollo deprogramas en cualquier lenguaje.

System.out.println

Una de las herramientas de depuración más efectivas en cualquier lenguaje de programa-ción es simplemente la salida de información por pantalla. El comando System.out.printlnimprime la cadena que se le especifique en la ventana de texto en la que se invocó alnavegador. La forma de usarlo se muestra a continuación:

public void paint( Graphics g ) { g.drawString( «Hola Mundo!»,25,25 ); System.out.println( «Estamos en paint()» ); }

Ciclo de vida de un appletPara seguir el ciclo de vida de un applet, supondremos que estamos ejecutando en nuestronavegador el applet básico HolaMundo, a través de la página HTML que lo carga y corre.

Lo primero que aparece son los mensajes «initializing... starting...», como resultado de lacarga del applet en el navegador. Una vez cargado, lo que sucede es:

Se crea una instancia de la clase que controla al applet

El applet se inicializa a si mismo

Comienza la ejecución del applet

Cuando se abandona la página, por ejemplo, para ir a la siguiente, el applet detiene laejecución. Cuando se regresa a la página que contiene el applet, se reanuda la ejecución.

Si se utiliza la opción del navegador de Reload, es decir, volver a cargar la página, el appletes descargado y vuelto a cargar. El applet libera todos los recursos que hubiese acaparado,detiene su ejecución y ejecuta su finalizador para realizar un proceso de limpieza final desus trazas. Después de esto, el applet se descarga de la memoria y vuelve a cargarsevolviendo a comenzar su inicialización.

Finalmente, cuando se concluye la ejecución del navegador, o de la aplicación que estávisualizando el applet, se detiene la ejecución del applet y se libera toda la memoria yrecursos ocupados por el applet antes de salir del navegador.

Protección de appletsComo curiosidad, más que como algo verdaderamente útil, podemos proteger nuestros appletsde forma muy sencilla, o por lo menos evitar que nadie pueda ocultar en sus páginas HTMLque nosotros somos los legales autores de un applet.

80 Tutorial de Java

El método es muy sencillo y se basa en la utilización de un parámetro del cual comprobamossu existencia, por ejemplo:

<PARAM NAME=copyright VALUE=»Applet de Prueba, A.Froufe (C)1996, Todos los derechos reservados»>

y en el código Java de nuestro applet, comprobaríamos que efectivamente el parámetrocopyright existe y ese es su contenido:

if( !getParameter( «copyright»).equals( «...» ) throw( new Exception( «Violacion del Copyright» ) );

donde «...» es el texto completo del valor del parámetro. Pero también podemos hacerlo deforma más elegante:

copyright = getParameter( «copyright» ); // System.out.println( copyright.hashCode() );

if( copyright != -913936799 ) throw( new Exception( «Violacion del Copyright» ) );

en donde la sentencia comentada nos proporciona el valor del copyright para poder introdu-cirlo en la comparación de la presencia o no del parámetro en la llamada al applet. Habríaque declarar y definir correctamente tipos y variables, pero la idea básica es la que expues-ta.

Escribir applets en JavaPara escribir applets Java, hay que utilizar una serie de métodos, algunos de los cuales yase hay sumariado al hablar de los métodos del appletviewer, que es el visualizador de appletsde Sun. Incluso para el applet más sencillo necesitaremos varios métodos. Son los que seusan para arrancar (start) y detener (stop) la ejecución del applet, para pintar (paint) y actua-lizar (update) la pantalla y para capturar la información que se pasa al applet desde elfichero HTML a través de la marca APPLET.

init()

Esta función miembro es llamada al crearse el applet. Es llamada sólo una vez. La claseApplet no hace nada en init(). Las clases derivadas deben sobrecargar este método paracambiar el tamaño durante su inicialización, y cualquier otra inicialización de los datos quesolamente deba realizarse una vez. Deberían realizarse al menos las siguientes acciones:

· Carga de imágenes y sonido· El resize del applet para que tenga su tamaño correcto· Asignación de valores a las variables globales

Por ejemplo:

public void init() { if( width < 200 || height < 200 ) resize( 200,200 );

Tutorial de Java 81

valor_global1 = 0; valor_global2 = 100;

// cargaremos imágenes en memoria sin mostrarlas // cargaremos música de fondo en memoria sin reproducirla }

destroy()

Esta función miembro es llamada cuando el applet no se va a usar más. La clase Applet nohace nada en este método. Las clases derivadas deberían sobrecargarlo para hacer unalimpieza final. Los applet multithread deberán usar destroy() para «matar» cuanquier threaddel applet que quedase activo.

start()

Llamada para activar el applet. Esta función miembro es llamada cuando se visita el applet.La clase Applet no hace nada en este método. Las clases derivadas deberían sobrecargar-lo para comenzar una animación, sonido, etc.

public void start() { estaDetenido = false;

// comenzar la reproducción de la música musicClip.play(); }

También se puede utilizar start() para eliminar cualquier thread que se necesite.

stop()

Llamada para detener el applet. Se llama cuando el applet desaparece de la pantalla. Laclase Applet no hace nada en este método. Las clases derivadas deberían sobrecargarlopara detener la animación, el sonido, etc.

public void stop() { estaDetenido = true;

if( /* ¿se está reproduciendo música? */ ) musicClip.stop(); }

resize( int width,int height )

El método init() debería llamar a esta función miembro para establecer el tamaño del applet.Puede utilizar las variables ancho y alto, pero no es necesario. Cambiar el tamaño en otrositio que no sea init() produce un reformateo de todo el documento y no se recomienda.

82 Tutorial de Java

En el navegador Netscape, el tamaño del applet es el que se indica en la marca APPLET delHTML, no hace caso a lo que se indique desde el código Java del applet.

width

Variable entera, su valor es el ancho definido en el parámetro WIDTH de la marca HTML delAPPLET. Por defecto es el ancho del icono.

height

Variable entera, su valor es la altura definida en el parámetro HEIGHT de la marca HTML delAPPLET. Por defecto es la altura del icono. Tanto width como height están siempre disponi-bles para que se puede chequear el tamaño del applet.

Podemos retomar el ejemplo de init():

public void init() { if( width < 200 || height < 200 ) resize( 200,200 ); ...

paint( Graphics g )

Se llama cada vez que se necesita refrescar el área de dibujo del applet. La clase Appletsimplemente dibuja una caja con sombreado de tres dimensiones en el área. Obviamente, laclase derivada debería sobrecargar este método para representar algo inteligente en lapantalla.

Para repintar toda la pantalla cuando llega un evento Paint, se pide el rectángulo sobre elque se va a aplicar paint() y si es más pequeño que el tamaño real del applet se invoca arepaint(), que como va a hacer un update(), se actualizará toda la pantalla.

Podemos utilizar paint() para imprimir nuestro mensaje de bienvenida:

void public paint( Graphics g ) { g.drawString( «Hola Java!»,25,25 ); // Dibujaremos la imágenes que necesitemos }

update( Graphics g )

Esta es la función que se llama realmente cuando se necesita actualizar la pantalla. La claseApplet simplemente limpia el área y llama al método paint(). Esta funcionalidad es suficienteen la mayoría de los casos. De cualquier forma, las clases derivadas pueden sustituir estafuncionalidad para sus propósitos.

Tutorial de Java 83

Podemos, por ejemplo, utilizar update() para modificar selectivamente partes del área gráfi-ca sin tener que pintar el área completa:

public void update( Graphics g ) { if( estaActualizado ) { g.clear(); // garantiza la pantalla limpia repaint(); // podemos usar el método padre: super.update() } else // Información adicional g.drawString( «Otra información»,25,50 ); }

repaint()

A esta función se la debería llamar cuando el applet necesite ser repintado. No deberíasobrecargarse, sino dejar que Java repinte completamente el contenido del applet.

Al llamar a repaint(), sin parámetros, internamente se llama a update() que borrará el rectán-gulo sobre el que se redibujará y luego se llama a paint(). Como a repaint() se le puedenpasar parámetros, se puede modificar el rectángulo a repintar.

getParameter( String attr )

Este método carga los valores parados al applet vía la marca APPLET de HTML. El argu-mento String es el nombre del parámetro que se quiere obtener. Devuelve el valor que se lehaya asignado al parámetro; en caso de que no se le haya asignado ninguno, devolveránull.

Para usar getParameter(), se define una cadena genérica. Una vez que se ha capturado elparámetro, se utilizan métodos de cadena o de números para convertir el valor obtenido altipo adecuado.

public void init() { String pv;

pv = getParameter( «velocidad» ); if( pv == null ) velocidad = 10; else velocidad = Integer.parseInt( pv ); }

getDocumentBase()

Indica la ruta http, o el directorio del disco, de donde se ha recogido la página HTML quecontiene el applet, es decir, el lugar donde está la hoja en todo Internet o en el disco.

84 Tutorial de Java

getCodeBase()

Indica la ruta http, o el directorio del disco, de donde se ha cargado el código bytecode queforma el applet, es decir, el lugar donde está el fichero .class en todo Internet o en el disco.

print( Graphics g )

Para imprimir en impresora, al igual que paint() se puede utilizar print(), que pintará en laimpresora el mapa de bits del dibujo.

La aplicación Fecha (Aproximación a OOP)Veamos ahora una aplicación un poco más útil que HolaMundo, presentaremos en pantallala fecha y hora del sistema. Aprovecharemos también para realizar un introducción muysencilla a los conceptos fundamentales de la programación orientada a objetos, clases yobjetos, a través de esta simple aplicación.

import java.util.Date;

class FechaApp { public static void main( String args[] ) { Date hoy = new Date(); System.out.println( hoy ); } }

Esta aplicación es una versión modificada de HolaMundoApp de la que difiere porque seimporta la clase Date, la aplicación se llama ahora FechaApp en vez de HolaMundoApp, secrea un objeto Date y el mensaje de salida a pantalla es diferente. Almacenaremos estanueva aplicación en el fichero FechaApp.java.

La línea de código:

class FechaApp {

es el inicio del bloque de la declaración de nuestra clase. Ya hemos dicho que todas lasfunciones y variables en Java, existen dentro de una clase o un objeto, Java no soportafunciones o variables globales. Por tanto, la declaración de la clase se convierte en el es-queleto de cualquier aplicación Java. La clase, el bloque básico de un lenguaje orientado aobjetos como Java, es la plantilla que usamos para describir los datos y el entorno asociadoa las instancias de esa clase. Cuando se instancia una clase, se crea un objeto del tipodefinido por la clase y exactamente igual que cualquier otra instancia realizada de la mismaclase. Los datos asociados a la clase u objeto son las variables y el entorno asociado con laclase u objeto son los métodos.

Un ejemplo de clase es la clase que representa un rectángulo. Esta clase contiene lasvariables que indican las coordenadas del origen del rectángulo y su ancho y alto. La clase

Tutorial de Java 85

puede contener un método para calcular el área de ese rectángulo. Ahora podemosinstanciarlo para muy diferentes propósitos, es decir, podemos tener objetos rectánguloespecíficos, así podremos obtener información de las dimensiones de nuestro dormitorio ode las dimensiones de la ventana en donde se está visualizando esta página.

class NombreDeLaClase { . . . }

Esta es la forma general de definición de una clase en Java, donde la palabra clave classinicia la definición de la clase NombreDeLaClase. Las variables y métodos de la clase hande ir colocados entre las llaves que delimitan el bloque de definición de la clase. FechaAppno tiene variables y solamente tiene un método llamado main().

Este método, main(), es el cerebro de cualquier aplicación Java. Cuando se ejecuta unaaplicación Java utilizando el intérprete Java, se debe especificar el nombre de la clase quese desea ejecutar. El intérprete entonces, invoca al método main() definido dentro de esaclase, que debe controlar el flujo del programa, pedir al sistema los recursos que necesite yejecutar cualquier otro método necesario para completar la funcionalidad de la aplicación.

La definición del método main() debe estar precedida por tres modificadores:

· public indica que el método main() puede ser llamado por cualquier objeto· static indica que el método main() es un método estático, es decir, un métodopropio de la clase· void indica que el método main() no devolverá ningún valor

El método main() en Java es similar a la función main() de C y C++. Cuando se ejecuta unprograma escrito en C o C++, arranca llamando en primer lugar a la función main(), quellamará a las demás funciones necesarias en la ejecución del programa. De forma similar,en el lenguaje Java, cuando se ejecuta una clase con el intérprete Java, el sistema comien-za llamando al método main() de la clase, que llamará a los demás métodos necesarios paracompletar la ejecución de la aplicación. Si se intenta ejecutar una clase con el intérpreteJava que no contenga el método main(), el intérprete generará un mensaje de error.

El método main() acepta como argumento un array de Strings:

public static void main( Strings args[] ) {

Este array de Strings es el mecanismo a través del cual el sistema puede pasar informacióna la aplicación. Cada una de las cadenas String es un argumento de línea de comandos.Permiten pasar información a la aplicación, para variar su ejecución, sin necesidad derecompilarla. Por ejemplo, si desarrollamos una aplicación de ordenación, podríamos permi-tir al usuario seleccionar el método, ascendente o descendente, en la línea de comandos deejecución de la aplicación.

-descendente

Nuestra aplicación FechaApp ignora los argumentos de la línea de comandos, así que nonos extendemos más, pero volveremos sobre ello más adelante. No obstante, los programa-dores de C y C++ deben tener en cuenta que en Java el número y tipo de argumentos de lalínea de comandos es diferente a los que se pasan a la función main() en C y C++.

La aplicación FechaApp es el programa más simple que podemos hacer que realice algo

86 Tutorial de Java

interesante, pero por su misma sencillez no necesita ninguna clase adicional. Sin embargo,la mayoría de los programas que escribamos serán más complejos y necesitarán que escri-bamos otras clases y utilizar las que nos proporciona Java como soporte.

Nuestra aplicación FechaApp utiliza dos clases, la clase System y la clase Date, que nosproporciona el entorno de desarrollo de Java. La clase System proporciona un acceso alsistema independiente del hardware sobre el que estemos ejecutando la aplicación y laclase Date proporciona un acceso a las funciones de Fecha independientemente del siste-ma en que estemos ejecutando la aplicación.

Tutorial de Java 87

Capítulo 5El depurador de Java - JDB

El depurador de Java, jdb es un depurador de línea de comandos, similar al que Sun propor-ciona en sus Sistemas, dbx. Es complicado de utilizar y un tanto críptico, por lo que, enprincipio, tiene escasa practicidad y es necesaria una verdadera emergencia para tener querecurrir a él.

Trataremos por encima los comandos que proporciona el jdb, pero sin entrar en detalles desu funcionamiento, porque no merece la pena. Casi es mejor esperar a disponer de herra-mientas visuales para poder depurar con cierta comodidad nuestro código Java.

Para poder utilizar el depurador, las aplicaciones Java deben estar compiladas con la op-ción de depuración activada, -g. Posteriormente se puede lanzar appletviewer con la opciónde depuración, debug, y habremos puesto en marcha jdb.

Depurar HolaMundo

Hemos modificado nuestro applet de ejemplo para utilizarlo en nuestra sesión de ejemplocon el depurador. Se compilaría con el comando:

%javac -g hm.java

y el contenido de nuestro applet HolaMundo modificado y guardado en el fichero hm.javasería el siguiente:

// // Applet HolaMundo de ejemplo, para depurar // import java.awt.Graphics; import java.applet.Applet;

public class hm extends Applet { int i;

public void paint( Graphics g ) { i = 10; g.drawString( «Hola Mundo!»,25,25 ); } }

Una vez compilado, iniciamos la sesión lanzando el visor de applets de Sun con la opción dedepuración, utilizando el comando:

88 Tutorial de Java

%appletviewer -debug hm.html

El fichero hm.html contiene las líneas mínimas para poder activar el applet, estas líneas sonlas que reproducimos:

<html> <applet code=hm.class width=100 height=100> </applet> </html>

Se inicia pues la sesión con el depurador y vamos a ir reproduciendo lo que aparece en lapantalla a medida que vamos introduciendo comandos:

%appletviewer -debug hm.htmlLoading jdb...0xee301bf0:class(sun.applet.AppletViewer)>

Comando help

El comando help proporciona una lista de los comandos que están disponibles en la sesiónde jdb. Esta lista es la que sigue, en donde hemos aprovechado la presencia de todos loscomandos para comentar la acción que cada uno de ellos lleva a cabo.

>help** command list **threads [threadgroup] — lista threadsthread <thread id> — establece el thread por defectosuspend [thread id(s)] — suspende threads (por defecto, todos)resume [thread id(s)] — continúa threads (por defecto, todos)where [thread id]|all — muestra la pila de un threadthreadgroups — lista los grupos de threadsthreadgroup <name> — establece el grupo de thread actual

print <id> [id(s)] — imprime un objeto o campodump <id> [id(s)] — imprime toda la información del objeto

locals — imprime las variables locales de la pila actual

classes — lista las clases conocidasmethods <class id> — lista los métodos de una clase

stop in <class id>.<method> — fija un punto de ruptura en un métodostop at <class id>:<line> — establece un punto de ruptura en una líneaup [n frames] — ascender en la pila de threadsdown [n frames] — descender en la pila de threadsclear <class id>:<line> — eliminar un punto de rupturastep — ejecutar la línea actualcont — continuar la ejecución desde el punto de ruptura

Tutorial de Java 89

catch <class id> — parar por la excepción especificadaignore <class id> — ignorar la excepción especificada

list [line number] — imprimir código fuenteuse [source file path] — ver o cambiar la ruta del fichero fuentememory — informe del uso de la memoriaload <classname> - carga la clase Java a ser depuradarun <args> - comienza la ejecución de la clase cargada!! - repite el último comandohelp (or ?) - lista los comandosexit (or quit) - salir del depurador>

Comando threadgroups

El comando threadgroups permite ver la lista de threads que se están ejecutando. Losgrupos system y main deberían estar siempre corriendo.

>threadgroups1.(java.lang.ThreadGroup)0xee300068 system2.(java.lang.ThreadGroup)0xee300a98 main>

Comando threads

El comando threads se utiliza para ver la lista completa de los threads que se están ejecu-tando actualmente.

>threadsGroup system:1.(java.lang.Thread)0xee300098 clock handler cond2.(java.lang.Thread)0xee300558 Idle thread run3.(java.lang.Thread)0xee3005d0 sync Garbage Collector cond4.(java.lang.Thread)0xee300620 Finalizer thread cond5.(java.lang.Thread)0xee300a20 Debugger agent run6.(java.tools.debug.BreakpointHandler)0xee300b58) Breakpoint handler condGroup main:7.(java.lang.Thread)0xee300048 main suspended>

Comando run

El comando run es el que se utiliza para arrancar el appletviewer en la sesión de depura-ción. Lo teclearemos y luego volveremos a listar los threads que hay en ejecución.

>runrun sun.applet.AppletViewer hm.htmlrunning...

90 Tutorial de Java

main[1]threadsthreadsGroup sun.applet.AppletViewer.main:1.(java.lang.Thread)0xee3000c0 AWT-Motif running2.(sun.awt.ScreenUpdater)0xee302ed0 ScreenUpdater cond. WaitingGroup applet-hm.class:3.(java.lang.Thread)0xee302f38 Thread-6 cond. Waitingmain[1]

El visor de applets de Sun aparecerá en la pantalla y mostrará el conocido mensaje desaludo al Mundo. Ahora vamos a rearrancar el appletviewer con un punto de ruptura, paradetener la ejecución del applet, y podamos seguir mostrando los comandos disponibles enel jdb.

main[1]exit%appletviewer -debug hm.htmlLoading jdb...0xee3009c8:class(sun.applet.AppletViewer)>stop in hm.paintBreakpoint set in hm.paint>runrun sun.applet.AppletViewer hm.htmlrunning...Breakpoint hit: hm.paint(hm.java:9)AWT-Motif[1]

Comando where

El comando where mostrará la pila de ejecución del applet.

AWT-Motif[1]where[1]hm.paint(hm.java:9)[2]sun.awt.motif.MComponentPeer.paint(MComponenetPeer.java:109)[3]sun.awt.motif.MComponentPeer.handleExpose(MComponenetPeer.java:170)AWT-Motif[1]

Comando use

El comando use nos informa del camino donde jdb va a buscar los ficheros fuentes quecontienen el código Java de las clases que se están depurando. Por defecto, utilizará elcamino que se especifique en la variable de entorno CLASSPATH.

AWT-Motif[1]use/usr/local/java/classes:AWT-Motif[1]

Comando list

El comando list mostrará el código fuente actual al comienzo del punto de ruptura quehayamos fijado.

AWT-Motif[1]list

Tutorial de Java 91

9 public void paint( Graphics g ) {10 => i = 10;11 g.drawString( «Hola Mundo!»,25,25 ) ;12 }13 }AWT-Motif[1]

Comando dump

El comando dump nos permitirá ahora ver el valor del objeto g pasado desde el appletviewer.

AWT-Motif[1]dump gg = (sun.awt.motif.X11Graphics)0xee303df8 { int pData = 1342480 Color foreground = (java.awt.Color)0xee302378 Font font = (java.awt.Font)0xee302138 int originX = 0 int originY = 0 float scaleX = 1 float scaleY = 1 Image image = null}AWT-Motif[1]

Comando step

El comando step nos porporciona el método para ejecutar la línea actual, que estará siendo apuntada por elindicador si hemos utilizado el comando list.

AWT-Motif[1]stepBreakpoint hit: hm.paint(hm.java:11)AWT-Motif[1]list9 public void paint( Graphics g ) {10 i = 10;11 => g.drawString( «Hola Mundo!»,25,25 );12 }13 }AWT-Motif[1]

92 Tutorial de Java

Tutorial de Java 93

Capítulo 6Clases Java

En cualquier lenguaje orientado a objetos, las clases definen cualquier objeto que se puedamanipular. Java tiene muchas clases útiles, no solamente aquellas que se utilizan paragráficos y sonido, usadas en la construcción de applets mucho más complejos.

La clase MathLa clase Math representa la librería matemática de Java. Las funciones que contiene son lasde todos los lenguajes, parece que se han metido en una clase solamente a propósito deagrupación, por eso se encapsulan en Math, y lo mismo sucede con las demás clases quecorresponden a objetos que tienen un tipo equivalente (Character, Float, etc.). El construc-tor de la clase es privado, por los que no se pueden crear instancias de la clase. Sin embar-go, Math es public para que se pueda llamar desde cualquier sitio y static para que no hayaque inicializarla.

Funciones matemáticas

Si se importa la clase, se tiene acceso al conjunto de funciones matemáticas estándar:

Math.abs( x ) para int, long, float y doubleMath.sin( double )Math.cos( double )Math.tan( double )Math.asin( double )Math.acos( double )Math.atan( double )Math.atan2( double,double )Math.exp( double )Math.log( double )Math.sqrt( double )Math.ceil( double )Math.floor( double )Math.rint( double )Math.pow( a,b )Math.round( x ) para double y float

94 Tutorial de Java

Math.random() devuelve un doubleMath.max( a,b ) para int, long, float y doubleMath.min( a,b ) para int, long, float y doubleMath.E para la base exponencialMath.PI para PI

He aquí un ejemplo, Mates.java, de uso de algunas funciones de la clase Math:

class Mates { public static void main( String args[] ) { int x; double rand,y,z; float max;

rand = Math.random(); x = Math.abs( -123 ); y = Math.round( 123.567 ); z = Math.pow( 2,4 ); max = Math.max( (float)1e10,(float)3e9 );

System.out.println( rand ); System.out.println( x ); System.out.println( y ); System.out.println( z ); System.out.println( max ); } }

La clase CharacterAl trabajar con caracteres se necesitan muchas funciones de comprobación y traslación.Estas funciones están empleadas en la clase Character . De esta clase sí que se puedencrear instancias, al contrario que sucede con la clase Math .

Declaraciones

La primera sentencia creará una variable carácter y la segunda un objeto Character:

char c; Character C;

Comprobaciones booleanas Character.isLowerCase( c ) Character.isUpperCase( c ) Character.isDigit( c ) Character.isSpace( c )

En este caso, si tuviésemos un objeto Character C, no se podría hacer C.isLowerCase,porque no se ha hecho un new de Character. Estas funciones son estáticas y no conocen alobjeto, por eso hay que crealo antes.

Tutorial de Java 95

Traslaciones de caracteres char c2 = Character.toLowerCase( c ); char c2 = Character.toUpperCase( c );

Traslaciones de carácter/dígito int i = Character.digit( c,base ); char c = Character.forDigit( i,base );

Métodos de la clase Character C = new Character( ‘J’ ); char c = C.charValue(); String s = C.toString();

La clase FloatCada tipo numérico tiene su propia clase de objetos. Así el tipo float tiene el objeto Float. Dela misma forma que con la clase Character , se han codificado muchas funciones útilesdentro de los métodos de la clase Float .

Declaraciones

La primera sentencia creará una variable float y la segunda un objeto Float:

float f; Float F;

Valores de Float Float.POSITIVE_INFINITY Float.NEGATIVE_INFINITY Float.NaN Float.MAX_VALUE Float.MIN_VALUE

Conversiones de Clase/Cadena String s = Float.toString( f ); f = Float.valueOf( «3.14» );

96 Tutorial de Java

Comprobaciones boolean b = Float.isNaN( f ); boolean b = Float.isInfinite( f );

La función isNaN() comprueba si f es un No-Número. Un ejemplo de no-número es raizcuadrada de -2.

Conversiones de Objetos Float F = new Float( Float.PI ); String s = F.toString(); int i = F.intValue(); long l = F.longValue(); float F = F.floatValue(); double d = F.doubleValue();

Otros Métodos int i = F.hashCode(); boolean b = F.equals( Object obj ); int i = Float.floatToIntBits( f ); float f = Float.intBitsToFloat( i );

La clase DoubleCada tipo numérico tiene su propia clase de objetos. Así el tipo double tiene el objeto Double.De la misma forma que con la clase Character , se han codificado muchas funciones útilesdentro de los métodos de la clase Double .

Declaraciones

La primera sentencia creará una variable double y la segunda un objeto Double:

double d; Double D;

Valores de Double Double.POSITIVE_INFINITY Double.NEGATIVE_INFINITY Double.NaN Double.MAX_VALUE Double.MIN_VALUE

Tutorial de Java 97

Métodos de Double D.isNaN(); Double.isNaN( d ); D.isInfinite(); Double.isInfinite( d ); boolean D.equals(); String D.toString(); int D.intValue(); long D.longValue(); float D.floatValue(); double D.doubleValue(); int i = D.hashCode(); Double V.valueOf( String s ); long l = Double.doubleToLongBits( d ); double d = Double.longBitsToDouble( l );

La clase IntegerCada tipo numérico tiene su propia clase de objetos. Así el tipo int tiene el objeto Integer. Dela misma forma que con la clase Character , se han codificado muchas funciones útilesdentro de los métodos de la clase Integer .

Declaraciones

La primera sentencia creará una variable int y la segunda un objeto Integer:

int i; Integer I;

Valores de Integer Integer.MIN_VALUE; Integer.MAX_VALUE;

Métodos de Integer String Integer.toString( int i,int base ); String Integer.toString( int i ); int I.parseInt( String s,int base ); int I.parseInt( String s ); Integer Integer.valueOf( String s,int base ); Integer Integer.valueOf( String s ); int I.intValue(); long I.longValue(); float I.floatValue(); double I.doubleValue(); String I.toString(); int I.hashCode();

98 Tutorial de Java

boolean I.equals( Object obj );

En los métodos toString(), parseInt() y valueOf() que no se especifica la base sobre la quese trabaja, se asume que es base 10 .

La clase LongCada tipo numérico tiene su propia clase de objetos. Así el tipo long tiene el objeto Long. Dela misma forma que con la clase Character , se han codificado muchas funciones útilesdentro de los métodos de la clase Long .

Declaraciones

La primera sentencia creará una variable long y la segunda un objeto Long:

long l; Long L;

Valores de Long Long.MIN_VALUE; Long.MAX_VALUE;

Métodos de Long String Long.toString( long l,int base ); String Long.toString( long l ); long L.parseLong( String s,int base ); long L.parseLong( String s ); Long Long.valueOf( String s,int base ); Long Long.valueOf( String s ); int L.intValue(); long L.longValue(); float L.floatValue(); double L.doubleValue(); String L.toString(); int L.hashCode(); boolean L.equals( Object obj );

En los métodos toString(), parseInt() y valueOf() que no se especifica la base sobre la quese trabaja, se asume que es base 10 .

La clase BooleanLos valores boolean también tienen su tipo asociado Boolean, aunque en este caso haymenos métodos implementados que para el resto de las clases numéricas.

Tutorial de Java 99

Declaraciones

La primera sentencia creará una variable boolean y la segunda un objeto Boolean:

boolean b; Boolean B;

Valores de Boolean Boolean.TRUE; Boolean.FALSE;

Métodos de Boolean boolean B.booleanValue(); String B.toString(); boolean B.equals( Object obj );

La clase StringJava posee gran capacidad para el manejo de cadenas dentro de sus clases String yStringBuffer . Un objeto String representa una cadena alfanumérica de un valor constanteque no puede ser cambiada después de haber sido creada. Un objeto StringBuffer represen-ta una cadena cuyo tamaño puede variar.

Los Strings son objetos constantes y por lo tanto muy baratos para el sistema. La mayoría delas funciones relacionadas con cadenas esperan valores String como argumentos y devuel-ven valores String.

Hay que tener en cuenta que las funciones estáticas no consumen memoria del objeto, conlo cual es más conveniente usar Character que char. No obstante, char se usa, por ejemplo,para leer ficheros que están escritos desde otro lenguaje.

Existen muchos constructores para crear nuevas cadenas:

String(); String( String str ); String( char val[] ); String( char val[],int offset,int count ); String( byte val[],int hibyte ); String( byte val[],int hibyte,int offset,int count );

Tal como uno puede imaginarse, las cadenas pueden ser muy complejas, existiendo muchasfunciones muy útiles para trabajar con ellas y, afortunadamente, la mayoría están codifica-das en la clase String .

100 Tutorial de Java

Funciones Básicas

La primera devuelve la longitud de la cadena y la segunda devuelve el carácter que seencuentra en la posición que se indica en indice:

int length(); char charAt( int indice );

Funciones de Comparación de Strings

boolean equals( Object obj ); boolean equalsIgnoreCase( Object obj );

Lo mismo que equals() pero no tiene en cuenta mayúsculas o minúsculas.

int compareTo( String str2 );

Devuelve un entero menor que cero si la cadena es léxicamente menor que str2. Devuelvecero si las dos cadenas son léxicamente iguales y un entero mayor que cero si la cadena esléxicamente mayor que str2.

Funciones de Comparación de Subcadenas

boolean regionMatch( int thisoffset,String s2,int s2offset,int len ); boolean regionMatch( boolean ignoreCase,int thisoffset,String s2, int s2offset,int 1 );

Comprueba si una región de esta cadena es igual a una región de otra cadena.

boolean startsWith( String prefix ); boolean startsWith( String prefix,int offset ); boolean endsWith( String suffix );

Devuelve si esta cadena comienza o termina con un cierto prefijo o sufijo comenzando en undeterminado desplazamiento.

int indexOf( int ch ); int indexOf( int ch,int fromindex ); int lastIndexOf( int ch ); int lastIndexOf( int ch,int fromindex ); int indexOf( String str ); int indexOf( String str,int fromindex ); int lastIndexOf( String str ); int lastIndexOf( String str,int fromindex );

Devuelve el primer/último índice de un carácter/cadena empezando la búsqueda a partir deun determinado desplazamiento.

String substring( int beginindex ); String substring( int beginindex,int endindex ); String concat( String str ); String replace( char oldchar,char newchar );

Tutorial de Java 101

String toLowerCase(); String toUpperCase(); String trim();

Ajusta los espacios en blanco al comienzo y al final de la cadena.

void getChars( int srcBegin,int srcEnd,char dst[],int dstBegin ); void getBytes( int srcBegin,int srcEnd,byte dst[],int dstBegin ); String toString(); char toCharArray(); int hashCode();

Funciones ValueOf

La clase String posee numerosas funciones para transformar valores de otros tipos dedatos a su representación como cadena. Todas estas funciones tienen el nombre de valueOf,estando el método sobrecargado para todos los tipos de datos básicos.

Veamos un ejemplo de su utilización:

String Uno = new String( «Hola Mundo» ); float f = 3.141592;

String PI = Uno.valueOf( f ); String PI = String.valueOf( f ); // Mucho más correcto

Funciones de Conversión

String valueOf( boolean b ); String valueOf( int i ); String valueOf( long l ); String valueOf( float f ); String valueOf( double d ); String valueOf( Object obj ); String valueOf( char data[] ); String valueOf( char data[],int offset,int count );Usa arrays de caracteres para la cadena. String copyValueOf( char data[] ); String copyValueOf( char data[],int offset,int count );Crea un nuevo array equivalente para la cadena.

La clase StringBufferJava posee gran capacidad para el manejo de cadenas dentro de sus clases String yStringBuffer . Un objeto String representa una cadena alfanumérica de un valor constanteque no puede ser cambiada después de haber sido creada. Un objeto StringBuffer represen-ta una cadena cuyo tamaño puede variar.

La clase StringBuffer dispone de muchos métodos para modificar el contenido de los obje-

102 Tutorial de Java

tos StringBuffer. Si el contenido de una cadena va a ser modificado en un programa, habráque sacrificar el uso de objetos String en beneficio de StringBuffer, que aunque consumenmás recursos del sistema, permiten ese tipo de manipulaciones.

Al estar la mayoría de las características de los StringBuffers basadas en su tamaño varia-ble, se necesita un nuevo método de creación:

StringBuffer(); StringBuffer( int len ); StringBuffer( String str );

Se puede crear un StringBuffer vacío de cualquier longitud y también se puede utilizar unString como punto de partida para un StringBuffer.

StringBuffer Dos = new StringBuffer( 20 ); StringBuffer Uno = new StringBuffer( «Hola Mundo» );

Cambio de Tamaño

El cambio de tamaño de un StringBuffer necesita varias funciones específicas para manipu-lar el tamaño de las cadenas:

int length(); char charAt( int index ); void getChars( int srcBegin,int srcEnd,char dst[],int dstBegin ); String toString(); void setLength( int newlength ); void setCharAt( int index,char ch ); int capacity(); void ensureCapacity( int minimum ); void copyWhenShared();

Obervar que una de las funciones devuelve una cadena constante normal de tipo String.Este objeto se puede usar con cualquier función String, como por ejemplo, en las funcionesde comparación.

Modificación del Contenido

Para cambiar el contenido de un StringBuffer, se pueden utilizar dos métodos: append() einsert().

En el ejemplo CadAppend.java, vemos el uso de estos dos métodos:

class CadAppend { public static void main( String args[] ) { StringBuffer str = new StringBuffer( «Hola» ); str.append( « Mundo» ); System.out.println( str ); } }

En este otro ejemplo, CadInversa.java, mostramos un método muy simple que le da la vueltaa una cadena:

Tutorial de Java 103

class CadInversa { public static String cadenaInversa( String fuente ) { int longitud = fuente.length();

StringBuffer destino = new StringBuffer( longitud ); for( int i=(longitud-1); i >= 0; i— ) destino.append( fuente.charAt( i ) );

return( destino.toString() ); }

public static void main( String args[] ) { System.out.println( cadenaInversa( «Hola Mundo» ) ); } }

Las funciones que cambian el tamaño son pues:

StringBuffer append( Object obj ); StringBuffer append( String str ); StringBuffer append( char str[] ); StringBuffer append( char str[],int offset,int len ); StringBuffer append( boolean b ); StringBuffer append( int i ); StringBuffer append( long l ); StringBuffer append( float f ); StringBuffer append( double d ); StringBuffer append( char ch ); StringBuffer insert( int offset,Object obj ); StringBuffer insert( int offset,String str ); StringBuffer insert( int offset,char str[] ); StringBuffer insert( int offset,boolean b ); StringBuffer insert( int offset,int i ); StringBuffer insert( int offset,long l ); StringBuffer insert( int offset,float f ); StringBuffer insert( int offset,double d ); StringBuffer insert( int offset,char ch );

Operadores de Concatenación

Hay que recordar que los operadores «+» y «+=» también se pueden aplicar a cadenas.Ambos realizan una concatenación y están implementados con objetos StringBuffer.

Por ejemplo, la sentencia:

String s = «¿Qué» + « tal ?»;

es interpretada por el compilador como:

String s = new StringBuffer().append( «¿Qué» ).append( « tal ?» ).toString();

y se marcaría el StringBuffer para borrarlo ya que el contenido pasa al objeto String. Tam-bién, la sentencia:

s += « por ahí!»;

sería interpretada por el sistema como:

104 Tutorial de Java

String s = new StringBuffer().append( s ).append( « por ahí!» ).toString();

y volvería a marcar para borrar el nuevo StringBuffer.

Uso de conversionesVeamos un ejemplo de utilidad de estas funciones. En el applet Conversion.java, que semuestra en el código que sigue, se usan estas funciones para producir una salida útil en unprograma, presentando las coordenadas en las que se ha hecho click con el botón del ratón. public class Conversion extends Applet { int RatonX = 25; int RatonY = 25; String Status = «Haz click con el ratón»;

public void paint( Graphics g ) { g.drawString( Status,RatonX,RatonY ); }

public boolean mouseDown( Event evt,int x,int y ) { Integer X = new Integer( x ); Integer Y = new Integer( y );

RatonX = x; RatonY = y; Status = X.toString()+»,»+Y.toString();

repaint(); return true; } }

Tutorial de Java 105

Capítulo 7Abstract Window Toolkit (AWT)

Introducción al AWTAWT es el acrónimo del X Window Toolkit para Java, donde X puede ser cualquier cosa:Abstract, Alternative, Awkward, Another o Asqueroso; aunque parece que Sun se decantapor Abstracto , seriedad por encima de todo. Se trata de una biblioteca de clases Java parael desarrollo de Interfaces de Usuario Gráficas. La versión del AWT que Sun proporcionacon el JDK se desarrolló en sólo dos meses y es la parte más débil de todo lo que representaJava como lenguaje. El entorno que ofrece es demasiado simple, no se han tenido en cuen-ta las ideas de entornos gráficos novedosos, sino que se ha ahondado en estructuras orien-tadas a eventos, llenas de callbacks y sin soporte alguno del entorno para la construccióngráfica; veremos que la simple acción de colocar un dibujo sobre un botón se vuelve unatarea harto complicada. Quizá la presión de tener que lanzar algo al mercado haya tenidomucho que ver en la pobreza de AWT.

JavaSoft, asegura que esto sólo era el principio y que AWT será multi-idioma, tendrá herra-mientas visuales, etc. En fin, al igual que dicen los astrólogos, el futuro nos deparará mu-chas sorpresas.

La estructura básica del AWT se basa en Componentes y Contenedores. Estos últimoscontienen Componentes posicionados a su respecto y son Componentes a su vez, de formaque los eventos pueden tratarse tanto en Contenedores como en Componentes, corriendopor cuenta del programador (todavía no hay herramientas de composición visual) el encajede todas las piezas, así como la seguridad de tratamiento de los eventos adecuados. Nadatrivial.

No obstante y pese a ello, vamos a abordar en este momento la programación con el AWTpara tener la base suficiente y poder seguir profundizando en las demás características dellenguaje Java, porque como vamos a ir presentando ejemplos gráficos es imprescindible elconocimiento del AWT. Mientras tanto, esperemos que JavaSoft sea fiel a sus prediccionesy lo que ahora veamos nos sirva de base para migrar a un nuevo y maravilloso AWT.

Interface de usuarioLa interface de usuario es la parte del programa que permite a éste interactuar con el usua-rio. Las interfaces de usuario pueden adoptar muchas formas, que van desde la simple líneade comandos hasta las interfaces gráficas que proporcionan las aplicaciones más moder-nas.

106 Tutorial de Java

La interface de usuario es el aspecto más importante de cualquier aplicación. Una aplica-ción sin un interfaz fácil, impide que los usuarios saquen el máximo rendimiento del progra-ma. Java proporciona los elementos básicos para construir decentes interfaces de usuario através del AWT.

Al nivel más bajo, el sistema operativo transmite información desde el ratón y el tecladocomo dispositivos de entrada al programa. El AWT fue diseñado pensando en que el progra-mador no tuviese que preocuparse de detalles como controlar el movimiento del ratón o leerel teclado, ni tampoco atender a detalles como la escritura en pantalla. El AWT constituyeuna librería de clases orientada a objeto para cubrir estos recursos y servicios de bajo nivel.

Debido a que el lenguaje de programación Java es independiente de la plataforma en quese ejecuten sus aplicaciones, el AWT también es independiente de la plataforma en que seejecute. El AWT proporciona un conjunto de herramientas para la construcción de interfacesgráficas que tienen una apariencia y se comportan de forma semejante en todas las platafor-mas en que se ejecute. Los elementos de interface proporcionados por el AWT estánimplementados utilizando toolkits nativos de las plataformas, preservando una aparienciasemejante a todas las aplicaciones que se creen para esa plataforma. Este es un puntofuerte del AWT, pero también tiene la desventaja de que una interface gráfica diseñada parauna plataforma, puede no visualizarse correctamente en otra diferente.

Estructura del AWTLa estructura de la versión actual del AWT podemos resumirla en los puntos que expone-mos a continuación:

· Los Contenedores contienen Componentes, que son los controles básicos· No se usan posiciones fijas de los Componentes, sino que están situados através de una disposición controlada (layouts)· El común denominador de más bajo nivel se acerca al teclado, ratón y manejode eventos· Alto nivel de abstracción respecto al entorno de ventanas en que se ejecute laaplicación (no hay áreas cliente, ni llamadas a X, ni hWnds, etc.)· La arquitectura de la aplicación es dependiente del entorno de ventanas, envez de tener un tamaño fijo· Es bastante dependiente de la máquina en que se ejecuta la aplicación (nopuede asumir que un diálogo tendrá el mismo tamaño en cada máquina)· Carece de un formato de recursos. No se puede separar el código de lo que espropiamente interface. No hay ningún diseñador de interfaces (todavía)

Componentes y contenedoresUna interface gráfica está construida en base a elementos gráficos básicos, los Componen-tes. Típicos ejemplos de estos Componentes son los botones, barras de desplazamiento,etiquetas, listas, cajas de selección o campos de texto. Los Componentes permiten al usua-rio interactuar con la aplicación y proporcionar información desde el programa al usuario

Tutorial de Java 107

sobre el estado del programa. En el AWT, todos los Componentes de la interface de usuarioson instancias de la clase Component o uno de sus subtipos.

Los Componentes no se encuentran aislados, sino agrupados dentro de Contenedores. LosContenedores contienen y organizan la situación de los Componentes; además, los Conte-nedores son en sí mismos Componentes y como tales pueden ser situados dentro de otrosContenedores. También contienen el código necesario para el control de eventos, cambiarla forma del cursor o modificar el icono de la aplicación. En el AWT, todos los Contenedoresson instancias de la clase Container o uno de sus subtipos.

En la imagen siguiente presentamos una interface de usuario muy simple, con la aparienciaque presenta cuando se visualiza bajo Windows ’95.

Los Componentes deben circunscribirse dentro del Contenedor que los contiene. Esto haceque el anidamiento de Componentes (incluyendo Contenedores) en Contenedores creanárboles de elementos, comenzando con un Contenedor en la raiz del árbol y expandiéndoloen sus ramas. A continuación presentamos el árbol que representa la interface que corres-ponde con la aplicación gráfica generada anteriormente.

108 Tutorial de Java

Tipos de componentesEn el árbol siguiente mostramos la relación que existe entre todas las clases que proporcio-na AWT para la creación de interfaces de usuario, presentando la jerarquía de Clases eInterfaces:

Clases:

· · BorderLayout· · CardLayout· · CheckboxGroup· · Color· · Component

· · Button· · Canvas· · Checkbox· · Choice· Container

· · Panel· · Window

· · Dialog· · Frame

· · Label· · List· · Scrollbar· TextComponent

· · TextArea· · TextField

· · Dimension· · Event· FileDialog· · FlowLayout· · Font· FontMetrics· Graphics· · GridLayout· GridBagConstraints· · GridBagLayout· Image· · Insets· MediaTracker· MenuComponent

· · MenuBar· · MenuItem

· CheckboxMenuItem· Menu

· Point

Tutorial de Java 109

· Polygon· Rectangle· Toolkit

Interfaces:

· · LayoutManager· · MenuContainer

En la figura siguiente reproducimos la ventana generada por el código de la aplicaciónComponentesAWT.java que muestra todos los Componentes que proporciona el AWT. Va-mos a ver en detalle estos Componentes, pero aquí podemos observar ya la estética quepresentan en su conjunto. La ventana es necesaria porque el programa incluye un menú, ylos menús solamente pueden utilizarse en ventanas. El código contiene un método main()para poder ejecutarlo como una aplicación independiente.

ComponentesComponent es una clase abstracta que representa todo lo que tiene una posición, un tama-ño, puede ser pintado en pantalla y puede recibir eventos.

Los Objetos derivados de la clase Component que se incluyen en el Abstract WindowToolkit son los que aparecen a continuación:

· · Button· · Canvas· · Checkbox· · Choice· Container

· · Panel· · Window

· · Dialog· · Frame

· · Label· · List· · Scrollbar· TextComponent

110 Tutorial de Java

· · TextArea· · TextField

Vamos a ver un poco más en detalle los Componentes que nos proporciona el AWT paraincorporar a la creación de la interface con el usuario.

BotonesVeremos ejemplos de cómo se añaden botones a un panel para la interacción del usuariocon la aplicación, pero antes vamos a ver la creación de botones como objetos.

Se pueden crear objetos Button con el operador new :

Button boton;

boton = new Button( «Botón»);

La cadena utilizada en la creación del botón aparecerá en el botón cuando se visualice enpantalla. Esta cadena también se devolverá para utilizarla como identificación del botóncuando ocurra un evento.

Eventos Button

Cada vez que el usuario pulsa un botón, se produce un evento, de la misma forma que seproduce un evento cuando se aprieta el botón del ratón. Los eventos de pulsación de unbotón se pueden capturar sobrecargando el método action():

public boolean action( Event evt,Object obj ) { if( evt.target instanceof Button ) System.out.println( (String)obj ); else System.out.println( «Evento No-Button» ); }

La distinción entre todos los botones existentes se puede hacer utilizando el objeto destinopasado por el objeto Event y comparándolo con los objetos botón que hemos dispuesto ennuestro interface:

import java.awt.*;import java.applet.Applet;

public class Botones extends Applet { Button b1,b2,b3;

public void init() { b1 = new Button( «Botón B1» ); b2 = new Button( «Botón B2» ); b3 = new Button( «Botón B3» );

this.add( b1 ); this.add( b2 ); this.add( b3 );

Tutorial de Java 111

}

public boolean action( Event evt,Object obj ) { if( evt.target.equals( b1 ) ) System.out.println( «Se ha pulsado el boton B1» ); if( evt.target.equals( b2 ) ) System.out.println( «Se ha pulsado el boton B2» ); if( evt.target.equals( b3 ) ) System.out.println( «Se ha pulsado el boton B3» );

return true; } }

En el applet anterior, Botones.java, observamos que se imprime el texto asociado al botónque hayamos pulsado.

Botones de Pulsación

Los botones presentados en el applet son los botones de pulsación estándar; no obstante,para variar la representación en pantalla y para conseguir una interfaz más limpia, AWTofrece a los programadores otros tipos de botones.

Botones de Lista

Los botones de selección en una lista (Choice) permiten el rápido acceso a una lista deelementos. Por ejemplo, podríamos implementar una selección de colores y mantenerla enun botón Choice:

import java.awt.*;import java.applet.Applet;

public class BotonSeleccion extends Applet { Choice Selector;

public void init() { Selector = new Choice();

Selector.addItem( «Rojo» ); Selector.addItem( «Verde» ); Selector.addItem( «Azul» );

112 Tutorial de Java

add( Selector ); }

public boolean action( Event evt,Object obj ) { if( evt.target instanceof Choice ) { String color = (String)obj;

System.out.println( «El color elegido es el « + color ); }

return true; } }

En este ejemplo, BotonSeleccion.java, la cadena proporcionada al método addItem() serádevuelta en el argumento Object de un evento Choice, por ello en el manejador del botón deselección, comprobamos en primer lugar que se trate efectivamente de un evento generadoen un botón de tipo Choice.

Botones de Marcación

Los botones de comprobación (Checkbox) se utilizan frecuentemente como botones de es-tado. Proporcionan información del tipo Sí o No (true o false). El estado del botón se devuel-ve en el argumento Object de los eventos Checkbox; el argumento es de tipo booleano:verdadero (true) si la caja se ha seleccionado y falso (false) en otro caso.

Tanto el nombre como el estado se devuelven en el argumento del evento, aunque se pue-den obtener a través de los métodos getLabel() y getState() del objeto Checkbox.

import java.awt.*;import java.applet.Applet;

public class BotonComprobacion extends Applet { Checkbox Relleno;

public void init() { Relleno = new Checkbox( «Relleno»);

add( Relleno ); }

public boolean action( Event evt,Object obj ) { if( evt.target instanceof Checkbox ) System.out.println( «CheckBox: « + evt.arg.toString() );

Tutorial de Java 113

return true; } }

El sencillo ejemplo anterior, BotonComprobacion.java, muestra los cambios que se produ-cen en el estado del botón cuando la caja está o no seleccionada.

Botones de Selección

Los botones de comprobación se pueden agrupar para formar una interfaz de botón de radio(CheckboxGroup), que son agrupaciones de botones Checkbox en las que siempre hay unúnico botón activo.

import java.awt.*;import java.applet.Applet;

public class BotonRadio extends Applet { CheckboxGroup Radio;

public void init() { Radio = new CheckboxGroup();

add( new Checkbox( «Primero»,Radio,true) ); add( new Checkbox( «Segundo»,Radio,false) ); add( new Checkbox( «Tercero»,Radio,false) ); } }

En el ejemplo anterior, BotonRadio.java, observamos que siempre hay un botón activo entrelos que conforman el interfaz de comprobación que se ha implementado.

Botones Autocontenidos

La naturaleza orientada a objetos de Java nos da la posibilidad de crear botones completa-mente autocontenidos. En este tipo de botón, se construye el manejador de eventos dentrode la propia clase extendida de botón. Se pueden añadir estos botones a la aplicación, sintener que preocuparse de los eventos que pudieran generar.

En el ejemplo siguiente, BotonAuto.java, creamos el botón que muestra la figura, un botónque genera el texto «Boton Aceptar» por la salida estándar:

114 Tutorial de Java

import java.awt.*;import java.applet.Applet;

class BotonAceptar extends Button {

public BotonAceptar() { setLabel( «Aceptar» ); }

public boolean action( Event evt,Object obj ) { System.out.println( «Boton Aceptar» ); return true; } }

public class BotonAuto extends Applet { BotonAceptar boton;

public void init() { boton = new BotonAceptar(); add( boton ); } }

Es de hacer notar que no hay un método action() en la clase applet BotonAuto , la claseBotonAceptar manejará sus eventos. Si se hubiesen colocado otros objetos en el applet, sepodría haber usado un método action() para tratar los eventos de esos objetos.

EtiquetasLas etiquetas (Label) proporcionan una forma de colocar texto estático en un panel, paramostrar información que no varía, normalmente, al usuario.

El applet Etiqueta.java presenta dos textos en pantalla, tal como aparece en la figura si-guiente:

Tutorial de Java 115

import java.awt.*;import java.applet.Applet;

public class Etiqueta extends Applet {

public void init() { Label etiq1 = new Label( «Hola Java!» ); Label etiq2 = new Label( «Otra Etiqueta» );

setLayout( new FlowLayout( FlowLayout.CENTER,10,10) ); add( etiq1 ); add( etiq2 ); } }

ListasLas listas (List) aparecen en los interfaces de usuario para facilitar a los operadores lamanipulación de muchos elementos. Se crean utilizando métodos similares a los de losbotones Choice. La lista es visible todo el tiempo, utilizándose una barra de desplazamientopara visualizar los elementos que no caben en el área que aparece en la pantalla.

El ejemplo siguiente, Lista.java, crea una lista que muestra cuatro líneas a la vez y no permi-te selección múltiple.

import java.awt.*;import java.applet.Applet;

public class Lista extends Applet {

public void init() { List l = new List( 4,false );

116 Tutorial de Java

l.addItem( «Mercurio» ); l.addItem( «Venus» ); l.addItem( «Tierra» ); l.addItem( «Marte» ); l.addItem( «Jupiter» ); l.addItem( «Saturno» ); l.addItem( «Neptuno» ); l.addItem( «Urano» ); l.addItem( «Pluton» ); add( l ); }

public boolean action( Event evt,Object obj ) { if( evt.target instanceof List ) System.out.println( «Entrada de la Lista: « + obj );

return true; } }

Para acceder a los elementos seleccionados se utilizan los métodos getSelectedItem() ogetSelectedItems(). Para listas de selección simple, cualquier selección con doble-click enla lista disparará el método action() de la misma forma que con los eventos de selección enmenús.

En el applet siguiente, ListaMult.java, se permite al usuario seleccionar varios elementos delos que constituyen la lista. En la figura se muestra la apariencia de una selección múltipleen este applet.

import java.awt.*;import java.applet.Applet;

public class ListaMult extends Applet { List lm = new List( 6,true );

public void init() { Button boton = new Button( «Aceptar» );

lm.addItem( «Mercurio» ); lm.addItem( «Venus» ); lm.addItem( «Tierra» );

Tutorial de Java 117

lm.addItem( «Marte» ); lm.addItem( «Jupiter» ); lm.addItem( «Saturno» ); lm.addItem( «Neptuno» ); lm.addItem( «Urano» ); lm.addItem( «Pluton» ); add( lm ); add( boton ); }

public boolean action( Event evt,Object obj ) { if( evt.target instanceof Button ) { if( «Aceptar».equals( obj ) ) { String seleccion[];

seleccion = lm.getSelectedItems(); for( int i=0; i < seleccion.length; i++ ) System.out.println( seleccion[i] ); } }

return true; } }

En este caso de la selección múltiple en una lista, utilizamos un evento externo para dispa-rar las acciones asociadas a la lista. En el ejemplo, hemos incluido un botón para generar elevento que hace que el applet recoja los elementos que hay seleccionados en la lista.

Campos de textoPara la entrada directa de datos se suelen utilizar los campos de texto, que aparecen enpantalla como pequeñas cajas que permiten al usuario la entrada por teclado.

Los campos de texto (TextField) se pueden crear vacíos, vacíos con una longitud determina-da, rellenos con texto predefinido y rellenos con texto predefinido y una longitud determina-da. El applet siguiente, CampoTexto.java, genera cuatro campos de texto con cada una delas características anteriores. La imagen muestra los distintos tipos de campos.

118 Tutorial de Java

import java.awt.*;import java.applet.Applet;

public class CampoTexto extends Applet { TextField tf1,tf2,tf3,tf4;

public void init() { // Campo de texto vacío tf1 = new TextField(); // Campo de texto vacío con 20 columnas tf2 = new TextField( 20 ); // Texto predefinido tf3 = new TextField( «Hola» ); // Texto predefinido en 30 columnas tf4 = new TextField( «Hola»,30 ); add( tf1 ); add( tf2 ); add( tf3 ); add( tf4 ); }

public boolean action( Event evt,Object obj ) { if( evt.target instanceof TextField ) { if( evt.target.equals( tf1 ) ) System.out.println( «Campo de Texto 1: « + evt.arg.toString() ); if( evt.target.equals( tf1 ) ) System.out.println( «Campo de Texto 2: « + evt.arg.toString() ); if( evt.target.equals( tf1 ) ) System.out.println( «Campo de Texto 3: « + evt.arg.toString() ); if( evt.target.equals( tf1 ) ) System.out.println( «Campo de Texto 4: « + evt.arg.toString() ); } return true; } }

Cuando el usuario teclea un retorno de carro en un campo de texto, se genera un evento

Tutorial de Java 119

TextField, que al igual que con los otros Componentes del AWT podemos capturar con elmétodo action(), tal como se demuestra en el ejemplo.

Areas de textoJava, a través del AWT, permite incorporar texto multilínea dentro de zonas de texto (TextArea).Los objetos TextArea se utilizan para elementos de texto que ocupan más de una línea,como puede ser la presentación tanto de texto editable como de sólo lectura.

Para crear una área de texto se pueden utilizar cuatro formas análogas a las que se handescrito en la creación de Campos de Texto. Pero además, para las áreas de texto hay queespecificar el número de columnas.

Se puede permitir que el usuario edite el texto con el método setEditable() de la misma formaque se hacía en el TextField. En la figura aparece la representación del applet AreaTexto.java,que presenta dos áreas de texto, una vacía y editable y otra con un texto predefinido y noeditable.

import java.awt.*;import java.applet.Applet;

public class AreaTexto extends Applet { TextArea t1,t2;

120 Tutorial de Java

public void init() { Button boton = new Button( «Aceptar» );

t1 = new TextArea(); t2 = new TextArea( «Tutorial de Java»,5,40 ); t2.setEditable( false );

add( t1 ); add( t2 ); add( boton ); }

public boolean action( Event evt,Object obj ) { if( evt.target instanceof Button ) { if( «Aceptar».equals( obj ) ) { String texto = t1.getText();

System.out.println( texto ); } }

return true; } }

Para acceder al texto actual de una zona de texto se utiliza el método getText(), tal comomuestra el código anterior. Las áreas de texto no generan eventos por sí solas, por lo quehay que utilizar eventos externos, para saber cuando tenemos que acceder a la informacióncontenida en el TextArea. En este caso hemos utilizado un botón que generará un evento alpulsarlo que hará que se recoja el texto que haya escrito en el área de texto que constituyeel applet.

CanvasSi tenemos un applet que trabaja con imágenes directamente, ya sea un applet gráfico o dedibujo, los lienzos o zonas de dibujo (Canvas) resultan muy útiles.

Los Canvas son un Componente básico que captura eventos de exposición (expose), deratón y demás eventos relacionados. La clase base Canvas no responde a estos eventos,pero se puede extender esa clase base creando subclases en las que controlemos eseoseventos.

Al permitir saltarse el manejo normal de eventos, y junto con los métodos de representacióngráfica, los canvas simplifican la producción de applets que necesitan una única funcionalidadpara distintas áreas. Por ejemplo, el applet Lienzo.java, contiene un manejador de eventosque controla el evento mouseDown en el canvas. Si el evento no se genera en el canvas, sele pasa al applet que lo tratará como un evento de ratón normal.

import java.awt.*;

Tutorial de Java 121

import java.applet.Applet;

public class Lienzo extends Applet { Button boton;

public void init() { setLayout( new BorderLayout( 15,15 ) ); boton = new Button( «Test» ); MiCanvas canv = new MiCanvas( 100,100 );

add( «Center»,canv ); add( «South»,boton ); }

public boolean action( Event evt,Object obj ) { System.out.println( «Evento: « + obj ); return true; }

public boolean mouseDown( Event evt,int x, int y ) { System.out.println( «Raton: («+x+»,»+y+»)» ); return true; } }

class MiCanvas extends Canvas { private int ancho; private int alto;

public MiCanvas( int anc,int alt ) { ancho = anc; alto = alt;

reshape( 0,0,anc,alt ); }

public void paint( Graphics g ) { g.setColor( Color.blue ); g.fillRect( 0,0,ancho,alto ); }

public boolean mouseDown( Event evt,int x, int y ) { if( x < ancho && y < alto ) { System.out.println( «Raton en Canvas: («+x+»,»+y+»)» ); return true; } return false; } }

122 Tutorial de Java

Barras de desplazamientoEn determinados applets que necesiten realizar el ajuste de valores lineales en pantalla,resulta útil el uso de barras de desplazamiento (Scrollbar). Las barras de desplazamientoproporcionan una forma de trabajar con rangos de valores o de áreas como el ComponenteTextArea, que proporciona dos barras de desplazamiento automáticamente.

Si queremos implementar un selector de color, como en el applet Slider.java, podemos utili-zar una barra de desplazamiento para cada uno de los colores primarios.

import java.awt.*;import java.applet.Applet;

public class Slider extends Applet { Scrollbar rojo,verde,azul;

public void init() { rojo = new Scrollbar( Scrollbar.VERTICAL,0,1,0,255 ); verde = new Scrollbar( Scrollbar.VERTICAL,0,1,0,255 ); azul = new Scrollbar( Scrollbar.VERTICAL,0,1,0,255 );

add( rojo ); add( verde ); add( azul ); } }

Este tipo de interfaz proporciona al usuario un punto de referencia visual de un rango y almismo tiempo la forma de cambiar los valores. Por ello, las barras de desplazamiento sonComponentes un poco más complejos que los demás, reflejándose esta complejidad en susconstructores. Al crearlos hay que indicar su orientación, su valor inicial, los valores mínimoy máximo que puede alcanzar y el porcentaje de rango que estará visible.

También podríamos utilizar una barra de desplazamiento para un rango de valores de color,tal como se muestra en el ejemplo Ranger.java.

import java.awt.*;import java.applet.Applet;

public class Ranger extends Applet { Scrollbar rango;

public void init() { rango = new Scrollbar( Scrollbar.HORIZONTAL,0,64,0,255 );

Tutorial de Java 123

add( rango ); } }

Como se puede ver, el ancho de la barra es mayor, en relación al Scrollbar. En este caso,maxValue representa el valor máximo para el lado izquierdo de la barra. Si se quieren repre-sentar 64 colores simultáneamente, es decir [0-63] a [192-255], maxValue debería ser 192.

Igual que otros Componentes, las barras de desplazamiento generan eventos; pero al con-trario que en el resto, se tiene que utilizar el método handleEvent() directamente, en lugardel método action(). El destino del evento será un objeto de la clase Scrollbar, a partir deéste se obtiene la posición de la barra de desplazamiento.

Como se habrá podido observar en los applets anteriores, las barras de desplazamiento nodisponen de un display o zona donde se muestren directamente los valores asociados a losdesplazamientos. Al contrario, si se desea eso, es necesario añadir explícitamente una cajade texto, tal como se muestra en el ejemplo RangoRojo.java.

import java.awt.*;import java.applet.Applet;

public class RangoRojo extends Applet { Scrollbar rango; TextField valor; Label etiqueta;

public void init() { rango = new Scrollbar( Scrollbar.HORIZONTAL,0,1,0,255 ); valor = new TextField( «0»,5 ); etiqueta = new Label( «Rojo (0-255)» );

setLayout( new GridLayout( 1,3 ) ); valor.setEditable( false );

add( etiqueta ); add( rango ); add( valor ); }

public boolean handleEvent( Event evt ) { if( evt.target instanceof Scrollbar ) { valor.setText( Integer.toString( ((Scrollbar)evt.target).getValue() ) ); return true; }

124 Tutorial de Java

return super.handleEvent( evt ); } }

Ese era el código del applet que construye la ventana de la figura y actualiza el campo detexto asociado. No implementa ninguna otra acción o evento.

Diseño de componentes propiosTambién podemos atrevernos en el diseño de Componentes propios. Deberán ser unasubclase de Canvas o Panel. Para mostrarlos en pantalla deberemos sobreescribir los mé-todos paint(), update(), minimumSize() y preferredSize() y para controlar los eventos tendre-mos que sobreescribir el método handleEvent().

Para que podamos reusarlos, tenemos que poner cuidado en el diseño, implementandométodos get y set, lanzando excepciones cuando proceda y permitiendo el acceso a loscampos de que disponga nuestro Componente.

Veamos un ejemplo, Separador.java, en que a partir de la clase Canvas , creamos un separadorque será una línea vertical u horizontal simulando tres dimensiones.

import java.awt.*;

public class Separador extends Canvas { public final static int HORIZONTAL = 0; public final static int VERTICAL = 1;

int orientacion; Dimension sepTama,sepDim;

public Separador( int lon,int thick,int orient ) { orientacion = orient; if( orient == HORIZONTAL ) sepTama = new Dimension( lon,thick ); else sepTama = new Dimension( thick,lon ); }

public int getOrientacion() { return orientacion; }

public void setOrientacion( int orient) { if( orient > VERTICAL || orient < HORIZONTAL ) throw new IllegalArgumentException( «Orientación ilegal» );

if( orientacion != orient ) { orientacion = orient; sepDim = new Dimension( sepDim.height,sepDim.width ); invalidate(); } }

public Dimension preferredSize() {

Tutorial de Java 125

return sepDim; }

public Dimension minimumSize() { return sepDim; }

public void paint( Graphics g ) { int x1,y1,x2,y2; Rectangle bbox = bounds(); Color c = getBackground(); Color brillo = c.brighter(); Color oscuro = c.darker();

if( orientacion == HORIZONTAL ) { x1 = 0; x2 = bbox.width - 1; y1 = y2 = bbox.height/2 - 1; } else { x1 = x2 = bbox.width/2 - 1; y1 = 0; y2 = bbox.height - 1; }

g.setColor( oscuro ); g.drawLine( x1,y1,x2,y2 );

g.setColor( brillo ); if( orientacion == HORIZONTAL ) g.drawLine( x1,y1+1,x2,y2+1 ); else g.drawLine( x1+1,y1,x2+1,y2 ); } }

El código que mostramos a continuación EjSeparador.java, muestra un ejemplo de uso denuestro separador recién creado:

import java.awt.*;import java.applet.Applet;

public class EjSeparador extends Applet { public final static int HORIZONTAL = 0; public final static int VERTICAL = 1;

public void init() { setLayout( new BorderLayout() );

TextField texto1 = new TextField( «Hola»,20 ); TextField texto2 = new TextField( «que tal»,20 ); Separador raya = new Separador( 8,2,HORIZONTAL );

add( «North»,texto1 ); add( «Center»,raya );

126 Tutorial de Java

add( «South»,texto2 ); } }

Y ahora podemos complicar nuestro Componente, o utilizarlo como base para el desarrollode otros más complejos. Por ejemplo, vamos a implementar un Componente de Grupo , queextenderemos de la clase Panel y que la utilizaremos para enmarcar a otros Componentes,tal como se utiliza en otros entornos de ventanas. En el código fuente de la implementacióndel Componente, Grupo.java, podemos observar que las acciones seguidas son exacta-mente las mismas que en el caso anterior: sobreescribir los métodos que necesitamos ypunto.

import java.awt.*;

public class Grupo extends Panel { String Rotulo; int altoTexto; int longTexto; int offset; static Font defFuente; FontMetrics metFuente;

public Grupo( String titulo ) { altoTexto = 20; longTexto = 20; offset = 10; Rotulo = titulo; setLayout( null ); metFuente = getFontMetrics( defFuente ); }

public void paint( Graphics g ) { Dimension d = size(); Font fuente = getFont(); if( fuente != null ) metFuente = getFontMetrics( fuente );

longTexto = metFuente.stringWidth( Rotulo ); altoTexto = metFuente.getHeight(); g.setColor( Color.gray ); g.drawRect( 0, altoTexto/2,d.width-3,d.height-altoTexto/2-3 ); g.setColor( Color.white ); g.drawRect( 1,altoTexto/2+1,d.width-3,d.height-altoTexto/2-3 ); g.setColor( getBackground() ); g.drawLine( offset,altoTexto/2,offset+longTexto+12,altoTexto/2 ); g.drawLine( offset,altoTexto/2+1,offset+longTexto+12,altoTexto/2+1 ); g.setColor( getForeground() ); g.drawString( Rotulo,offset+6,altoTexto-3 ); }

public boolean handleEvent( Event evt ) { return( super.handleEvent( evt ) ); }

public Dimension preferredSize() { return( minimumSize() );

Tutorial de Java 127

}

public Dimension minimumSize() { return( new Dimension( 100,100 ) ); }

static { defFuente = new Font( «Dialog»,0,12 ); } }

Un ejemplo de uso, lo podremos observar compilando y ejecutando el código que se puestraa continuación, EjGrupo.java:

import java.awt.*;import java.applet.Applet

public class EjGrupo extends Applet { Label etiq; Button izqda; Button dcha; Button todo;

public void init() { setBackground( Color.lightGray ); setLayout( null );

Grupo g = new Grupo( «Etiqueta» ); add( g ); g.setFont( new Font(«Dialog»,0,14 ) ); g.reshape( insets().left + 10,insets().top + 20,290,40 ); g.setLayout( new FlowLayout( FlowLayout.CENTER,10,10 ) ); etiq = new Label( «Hola Java!» ); g.add( etiq );

Grupo g2 = new Grupo( «Botones» ); add( g2 ); g2.reshape( insets().left+10,80,290,60 ); izqda = new Button( «Bot•n 1» ); g2.add( izqda ); izqda.reshape( 20,20,70,30 ); dcha = new Button( «Bot•n 2» ); g2.add( dcha ); dcha.reshape( 110,20,70,30 ); todo = new Button( «Bot•n 3» ); g2.add( todo ); todo.reshape( 200,20,70,30 ); } }

ContenedoresContainer es una clase abstracta derivada de Component , que representa a cualquiercomponente que pueda contener otros componentes. Se trata, en esencia, de añadir a la

128 Tutorial de Java

clase Component la funcionalidad de adición, sustracción, recuperación, control y organi-zación de otros componentes.

El AWT proporciona cuatro clases de Contenedores:

· · Window· · Frame· · Dialog· · Panel

Además de estos Contenedores, la clase Applet también es un Contenedor, es un subtipode la clase Panel y puede tener Componentes.

Window

Es una superficie de pantalla de alto nivel (una ventana). Una instancia de la clase Windowno puede estar enlazada o embebida en otro Contenedor. Una instancia de esta clase notiene ni título ni borde.

Frame

Es una superficie de pantalla de alto nivel (una ventana) con borde y título. Una instancia dela clase Frame puede tener una barra de menú. Una instancia de esta clase es mucho másaparente y más semejante a lo que nosotros entendemos por ventana.

Dialog

Es una superficie de pantalla de alto nivel (una ventana) con borde y título. Una instancia dela clase Dialog no puede existir sin una instancia asociada de la clase Frame .

Panel

Es un Contenedor genérico de Componentes. Una instancia de la clase Panel , simplementeproporciona un Contenedor al que ir añadiendo Componentes.

Crear un Contenedor

Antes de poder incorporar Componentes a la interface de usuario que se desea implementar,el programador debe crear un Contenedor. Cuando se construye una aplicación, el progra-mador debe crear en primer lugar una instancia de la clase Window o de la clase Frame .Cuando lo que se construye es un applet, ya existe un Frame (la ventana del navegador).Debido a que la clase Applet está derivada de la clase Panel , el programador puede ir

Tutorial de Java 129

añadiendo Componentes directamente a la instancia que se crea de la clase Applet .

En el siguiente ejemplo se crea un Frame vacío. El título del Frame, que corresponderá altítulo de la ventana, se fija en la llamada al constructor. Un Frame inicialmente está invisible,para poder verlo es necesario invocar al método show():

import java.awt.*;

public class Ejemplo1 { public static void main( String args[] ) { Frame f = new Frame( «Ejemplo 1» );

f.show(); } }

En el código de ejemplo que sigue, extendemos el código anterior para que la nueva clasesea una subclase de la clase Panel . En el método main() de esta nueva clase se crea unainstancia de ella y se le incorpora un objeto Frame llamando al método add(). El resultado deambos ejemplos es idéntico a efectos de apariencia en pantalla:

import java.awt.*;

public class Ejemplo2 extends Panel { public static void main( String args[] ) { Frame f = new Frame( «Ejemplo 2» ); Ejemplo2 ej = new Ejemplo2();

f.add( «Center»,ej ); f.pack(); f.show(); } }

Derivando la nueva clase directamente de la clase Applet en vez de Panel, nuestro ejemplopuede ahora ejecutarse tanto como una aplicación solitaria como dentro de una páginaHTML en un navegador. El código siguiente muestra esta circunstancia:

import java.awt.*;

public class Ejemplo3 extends java.applet.Applet { public static void main( String args[] ) { Frame f = new Frame( «Ejemplo 3» ); Ejemplo3 ej = new Ejemplo3();

f.add( «Center»,ej ); f.pack(); f.show(); } }

Un objeto Window, y en algunos casos incluso un objeto Dialog, pueden reemplazar alobjeto Frame. Son Contenedores válidos y los Componentes se añaden en ellos del mismomodo que se haría sobre un Frame.

130 Tutorial de Java

Añadir Componentes a un Contenedor

Para que la interface sea útil, no debe estar compuesta solamente por Contenedores, éstosdeben tener Componentes en su interior. Los Componentes se añaden al Contenedor invo-cando al método add() del Contenedor. Este método tiene tres formas de llamada que de-penden del manejador de composición o layout manager que se vaya a utilizar sobre elContenedor.

En el código siguiente, incorporamos dos botones al código del último ejemplo. La creaciónse realiza en el método init() porque éste siempre es llamado automáticamente al inicializarseel applet. De todos modos, al inciarse la ejecución se crean los botones, ya que el métodoinit() es llamado tanto por el navegador como por el método main():

import java.awt.*;

public class Ejemplo4 extends java.applet.Applet { public void init() { add( new Button( «Uno» ) ); add( new Button( «Dos» ) ); }

public static void main( String args[] ) { Frame f = new Frame( «Ejemplo 4» ); Ejemplo4 ej = new Ejemplo4();

ej.init();

f.add( «Center»,ej ); f.pack(); f.show(); } }

Creación de aplicaciones con AWTPara crear una aplicación utilizando AWT, vamos a ver en principio cómo podemos generarel interface de esa aplicación, mostrando los distintos elementos del AWT, posteriormentevolveremos hacia la implementación de la aplicación.

Interface

· · Crear el Marco de la aplicación (Frame)· · Inicializar Fuentes, Colores, Layouts y demás recursos· · Crear menús y Barras de Menús· · Crear los controles, diálogos, ventanas, etc.

Implementación

· Incorporar controladores de eventos· Incorporar funcionalidad (threads, red, etc.)· Incorporar un controlador de errores (excepciones)

Tutorial de Java 131

Crear el marco de la aplicaciónEl Contenedor de los Componentes es el Frame . También es la ventana principal de laaplicación, lo que hace que para cambiar el icono o el cursor de la aplicación no sea nece-sario crear métodos nativos; al ser la ventana un Frame, ya contiene el entorno básico parala funcionalidad de la ventana principal.

Vamos a empezar a crear una aplicación básica, a la que iremos incorporando Componen-tes. Quizás vayamos un poco de prisa en las explicaciones que siguen; no preocuparse, yaque lo que no quede claro ahora, lo estará más tarde. El problema es que para poder profun-dizar sobre algunos aspectos de Java, necesitamos conocer otros previos, así que propor-cionaremos un ligero conocimiento sobre algunas características de Java y del AWT, paraque nos permitan entrar a fondo en otras; y ya conociendo estas últimas, volveremos sobrelas primeras. Un poco lioso, pero imprescindible.

En el archivo AplicacionAWT.java, se encuentra el código completo de la aplicación quevamos ir construyendo a lo largo de este repaso general por las características de quedispone el AWT.

Comenzaremos el desarrollo de nuestra aplicación básica con AWT a partir del código quemostramos a continuación:

import java.awt.*;

public class AplicacionAWT extends Frame { static final int HOR_TAMANO = 300; static final int VER_TAMANO = 200;

public AplicacionAWT() { super( «Aplicación Java con AWT» );

pack(); resize( HOR_TAMANO,VER_TAMANO ); show(); }

public static void main( String args[] ) { new AplicacionAWT(); } }

La clase anterior es un Frame , ya que extiende esa clase y hereda todas sus característi-cas. Tiene un método, el constructor, que no admite parámetros.

Además, se hace una llamada explícita al constructor super, es decir, al constructor de laclase padre, para pasarle como parámetro la cadena que figurará como el título de la venta-na.

La llamada a show() es necesaria, ya que por defecto, los Contenedores del AWT se creanocultos y hay que mostrarlos explícitamente. La llamada a pack() hace que los Componen-tes se ajusten a sus tamaños correctos en función del Contenedor en que se encuentren

132 Tutorial de Java

situados.

La ejecución de la aplicación mostrará la siguiente ventana en pantalla:

Los atributos fundamentales de la ventana anterior son:

· Marco de 300x200 pixels· No tiene barra de menú· No tiene ningún Componente· Título «Aplicación Java con AWT»· Color de fondo por defecto· Layout por defecto· Fondo de la ventana vacío

Inicializar fuentes, colores y recursosVamos a ir alterando los recursos de la ventana de la aplicación Java que estamos desarro-llando con el AWT, para ir incorporando y visualizando los distintos Componentes que pro-porciona AWT. Insertemos algunas líneas de código en el constructor para inicializar laaplicación:

· Cambiemos el font de caracteres a Times Roman de 12 puntos setFont( new Font( «TimesRoman»,Font.PLAIN,12 ) );

· Fijemos los colores de la ventana para que el fondo sea Blanco y el textoresalte en Negro

setBackground( Color.white ); setForeground( Color.black );

· Seleccionemos como disposición de los Componentes el BorderLayout paraeste Contenedor

setLayout( new BorderLayout() );· Incorporemos gráficos. Usamos métodos definidos en la clase Graphics; porejemplo, reproduzcamos el título de la ventana en medio con una fuente Time Romande 24 puntos en color Azul. Es necesario utilizar new con Font ya que en Java, todoson objetos y no podríamos utilizar un nuevo font de caracteres sin antes haberlo

Tutorial de Java 133

creado public void paint( Graphics g ) { g.setFont( new Font( «TimesRoman»,Font.BOLD,24 ) ); g.setColor( Color.blue ); g.drawString( getTitle(),30,50 ); }

· Incorporemos en la parte inferior de la ventana dos botones: Aceptar y Cance-lar

Panel p = new Panel(); p.add( new Button( «Aceptar» ) ); p.add( new Button( «Cancelar» ) ); add( «South»,p );

Los Componentes se incorporan al Contenedor a través de los dos métodos add() que haydefinidos:

add( Component c ); add( String s,Component c );

Los Componentes también se podían haber insertado en el Frame, organizándolos en unacierta forma, teniendo en cuenta que su manejador de composición es un BorderLayout. Porejemplo:

add( «South»,new Button( «Aceptar ) ); add( «South»,new Button( «Cancelar» ) );

Hemos utilizado un Panel y no el segundo método, porque es más útil el organizar losComponentes en pequeñas secciones. Así, con nuestro código podemos considerar al Pa-nel como una entidad separada del Frame, lo cual permitiría cambiar el fondo, layout, fuen-te, etc. del Panel sin necesidad de tocar el Frame.

Si ejecutamos de nuevo la aplicación con los cambios que hemos introducido, apareceráante nosotros la ventana que se muestra a continuación.

Si intentásemos en esta aplicación cerrar la ventana, no sucede nada. Cuando se intentacerrar la ventana, el sistema envía un evento que no se está tratando. Incorporemos pues uncontrolador de eventos y empecemos tratando el evento WINDOW_DESTROY, generadocuando se intenta cerrar la ventana:

public boolean handleEvent( Event evt ) { switch( evt.id ) {

134 Tutorial de Java

case Event.WINDOW_DESTROY: { System.exit( 0 ); return true; } default: return false; } }

Si ahora ejecutamos de nuevo la aplicación y cerramos la ventana... Efectivamente se cie-rra, tal como se suponía.

Crear menus y barra de menusEn la actual versión del AWT que se proporciona con el JDK, sólo se permite crear menús através de código, ya que Java todavía no dispone de un formato de recursos y tampoco hayun diseñador como pueden ser AppStudio, Delphi o X-Designer; aunque terminará habiendouno, con seguridad.

No hay ningún método para diseñar una buena interface, todo depende del programador.Los menús son el centro de la aplicación. La diferencia entre una aplicación útil y otra que estotalmente frustrante radica en la organización de los menús, pero eso, las reglas del diseñode un buen árbol de menús, no están claras. Hay un montón de libros acerca de la ergonomíay de cómo se debe implementar la interacción con el usuario. Lo cierto es que por cada unoque defienda una idea, seguro que hay otro que defiende la contraria. Todavía no hay unacuerdo para crear un estándar, con cada Window Manager se publica una guía de estilodiferente. Así que, vamos a explicar lo básico, sin que se deba tomar como dogma de fe,para que luego cada uno haga lo que mejor le parezca.

La interface MenuContainer solamente se puede implementar sobre un Frame. Un appletque desee tener un menú, debe crear un Frame en primer lugar. El código de la función quevamos a ver, crea una barra de menús y se llama desde el constructor del Frame. La funciónes private porque no queremos que se pueda llamar desde ninguna otra clase.

private void InicializaMenus() { mbarra = new MenuBar(); Menu m = new Menu( «Archivo» ); m.add( new MenuItem( «Nuevo») ); m.add( new MenuItem( «Abrir») ); m.add( new MenuItem( «Guardar») ); m.add( new MenuItem( «Guardar como») ); m.add( new MenuItem( «Imprimir») ); m.addSeparator(); m.add( new MenuItem( «Salir») ); mbarra.add( m );

m = new Menu( «Ayuda» ); m.add( new MenuItem( «Ayuda!» ) ); m.addSeparator(); m.add( new MenuItem( «Acerca de...» ) );

Tutorial de Java 135

mbarra.add( m );

setMenuBar( mbarra ); }

El menú que crea esta función tendrá la apariencia que muestra la figura siguiente:

Los eventos generados por las opciones de un menú se manejan del mismo modo que losBotones, Listas, etc. En el caso de menús, es el evento ACTION_EVENT de la clasejava.awt.MenuItem el que se genera y en evt.target se nos indica la opción seleccionada.

case Event.ACTION_EVENT: { if( evt.target instanceof MenuItem ) { if( «Nuevo».equals( evt.arg ) ) AplicacionAWT aAwt = new AplicacionAWT();; if( «Abrir».equals( evt.arg ) ) System.out.println( «Opcion -Abrir-» ); if( «Guardar».equals( evt.arg ) ) System.out.println( «Opcion -Guardar-» ); if( «Guardar como».equals( evt.arg ) ) System.out.println( «Opcion -Guardar como-» ); if( «Imprimir».equals( evt.arg ) ) System.out.println( «Opcion -Imprimir-» ); if( «Salir».equals( evt.arg ) ) System.exit( 0 ); if( «Ayuda!».equals( evt.arg ) ) System.out.println( «No hay ayuda» ); if( «Acerca de».equals( evt.arg ) ) System.out.println( «Opcion -Acerca de-» ); } }

En el código anterior hemos tratado los eventos del menú. Para más seguridad, aunque nosea estrictamente necesario, lo primero que hacemos es asegurarnos de que el objetoevt.target es realmente un objeto MenuItem, es decir, procede de la barra de menús; ydespués comprobamos la opción que se ha seleccionado.

Como todo, también se puede rizar el rizo y conseguir reproducir los sistemas de menús queestamos acostumbrados a ver en las aplicaciones que manejamos habitualmente. Un ejem-plo de ello son los menús en cascada, semejantes al que muestra la figura y que ha sidogenerado mediante la aplicación Cascada.java.

136 Tutorial de Java

Básicamente se utiliza la técnica ya descrita, pero en vez de crear un nuevo MenuItem secrea un nuevo Menu, lo que origina el menú en cascada.

No obstante, y volviendo al diseño de interfaces, no debe abusarse de este tipo de menús,porque pueden crear mucha más confusión al usuario. Siempre se debe tener en mente quelos usuarios tienen que navegar habitualmente por una gran cantidad de menús en las másdiversas aplicaciones, por lo que no debemos esconderles demasiado las opciones que lespueden interesar.

Dialogos y VentanasUna Ventana genérica, Window, puede utilizarse simplemente para que sea la clase padrede otras clases y se puede intercambiar por un Diálogo, Dialog, sin pérdida de funcionalidad.No se puede decir lo mismo de un Frame.

Se podría crear un menú pop-up con una Ventana, pero lo cierto es que en esta versión delJDK hay un montón de bugs y no merece la pena el enfrascarse en el intento. No obstante,hay ciertos métodos que están en la clase Window y que no están presentes en otras clasesque pueden resultar interesantes y necesitar una Ventana si queremos emplearlos. Son:

· getToolkit()· getWarningString()· pack()· toBack()· toFront()

Un Diálogo es una subclase de Window, que puede tener un borde y ser modal, es decir, nopermite hacer nada al usuario hasta que responda al diálogo. Esto es lo que se usa en lascajas de diálogo «Acerca de...», en la selección en listas, cuando se pide una entrada numé-rica, etc.

El código Java que se expone a continuación, implementa el diálogo Acerca de para laaplicación. Esta clase se crea oculta y necesitaremos llamar al método show() de la propiaclase para hacerla visible.

class AboutDialog extends Dialog { static int HOR_TAMANO = 300; static int VER_TAMANO = 150;

Tutorial de Java 137

public AboutDialog( Frame parent ) { super( parent,»Acerca de...»,true ); this.setResizable( false ); setBackground( Color.gray ); setLayout( new BorderLayout() );

Panel p = new Panel(); p.add( new Button( «Aceptar» ) ); add( «South»,p ); resize( HOR_TAMANO,VER_TAMANO ); }

public void paint( Graphics g ) { g.setColor( Color.white ); g.drawString( «Aplicación Java con AWT», HOR_TAMANO/4,VER_TAMANO/3 ); g.drawString( «Versión 1.00», HOR_TAMANO/3+15,VER_TAMANO/3+20 ); }

public boolean handleEvent( Event evt ) { switch( evt.id ) { case Event.ACTION_EVENT: { if( «Aceptar».equals( evt.arg ) ) { hide(); return true; } } default: return false; } } }

La ventana que aparece en pantalla generada por la clase anterior es la que muestra lafigura:

Las aplicaciones independientes deberían heredar tomando como padre la ventana princi-pal de esa aplicación. Así pueden implementar la interface MenuContainer y proporcionarmenús.

138 Tutorial de Java

No hay razón aparente para que sea una subclase de la clase Frame , pero si se quiereproporcionar funcionalidad extra, sí debería serlo, en vez de serlo de su padre: Window.Esto es así porque Frame implementa la interface MenuContainer, con lo cual tiene la posi-bilidad de proporcionar menús y cambiar el cursor, el icono de la aplicación, etc.

Un ejemplo más complicado de aplicación gráfica basada en el AWT es el convertidor dedecimal a binario/octal/hexadecimal/base36, Convertidor.java, cuya presentación en panta-lla es la que muestra la figura siguiente.

En la construcción de esta nueva aplicación se utilizan elementos que se presentan enprofundidad en secciones posteriores de este Tutorial.

PanelesLa clase Panel es el más simple de los Contenedores de Componentes gráficos. En reali-dad, se trataba de crear una clase no-abstracta (Container sí lo es) que sirviera de base alos applet y a otras pequeñas aplicaciones. La clase Panel consta de dos métodos propios:el constructor, cuyo fin es crear un nuevo Panel con un LayoutManager de tipo FlowLayout(el de defecto), y el método addNotify() que, sobrecargando la función del mismo nombre enla clase Container , llama al método createPanel() del Toolkit adecuado, creando así unPanelPeer . El AWT enviará así al Panel (y por tanto al applet) todos los eventos que sobreél ocurran. Esto que puede parecer un poco rebuscado, obedece al esquema arquitectónicodel AWT; se trata del bien conocido esquema de separación interface/implementación queestablece por un lado una clase de interface y por otro distintas clases de implementaciónpara cada una de las plataformas elegidas.

El uso de Paneles permite que las aplicaciones puedan utilizar múltiples layouts, es decir,que la disposición de los componentes sobre la ventana de visualización pueda modificarsecon mucha flexibilidad. Permite que cada Contenedor pueda tener su propio esquema defuentes de caracteres, color de fondo, zona de diálogo, etc.

Podemos, por ejemplo, crear una barra de herramientas para la zona superior de la ventanade la aplicación o incorporarle una zona de estado en la zona inferior de la ventana paramostrar información útil al usuario. Para ello vamos a implementar dos Paneles:

class BarraHerram extends Panel {

Tutorial de Java 139

public BarraHerram() { setLayout( new FlowLayout() ); add( new Button( «Abrir» ) ); add( new Button( «Guardar» ) ); add( new Button( «Cerrar» ) );

Choice c = new Choice(); c.addItem( «Times Roman» ); c.addItem( «Helvetica» ); c.addItem( «System» ); add( c ); add( new Button( «Ayuda» ) ); } }

class BarraEstado extends Panel { Label texto; Label mas_texto;

public BarraEstado() { setLayout( new FlowLayout() ); add( texto = new Label( «Creada la barra de estado» ) ); add( mas_texto = new Label( «Información adicional» ) ); }

public void verEstado( String informacion ) { texto.setText( informacion ); } }

Ahora, para dar funcionalidad, debemos crear los objetos correspondientes a la barra deherramientas y a la barra de estado con new; al contrario que en C++, en Java todos losobjetos deben ser creados con el operador new:

add( «North»,tb = new ToolBar() ); add( «South»,sb = new StatusBar() );

También vamos a incorporar un nuevo evento a nuestro controlador, para que maneje loseventos de tipo ACTION_EVENT que le llegarán cuando se pulsen los botones de la barrade herramientas o se realice alguna selección, etc.

case Event.ACTION_EVENT: { be.verEstado( evt.arg.toString() ); return true; }

Cuando la aplicación reciba este tipo de evento, alterará el contenido de la barra de estadopara mostrar la información de la selección realizada o el botón pulsado.

140 Tutorial de Java

Al final, la apariencia de la aplicación en pantalla es la que presenta la figura anterior.

LayoutsLos layout managers o manejadores de composición, en traducción literal, ayudan a adaptarlos diversos Componentes que se desean incorporar a un Panel, es decir, especifican laapariencia que tendrán los Componentes a la hora de colocarlos sobre un Contenedor. Javadispone de varios, en la actual versión, tal como se muestra en la imagen:

¿Por qué Java proporciona estos esquemas predefinidos de disposición de componentes?La razón es simple: imaginemos que deseamos agrupar objetos de distinto tamaño en cel-das de una rejilla virtual: si confiados en nuestro conocimiento de un sistema gráfico deter-minado, codificamos a mano tal disposición, deberemos preveer el redimensionamiento delapplet, su repintado cuando sea cubierto por otra ventana, etc., además de todas las cues-tiones relacionadas con un posible cambio de plataforma (uno nunca sabe a donde van a ira parar los propios hijos, o los applets).

Sigamos imaginando, ahora, que un hábil equipo de desarrollo ha previsto las disposicionesgráficas más usadas y ha creado un gestor para cada una de tales configuraciones, que se

Tutorial de Java 141

ocupará, de forma transparente para nosotros, de todas esas cuitas de formatos. Bien, puesestos gestores son instancias de las distintas clases derivadas de Layout Manager y que seutilizan en el applet que genera la figura siguiente, donde se muestran los diferentes tiposde layouts que proporciona el AWT.

En el applet que genera la figura siguiente, se utilizan los diferentes tipos de layouts queproporciona el AWT.

El ejemplo AwtGui.java, ilustra el uso de paneles, listas, barras de desplazamiento, botones,selectores, campos de texto, áreas de texto y varios tipos de layouts.

En el tratamiento de los Layouts se utiliza un método de validación, de forma que los Com-ponentes son marcados como no válidos cuando un cambio de estado afecta a la geometríao cuando el Contenedor tiene un hijo incorporado o eliminado. La validación se realizaautomáticamente cuando se llama a pack() o show(). Los Componentes visibles marcadoscomo no válidos no se validan automáticamente.

En el ejemplo de control de eventos que se muestra a continuación, la llamada a validate()hace que se realicen en un bloque, en un solo paso, todos los cálculos necesarios para lavalidación del layout.

public boolean action( Event evt,Object obj ) { if( obj.equals( «Cambia Font» ) ) { boton1.setFont( nuevoFont ); boton2.setFont( nuevoFont ); etiqueta.setFont( nuevoFont ); campoTexto.setFont( nuevoFont );

validate(); return true; } }

142 Tutorial de Java

FlowLayoutEs el más simple y el que se utiliza por defecto en todos los Paneles si no se fuerza el uso dealguno de los otros. Los Componentes añadidos a un Panel con FlowLayout se encadenanen forma de lista. La cadena es horizontal, de izquierda a derecha, y se puede seleccionar elespaciado entre cada Componente.

Por ejemplo, podemos poner un grupo de botones con la composición por defecto que pro-porciona FlowLayout:

import java.awt.*;import java.applet.Applet;

public class AwtFlow extends Applet { Button boton1,boton2,boton3;

public void init() { boton1 = new Button( «Aceptar» ); boton2 = new Button( «Abrir» ); boton3 = new Button( «Cerrar» );

add( boton1 ); add( boton2 ); add( boton3 ); } }

Este código, AwtFlow.java, construye tres botones con un pequeño espacio de separaciónentre ellos.

BorderLayoutLa composición BorderLayout (de borde) proporciona un esquema más complejo de coloca-ción de los Componentes en un panel. La composición utiliza cinco zonas para colocar losComponentes sobre ellas: Norte, Sur, Este, Oeste y Centro. Es el layout o composición quese utilizan por defecto Frame y Dialog.

El Norte ocupa la parte superior del panel, el Este ocupa el lado derecho, Sur la zona inferiory Oeste el lado izquierdo. Centro representa el resto que queda, una vez que se hayanrellenado las otras cuatro partes.

Tutorial de Java 143

Con BorderLayout se podrían representar botones de dirección:

import java.awt.*;import java.applet.Applet;

public class AwtBord extends Applet { Button botonN,botonS,botonE,botonO,botonC;

public void init() { setLayout( new BorderLayout() );

botonN = new Button( «Norte» ); botonS = new Button( «Sur» ); botonE = new Button( «Este» ); botonO = new Button( «Oeste» ); botonC = new Button( «Centro» );

add( «North»,botonN ); add( «South»,botonS ); add( «East»,botonE ); add( «West»,botonO ); add( «Center»,botonC ); } }

Este es el código, AwtBord.java, que genera el applet de botones de dirección:

GridLayoutLa composición GridLayout proporciona gran flexibilidad para situar Componentes. El la-yout se crea con un número de filas y columnas y los Componentes van dentro de las celdasde la tabla así definida.

En la figura siguiente se muestra un panel que usa este tipo de composición para posicionarseis botones en su interior, con tres filas y dos columnas que crearán las seis celdas nece-sarias para albergar los botones:

144 Tutorial de Java

import java.awt.*;import java.applet.Applet;

public class AwtGrid extends Applet { Button boton1,boton2,boton3,boton4,boton5,boton6;

public void init() { setLayout( new GridLayout( 3,2 ) );

boton1 = new Button( «1» ); boton2 = new Button( «2» ); boton3 = new Button( «3» ); boton4 = new Button( «4» ); boton5 = new Button( «5» ); boton6 = new Button( «6» );

add( boton1 ); add( boton2 ); add( boton3 ); add( boton4 ); add( boton5 ); add( boton6 ); } }

Este es el código, AwtGrid.java, que genera la imagen del ejemplo.

GridBagLayoutEs igual que la composición de GridLayout, con la diferencia que los Componentes no nece-sitan tener el mismo tamaño. Es quizá el layout más sofisticado y versátil de los que actual-mente soporta AWT.

En la figura siguiente vemos la imagen generada por un panel que usa el GridBagLayoutpara soportar diez botones en su interior:

Tutorial de Java 145

import java.awt.*;import java.applet.Applet;

public class AwtGBag extends Applet {

public void init() { GridBagLayout gridbag = new GridBagLayout(); GridBagConstraints gbc = new GridBagConstraints();

setLayout( gridbag ); gbc.fill = GridBagConstraints.BOTH; gbc.weightx = 1.0; Button boton0 = new Button( «Botón 0» ); gridbag.setConstraints( boton0,gbc ); add( boton0 ); Button boton1 = new Button( «Botón 1» ); gridbag.setConstraints( boton1,gbc ); add( boton1 ); Button boton2 = new Button( «Botón 2» ); gridbag.setConstraints( boton2,gbc ); add( boton2 );

gbc.gridwidth = GridBagConstraints.REMAINDER; Button boton3 = new Button( «Botón 3» ); gridbag.setConstraints( boton3,gbc ); add( boton3 );

gbc.weightx = 0.0; Button boton4 = new Button( «Botón 4» ); gridbag.setConstraints( boton4,gbc ); add( boton4 );

gbc.gridwidth = GridBagConstraints.RELATIVE; Button boton5 = new Button( «Botón 5» ); gridbag.setConstraints( boton5,gbc ); add( boton5 );

gbc.gridwidth = GridBagConstraints.REMAINDER; Button boton6 = new Button( «Botón 6» ); gridbag.setConstraints( boton6,gbc ); add( boton6 );

gbc.gridwidth = 1;

146 Tutorial de Java

gbc.gridheight = 2; gbc.weighty = 1.0; Button boton7 = new Button( «Botón 7» ); gridbag.setConstraints( boton7,gbc ); add( boton7 );

gbc.weighty = 0.0; gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.gridheight = 1; Button boton8 = new Button( «Botón 8» ); gridbag.setConstraints( boton8,gbc ); add( boton8 ); Button boton9 = new Button( «Botón 9» ); gridbag.setConstraints( boton9,gbc ); add( boton9 ); } }

Este es el código, AwtGBag.java, que utilizamos para generar la imagen del ejemplo.

Para aprovechar de verdad todas las posibilidades que ofrece este layout, hay que pintarantes en papel como van a estar posicionados los Componentes; utilizar gridx, gridy, gridwidthy gridheight en vez de GridBagConstraints.RELATIVE, porque en el proceso de validacióndel layout pueden quedar todos los Componentes en posición indeseable. Además, se de-berían crear métodos de conveniencia para hacer más fácil el posicionamiento de los Com-ponentes. En el ejemplo siguiente, AwtGBagConv.java, creamos el método de convenienciaaddComponente() para la incorporación de nuevos Componentes al layout, lo que hace mássencillo el manejo de los Constraints:

import java.awt.*;import java.applet.Applet;

public class AwtGBagConv extends Applet { GridBagLayout gridbag = new GridBagLayout();

void addComponente( Component comp,int gridx,int gridy, int gridw,int gridh ) { GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = gridx; gbc.gridy = gridy; gbc.gridwidth = gridw; gbc.gridheight = gridh; gridbag.setConstraints( comp,gbc ); add( comp ); }

public void init() { setLayout( gridbag ); addComponente( new Label( «Nombre:» ),0,0,1,1 ); addComponente( new TextField( 12 ),1,0,2,1 ); addComponente( new TextArea( 5,20 ),0,1,2,2 ); addComponente( new Checkbox( «Sí?» ),2,1,1,1 ); addComponente( new List(),2,2,1,1 ); } }

Tutorial de Java 147

CardLayoutEste es el tipo de composición que se utiliza cuando se necesita una zona de la ventana quepermita colocar distintos Componentes en esa misma zona. Este layout suele ir asociadocon botones de lista (Choice), de tal modo que cada selección determina el panel (grupo decomponentes) que se presentarán.

En la figura siguiente mostramos el efecto de la selección sobre la apriencia de la ventanaque contiene el panel con la composición CardLayout:

import java.awt.*;import java.applet.Applet;

public class AwtCard extends Applet { Panel card; final static String PanelBoton = «Panel con Botones»; final static String PanelTexto = «Panel con Campo de Texto»;

public void init() { setLayout( new BorderLayout() );

Panel ac = new Panel(); Choice c = new Choice();

c.addItem( PanelBoton ); c.addItem( PanelTexto ); ac.add( c ); add( «North»,ac );

card = new Panel(); card.setLayout( new CardLayout() );

Panel p1 = new Panel(); p1.add( new Button( «Botón 1» ) ); p1.add( new Button( «Botón 2» ) ); p1.add( new Button( «Botón 3» ) ); Panel p2 = new Panel(); p2.add( new TextField( «Texto»,20 ) );

card.add( PanelBoton,p1 ); card.add( PanelTexto,p2 );

148 Tutorial de Java

add( «Center»,card ); }

public boolean action( Event evt,Object arg ) { if( evt.target instanceof Choice ) { ( (CardLayout)card.getLayout() ).show( card,(String)arg ); return true; } return false; } }

Este es el código, AwtCard.java, que hemos utilizado para generar las dos imágenes quemuestran el funcionamiento de la composición CardLayout.

Crear un Layout propioSe puede crear un Layout personalizado en base a la interface LayoutManager. Hay queredefinir los cinco métodos que utiliza este interface, lo cual puede no resultar sencillo, asíque en lo posible se deben utilizar los métodos de colocación de componentes que propor-ciona AWT, fundamentalmente en el momento en que parezca que ninguno de los Layoutsque hemos visto satisface nuestras exigencias, deberíamos volver a comprobar que elGridBagLayout, que es el más flexible, de verdad no cumple nuestros requerimientos.

No obstante, vamos a implementar un layout propio, MiLayout.java, para poder colocar losComponentes en posiciones absolutas del panel que contenga a este layout. Derivamosnuestro nuevo layout de LayoutManager y redefinimos los cinco métodos de la clase paraque podamos posicionar los Componentes.

import java.awt.*;

public class MiLayout implements LayoutManager {

public MiLayout() { }

public void addLayoutComponent( String name,Component comp ) { }

public void removeLayoutComponent( Component comp ) { }

public Dimension preferredLayoutSize( Container parent ) { Insets insets = parent.insets(); int numero = parent.countComponents(); int ancho = 0; int alto = 0;

for( int i=0; i < numero; i++ ) { Component comp = parent.getComponent( i ); Dimension d = comp.preferredSize();

Tutorial de Java 149

Point p = comp.location(); if( ( p.x + d.width ) > ancho ) ancho = p.x + d.width; if( ( p.y + d.height ) > alto ) alto = p.y + d.height; } return new Dimension( insets.left + insets.right + ancho, insets.top + insets.bottom + alto ); }

public Dimension minimumLayoutSize( Container parent ) { Insets insets = parent.insets(); int numero = parent.countComponents(); int ancho = 0; int alto = 0;

for( int i=0; i < numero; i++ ) { Component comp = parent.getComponent( i ); Dimension d = comp.preferredSize(); Point p = comp.location(); if( ( p.x + d.width ) > ancho ) ancho = p.x + d.width; if( ( p.y + d.height ) > alto ) alto = p.y + d.height; } return new Dimension( insets.left + insets.right + ancho, insets.top + insets.bottom + alto ); }

public void layoutContainer( Container parent ) { int numero = parent.countComponents();

for( int i=0; i < numero; i++ ) { Component comp = parent.getComponent( i ); Dimension d = comp.preferredSize(); comp.resize( d.width,d.height ); } } }

Y ahora vamos a ver un ejemplo en que utilicemos nuestro Layout. Posicionaremos tresbotones en el panel y un campo de texto con una etiqueta precediéndolo. La apriencia quetendrá en pantalla será la que se muestra en la figura:

150 Tutorial de Java

import java.awt.*;import java.applet.Applet;

public class AwtLibre extends Applet { Button boton1,boton2,boton3; Label etiqueta; TextField texto;

public void init() { setLayout( new MiLayout() );

boton1 = new Button( «Aceptar» ); boton2 = new Button( «Abrir» ); boton3 = new Button( «Cerrar» ); etiqueta = new Label( «Texto» ); texto = new TextField( «»,20 );

add( boton1 ); add( boton2 ); add( boton3 ); add( etiqueta ); add( texto );

boton1.move( 0,10 ); boton2.move( 70,10 ); boton3.move( 30,40 ); etiqueta.move( 75,70 ); texto.move( 120,70 ); } }

Este código, AwtLibre.java, es el que genera la imagen anterior creando un applet que utili-za nuestro recién creado layout.

Control de eventosEn el pasado, un programa que quisiera saber lo que estaba haciendo el usuario, debíarecoger la información él mismo. En la práctica, esto significaba que una vez inicializado, elprograma entraba en un gran bucle en el que continuamente se bloqueaba para comprobarque el usuario estuviese haciendo algo interesante (por ejemplo, pulsar un botón, pulsaruna tecla, mover una barra o mover el ratón) y tomar las acciones oportunas. Esta técnica seconoce como polling.

Tutorial de Java 151

El polling funciona, pero se vuelve demasiado difícil de manejar con las aplicaciones moder-nas por dos razones fundamentales: Primero, el uso de polling tiende a colocar todo elcódigo de control de eventos en una única localización (dentro de un gran bucle); segundo,las interacciones dentro del gran bucle tienden a ser muy complejas. Además, el pollingnecesita que el programa esté ejecutando el bucle, consumiendo tiempo de CPU, mientrasestá esperando a que el usuario se decida a hacer algo, lo que supone un gran despilfarrode recursos.

El AWT resuelve estos problemas abrazando un paradigma diferente, en el que están basa-dos todos los sistemas modernos de ventanas: la orientación a eventos. Dentro del AWT,todas las acciones que pueda realizar el usuario caen dentro de un gran saco que son loseventos. Un evento describe, con suficiente detalle, una acción particular del usuario. Enlugar de que el programa activamente recoja todos los eventos generados por el usuario, elsistema Java avisa al programa cuando se produce un evento interesante.

La clase EventUn contenedor soltado en un entorno gráfico se convierte en rápido receptor de eventos detodo tipo, singularmente de los relacionados con el movimiento del ratón, pulsaciones deteclas, creación/movimiento/destrucción de partes gráficas y, por último, los referidos a ac-ciones del usuario respecto de componentes (elección de un menú, pulsación de un botón,etc.).

La clase Event es el jugador principal en el juego de los eventos. Intenta capturar las carac-terísticas fundamentales de todos los eventos que genera el usuario. Los datos miembro dela clase Event son los que se indican a continuación:

id - El tipo de evento que se ha producido

target - Componente sobre el que se ha producido el evento

x, y - Las coordenadas en donde se ha producido el evento relativas al Componente queactualmente está procesando ese evento. El origen se toma en la esquina superior izquier-da del Componente

key - Para eventos de teclado, es la tecla que se ha pulsado. Su valor será el valor Unicodedel carácter que representa la tecla. Otros valores que puede tomas son los de las teclasespeciales como INICIO, FIN, F1, F2, etc.

when - Instante en que se ha producido el evento

modifiers - La combinación aritmética del estado en que se encuentran las teclas modifica-doras Mays, Alt, Ctrl.

clickCount - El número de clicks de ratón consecutivos. Sólo tiene importancia en los even-tos MOUSE_DOWN

arg - Es un argumento dependiente del evento. Para objetos Button, este objeto arg es un

152 Tutorial de Java

objeto String que contiene la etiqueta de texto del botón

evt - El siguiente evento en una lista encadenada de eventos

Una instancia de la clase Event será creada por el sistema Java cada vez que se genere unevento. Es posible, sin embargo, que un programa cree y envíe eventos a los Componentesa través de su método postEvent().

Tipos de eventosLos eventos se catalogan por su naturaleza, que se indicará en el miembro id de su estruc-tura. Los grandes grupos de eventos son:

Eventos de Ventana

Son los que se generan en respuesta a los cambios de una ventana un frame o un dialogo.

· WINDOW_DESTROY· WINDOW_EXPOSE· WINDOW_ICONIFY· WINDOW_DEICONIFY· WINDOW_MOVED

Eventos de Teclado

Son generados en respuesta a cuando el usuario pulsa y suelta una tecla mientras un Com-ponente tiene el foco de entrada.

· KEY_PRESS· KEY_RELEASE· KEY_ACTION· KEY_ACTION_RELEASE

Eventos de Ratón

Son los eventos generados por acciones sobre el ratón dentro de los límites de un Compo-nente.

· MOUSE_DOWN· MOUSE_UP· MOUSE_MOVE· MOUSE_ENTER· MOUSE_EXIT· MOUSE_DRAG

Tutorial de Java 153

Eventos de Barras

Son los eventos generados como respuesta a la manipulación de barras de desplazamiento(scrollbars).

· SCROLL_LINE_UP· SCROLL_LINE_DOWN· SCROLL_PAGE_UP· SCROLL_PAGE_DOWN· SCROLL_ABSOLUTE

Eventos de Lista

Son los eventos generados al seleccionar elementos de una lista.

· LIST_SELECT· LIST_DESELECT

Eventos Varios

Son los eventos generados en función de diversas acciones.

· · ACTION_EVENT· LOAD_FILE· SAVE_FILE· GOT_FOCUS· LOST_FOCUS

El applet EventosPrnt.java está diseñado para observar los eventos que se producen sobreél. Cada vez que se genera un evento, el applet responde imprimiendo el evento que hacapturado en la línea de comandos desde donde se ha lanzado el applet.

Una vez que se haya compilado el código y cargado el applet en el appletviewer o en unnavegador con soporte Java, jugar un poco con el applet. Mover el cursor dentro del applet,picar con el ratón, picar y arrastrar, teclear algo, cambiar el tamaño de la ventana y taparlay destaparla con otra. Las acciones anteriores harán que en la ventana en donde se hayalanzado el appletviewer, o en la consola Java en caso de Netscape, vayan apareciendo lostextos que indican los eventos que está recibiendo el applet.

Lo cierto es que el uso de System.out.println() en un applet es algo que no debería utilizarse, eincluso puede llegar a no funcionar en algunos sistemas, pero tenía la ventaja de ser laforma más fácil de ver los eventos. No obstante, vamos a reescribir el código del appletutilizando una Lista.

Una Lista es una lista de cadenas o Strings definidas en java.awt.List. Crearemos una listade 25 líneas y no permitiremos selección múltiple, que son los dos parámetros que necesitael constructor del objeto List. El código EventosList.java que se muestra a continuacióncorresponde al anterior ejemplo un poco modificado.

154 Tutorial de Java

import java.awt.*;import java.applet.Applet;

public class EventosList extends Applet { List lista;

public void init() { lista = new List( 25,false ); add( lista ); lista.addItem( «Evento init» ); }

public void start() { lista.addItem( «Evento start» ); }

public void destroy() { lista.addItem( «Evento destroy» ); }

public void paint( Graphics g ) { lista.addItem( «Evento paint» ); }

public void update( Graphics g ) { lista.addItem( «Evento update» ); }

public boolean mouseUp( Event evt,int x, int y ) { lista.addItem( «Evento mouseUp en («+x+»,»+y+»)» ); return false; }

public boolean mouseDown( Event evt,int x, int y ) { lista.addItem( «Evento mouseDown en («+x+»,»+y+»)» ); return false; }

public boolean mouseDrag( Event evt,int x, int y ) { lista.addItem( «Evento mouseDrag en («+x+»,»+y+»)» ); return false; }

public boolean mouseMove( Event evt,int x, int y ) { lista.addItem( «Evento mouseMove en («+x+»,»+y+»)» ); return false; }

public boolean mouseEnter( Event evt,int x, int y ) { lista.addItem( «Evento mouseEnter en («+x+»,»+y+»)» ); return false; }

public boolean mouseExit( Event evt,int x, int y ) { lista.addItem( «Evento mouseExit» ); return false;

Tutorial de Java 155

}

public boolean keyDown( Event evt,int x ) { lista.addItem( «Evento keyDown,carácter «+(char)x ); return true; }

public void getFocus() { lista.addItem( «Evento getFocus» ); }

public void gotFocus() { lista.addItem( «Evento gotFocus» ); }

public void lostFocus() { lista.addItem( «Evento lostFocus» ); } }

El applet VisorEventos.java muestra los datos que componen cada evento que se produce.Hemos incorporado un botón y una lista, tal como se puede apreciar en la figura, para podergenerar diferentes eventos.

Moviendo el ratón o actuando sobre los dos Componentes, botón y lista, podemos observarlos datos que el sistema Java envía en la recolección de esos eventos.

Generación y propagación de eventosTomemos el applet, EventosPro.java, que aparece en la figura siguiente. Consta de dosinstancias de la clase Button , embebidas dentro de una instancia de la clase Panel . Esta

156 Tutorial de Java

instancia está a su vez embebida dentro de otra instancia de la clase Panel . Esta últimainstancia de la clase Panel está situada junto a una instancia de la clase TextArea , y ambasestán embebidas dentro de una instancia de la clase Applet .

La figura siguiente presenta los elementos que conforman este applet en forma de árbol,con el TextArea y Button como hojas y la instancia de Applet como raiz.

Cuando un usuario interactúa con el applet, el sistema Java crea una instancia de la claseEvent y rellena sus datos miembro con la información necesaria para describir la acción. Esen ese momento cuando el sistema Java permite al applet controlar el evento. Este controlcomienza por el Componente que recibe inicialmente el evento (por ejemplo, el botón queha sido pulsado) y se desplaza hacia arriba en el árbol de Componentes, componente acomponente, hasta que alcanza al Contenedor de la raíz del árbol. Durante este camino,cada Componente tiene oportunidad de ignorar el evento o reaccionar ante él en una (omás) de las forma siguientes:

· Modificar los datos miembros de la instancia de Event· Entrar en acción y realizar cálculos basados en la información contenida en el

Tutorial de Java 157

evento· Indicar al sistema Java que el evento no debería propagarse más arriba en elárbol

El sistema Java pasa información del evento a un Componente a través del métodohandleEvent() del Componente. Todos los métodos handleEvent() deben ser de la forma:

public boolean handleEvent( Event evt )

Un controlador de eventos solamente necesita una información: una referencia a la instan-cia de la clase Event que contiene la información del evento que se ha producido.

El valor devuelto por el método handleEvent() es importante. Indica al sistema Java si elevento ha sido o no completamente controlado por el controlador. Un valor true indica que elevento ha sido controlado y que su propagación debe detenerse. Un valor false indica queel evento ha sido ignorado, o que no ha sido controlado en su totalidad y debe continuar supropagación hacia arriba en el árbol de Componentes.

Veamos la descripción de una acción con el applet de la figura anterior. El usuario pulsa elbotón «Uno». El sistema run-time del lenguaje Java capturará la información sobre el evento(el número de clicks, la localización del click, la hora en que se ha producido la pulsación yel Componente que ha recibido el click) y empaqueta todos esos datos en una instancia dela clase Event . El sistema Java comienza entonces por el Componente que ha sido pulsado(en este caso, el botón «Uno») y, a través de una llamada al método handleEvent() delComponente, ofrece a éste la posibilidad de reaccionar ante el evento. Si el Componente nocontrola el evento, o no lo hace completamente (indicado por un valor de retorno false), elsistema Java presentará la instancia de Event al siguiente Componente por encima en elárbol (en este caso, una instancia de la clase Panel ). El sistema Java continúa de estemismo modo hasta que el evento es controlado en su totalidad o ya no hay Componentes alos que informar. En la figura siguiente mostramos el camino recorrido por el evento en suintento de que algún Componente lo controle.

Cada Componente del applet añade una línea al objeto TextArea indicando que ha recibidoun evento. Luego permite que el evento se propague al siguiente Componente.

El código del controlador de eventos usado en el ejemplo es el que muestran las siguienteslíneas:

158 Tutorial de Java

public boolean handleEvent( Event evt) { if( evt.id == Event.ACTION_EVENT ) ta.appendText( «Panel « + str + « recibe action...\n» ); else if( evt.id == Event.MOUSE_DOWN ) ta.appendText( «Panel « + str + « recibe mouse_down...\n» );

return super.handleEvent( evt ); }

Métodos de control de eventosEl método handleEvent() es un lugar para que el programador pueda insertar código paracontrolar los eventos. A veces, sin embargo, un Componente solamente estará interesadoen eventos de un cierto tipo (por ejemplo, eventos del ratón). En estos casos, el programa-dor puede colocar el código en un método de ayuda, en lugar de colocarlo en el métodohandleEvent().

No hay métodos de ayuda para ciertos tipos de eventos, aquí está la lista de los que estándisponibles para los programadores:

action( Event evt,Object obj ) gotFocus( Event evt,Object obj ) lostFocus( Event evt,Object obj ) mouseEnter( Event evt,int x,int y ) mouseExit( Event evt,int x,int y ) mouseMove( Event evt,int x,int y ) mouseUp( Event evt,int x,int y ) mouseDown( Event evt,int x,int y ) mouseDrag( Event evt,int x,int y ) keyDown( Event evt,int key ) keyUp( Event evt,int key )

false indicará que el método de ayuda no maneja el evento.

La implementación del método handleEvent() proporcionada por la clase Component invo-ca a cada método de ayuda. Por esta razón, es importante que las implementacionesredefinidas del método handleEvent() en clases derivadas, siempre finalicen con la senten-cia:

return( super.handleEvent( evt ) );

El siguiente trozo de código ilustra esta regla.

public boolean handleEvent( Event evt ) { if( evt.target instanceof MiBoton ) { // Hace algo... return true; }

return( super.handleEvent( evt ) ); }

No seguir esta regla tan simple hará que no se invoquen adecuadamente los métodos deayuda. El applet EventosRaton.java, que controla los eventos de ratón exclusivamente a

Tutorial de Java 159

través de código insertado en sus métodos de ayuda; va dibujando una línea (rubber band)entre el último punto donde se ha producido un click de ratón y la posición actual del cursor.

ACTION_EVENTAlgunos de los eventos que más frecuentemente tendremos que controlar son los siguien-tes:

ACTION_EVENTMOUSE_DOWNKEY_PRESSWINDOW_DESTROY

En la documentación de la clase Event se encuentra toda la lista de eventos que cualquieraplicación puede necesitar manejar y su documentación; como ejemplo de uso vamos adetenernos en el primero de ellos, ACTION_EVENT.

Como ejemplo del manejo de eventos vamos a ver este evento que se provoca al pulsar unbotón, seleccionar un menú, etc. Para su control podemos manejarlo en el métodohandleEvent() o en el método action().

Los dos métodos anteriores pertenecen a la clase Component por lo que todas las clasesderivadas de ésta contendrán estos dos métodos y se pueden sobrecargar para que seajuste su funcionamiento a lo que requiere nuestra aplicación.

Veamos el siguiente ejemplo, en que se controla este evento a través del métodohandleEvent(), que es el método general de manejo de eventos:

public boolean handleEvent( Event evt ) { switch( evt.id ) { case Event.ACTION_EVENT: // evt.arg contiene la etiqueta del botón pulsado // o el item del menú que se ha seleccionado if( ( «Pulsado «+n+» veces» ).equals( evt.arg ) ) return( true ); default: return( false ); } }

Pero en este caso, cuando se produce este evento se llama al método action(), que sería:

public boolean action( Event evt,Object arg ) { if( ( «Pulsado «+n+» veces» ).equals( arg ) ) return( true ); return( false ); }

Como se puede comprobar, incluso si las etiquetas cambian se puede recibir el evento. Losejemplos anteriores corresponden al control de un evento producido por un botón que cam-bia su etiqueta cada vez que se pulsa. Aunque esta no es la única forma de manejar even-tos; de hecho se puede hacer:

160 Tutorial de Java

if( evt.target == miBoton )

en donde se comparan objetos en lugar de etiquetas.

Mejorar el diseño de InterfacesLa interface de usuario es el aspecto más importante de una aplicación, tal como ya hemosrepetido. Un diseño pobre de la interface es un grave problema para que el usuario puedaobtener todo el partido posible de la aplicación. Para ser efectivos, no debemos limitarnos acolocar una serie de botones, etiquetas y barras de desplazamiento sobre la pantalla. Des-afortunadamente, nadie ha determinado una reglas correctas para del diseño de una buenainterface.

Los diseñadores del AWT parece que se pusieron como meta principal que las clases delAWT funcionasen correctamente, dejando un poco de lado su apariencia. Sin embargo, hanproporcionado suficientes mecanismos para poder alterar la apariencia de los Componen-tes del AWT, muchas veces no de forma sencilla, pero ahí están para que los programado-res podamos alterar la visualización de los Componentes sobre nuestro interface. Vamos aver unas cuantas formas sencillas de alterar y dar un mejor aspecto a nuestros diseños.

· · Cambio de Font de Caracteres· · Colores de Fondo y Texto· · Fijar el Tamaño Preferido· · Uso de Insets· · Habilitar y Deshabilitar Componentes

Cambio de fuentes de caracteresEl font de caracteres con el que se presenta un texto en pantalla influye mucho en el impactode una interface. Una interface efectiva no debería utilizar una maraña de fuentes, pero síque debería utilizar dos o tres diferentes para aumentar el atractivo y la efectividad de lostextos. El applet Fuentes.java, tal como se muestra en la figura, ilustra este extremo.

Utiliza tres tipos de fonts de caracteres (en diferente estilo y diferente tamaño) para llamar laatención del usuario sobre las tres zonas de la interface. La fuente por defecto para todoslos Componentes es la fuente Dialog. Java proporciona otras fuentes con propósitos más

Tutorial de Java 161

especializados, el número exacto de fuentes depende de la plataforma, por ello, se puedeutilizar el applet ListaFuentes.java para obtener una lista de las fuentes de caracteres dispo-nibles en el sistema.

Su navegador no entiende la marca &ltAPPLET>, la siguiente imagen muestrala listade fuentes disponible en Windows ‘95

Cuando un programador necesita presentar un Componente en pantalla, como unobjeto TextArea, en una fuente de caracteres distinta a la de defecto, la nueva fuentedebe seleccionarse mediante el método setFont():

public void setFont( Font f )

El método setFont() espera como parámetro una fuente. En el siguiente trozo de códigovemos cómo se usa:

TextArea ta = new TextArea(); Font f = new Font( «Helvetica»,Font.ITALIC,12 ); ta.setFont( f );

Este código con ligeras modificaciones funcionará para cualquier Componente. Si se cam-bia la fuente de un Contenedor, todos los Componentes colocados dentro del Contenedorautomáticamente adoptarán la nueva fuente de caracteres. El siguiente código,CambioFuentes.java, muestra esta circunstancia:

import java.awt.*;import java.applet.Applet;

public class CambioFuentes extends Applet {

public static void main( String args[] ) { Frame fr = new Frame( «Cambio de Fuentes» ); CambioFuentes cf = new CambioFuentes();

Font f = new Font( «Helvetica»,Font.ITALIC,12 ); fr.setFont( f ); fr.setLayout( new FlowLayout() ); Button b = new Button( «Hola» ); fr.add( b ); Checkbox cb = new Checkbox( «Púlsame» ); fr.add( cb ); TextArea ta = new TextArea(); fr.add( ta );

fr.pack(); fr.show(); } }

La fuente de caracteres solamente se indica para el objeto Frame, el botón, la caja y el áreade texto también utilizarán esta fuente.

162 Tutorial de Java

Colores de fondo y de textoEl impacto visual del color nunca debe ser desestimado cuando se ataca el diseño de unainterface de usuario. El color tiende a atraer la visión y puede utilizarse para llamr la aten-ción sobre una parte importante del interface. En el ejemplo siguiente, el color rojo alrededordel botón hace que la vista se fije inmediatamente en él.

La clase Component proporciona dos métodos para modificar el color de un Componente.A través de los métodos setBackground() y setForeground(), se pueden indicar los coloresdel fondo y del texto, respectivamente:

public void setBackground( Color c ) public void setForeground( Color c )

Ambos métodos solamente necesitan un parámetro, un objeto Color. A continuación mostra-mos un ejemplo de su uso:

TextArea ta = new TextArea(); ta.setBackground( Color.blue ); ta.setForeground( Color.red );

Este código funcionará con ligeras modificaciones para casi todos los Componentes delAWT. Si se cambia el Color de un Contenedor, todos los Componentes colocados dentro deese Contenedor, automáticamente adoptan el nuevo color. El applet, CambioColor.java, ilustraeste punto. El Color solamente se fija para el color de fondo del objeto Frame; el botón, lacaja y el área de texto usarán ese mismo color de fondo.

import java.awt.*;import java.applet.Applet;

public class CambioColor extends Applet {

public static void main( String args[] ) { Frame fr = new Frame( «Cambio de Color» ); CambioColor cc = new CambioColor();

fr.setBackground( Color.red ); fr.setLayout( new FlowLayout() ); Button b = new Button( «Hola» ); fr.add( b ); Checkbox cb = new Checkbox( «Púlsame» ); fr.add( cb ); TextArea ta = new TextArea(); fr.add( ta );

fr.pack(); fr.show(); } }

Tutorial de Java 163

La calidad de soporte del color varía mucho de una plataforma a otra. Bajo Windows ’95, laclase Button ignora totalmente los comandos de color y se empeña en permanecer bajo unpatrón de grises. Por otro lado, el fondo de la clase Label parece ser transparente. AlgunosComponentes no se presentan en pantalla con un mismo color para un mismo objeto Color.Bajo Windows ’95, un fondo de color naranja aparece como naranja en muchos Componen-tes (excepto en los botones), pero se presenta como amarillo cuando se trata de objetosTextArea o TextField. El soporte del color en Solaris parece ser mucho más consistente.

Fijar el tamaño preferidoOtro ingrediente importante de una buena interface es el tamaño con el cual aparecerá unComponente o Contenedor en la pantalla. En el corazón del control de la composición delinterface está el layout manager, que es capaz de fijar el tamaño y la posición de los Compo-nentes que se vayan incorporando al layout que está manejando. Esto, indirectamente, tam-bién influye en el tamaño del Contenedor.

En este caso, en lugar de llamar a un método para indicar cuál debe ser el tamaño de unComponente, hay que derivar una nueva clase del Componente y redefinir el métodopreferredSize() que devolverá el tamaño preferido. El layout manager llama al métodopreferredSize() para determinar cuál debe ser el tamaño preferido para cada Componente.

Hay que redefinir el método:

public Dimension preferredSize()

Uno puede estar tentado a utilizar el método resize() o el método reshape() para especificarun tamaño, pero no debe hacerse. Ambos métodos son usados directamente por el layoutmanager, y los ajustes de tamaño se reclacularán la próxima vez que el layout managertenga que recomponer la posición de los Componentes sobre el Contenedor.

Las siguientes líneas de código demuestran el uso del método preferredSize(), conveniente-mente redefinido en una clase derivada. Este método crea un nuevo objeto Dimension conla altura y anchura especificadas y se lo devuelve a quien lo ha llamado (normalmente ellayout manager).

public Dimension preferredSize() { return new Dimension( 200,100 ); }

Desde luego, no hay nada para evitar que el tamaño preferido de un botón varíedinámicamente, como ocurre en el applet CambioBoton.java, mostrado en la figura:

164 Tutorial de Java

Este applet contiene dos botones. Pulsando sobre uno de ellos se provoca el cambio detamaño en el otro. El método preferredSize() del applet de la figura anterior utiliza la variabledim, que es una variable privada:

public Dimension preferredSize() { return new Dimension( dim ); }

La variable dim puede ser fijada o cambiada vía una función miembro como la siguiente:

public void newPreferredSize( Dimension dim ) { this.dim = new Dimension( dim ); resize( dim ); }

El Contenedor debe llamar al método anterior antes de su llamada al método layout() pararesituar los Componentes después de que haya cambiado el tamaño de uno de ellos.

Uso de InsetsInsets, o borde interior, al igual que el tamaño preferido, se puede utilizar para proporcionara la interface de usuario una mayor ordenación espacial. El insets de un Contendor, especi-fica la cantidad de espacio alrededor del borde interior del Contenedor donde no se situaráningún Componente, estará vacío.

El borde interior se especifica para un Contenedor, redefiniendo su método insets():

public Insets insets()

El método insets() no necesita ningún parámetro y devuelve una instancia del objeto Insets.La clase Insets tiene cuatro campos, que especifican el número de pixels que constituirán elborde interior desde arriba, izquierda, abajo y derecha, respectivamente.

A continuación mostramos el uso típico de insets(). El código de este ejemplo indica que losComponentes contenidos en el layout deberán estar situados a 5 unidades desde cualquie-ra de los bordes del Contenedor.

public Insets insets() { return new Insets( 5,5,5,5 ); }

En la figura siguiente se muestra el efecto que provoca en la apariencia del interface el usode insets().

Tutorial de Java 165

Habilitar y deshabilitar componentesLos Componentes de una interface de usuario que no están actualmente disponibles peropueden estarlo, en función de alguna acción del usuario, se dice que están deshabilitados.Se presentan con su rótulo en gris y no responden a las acciones del usuario. El deshabilitarComponentes es mucho mejor que ocultarlos, porque el usuario puede ver las operacionesque puede realizar, aunque en ese momento no las tenga disponibles.

La clase Component proporciona tres métodos para llevar a cabo la habilitación ydeshabilitación de Componentes:

public void enable() public void disable() public void enable( boolean condicionBooleana )

Los primeros dos métodos habilitan y deshabilitan un Componente. El tercer método tam-bién hace eso dependiendo del valor de un parámetro booleano. Veamos un ejemplo:

Button b = new Button( «Púlsame» ); b.enable(); // o, b.disable(); // o, b.enable( true );

Este código funcionará, con muy pocas modificaciones, sobre cualquier Componente. Paraun uso efectivo de estos métodos, un programa debe monitorizar el estado de la interface deusuario. Como el usuario interactúa con la interface, su estado interno cambia. Esto hay quereflejarlo en el estado de los Componentes que pasarán de habilitados a deshabilitados, oviceversa, en función de las circunstancias.

Tomemos como ejemplo ahora el applet que aparece en la figura siguiente, obtenido de laejecución de Habilitar.java:

En este applet, los botones Añadir y Borrar están deshabilitados hasta que el usuario hayaentrado en el campo de texto. Este previene una activación inadvertida de los dos botonesen el caso de que no haya texto en el campo. Tan pronto como se teclee el primer carácteren el campo de texto, se habilitan los dos botones, cada cual asumiendo su propio rol. Si, encualquier momento, el campo de texto se volviese a quedar vacío, los dos botones volveríana estar deshabilitados

Además de que los Componentes deshabilitados tienen una apariencia visual diferente,tampoco reciben eventos desde el sistema Java; los eventos son inmediatamente propaga-dos al Contenedor en que está situado el Componente deshabilitado.

166 Tutorial de Java

Botón gráficoAWT adolece de herramientas ya desarrolladas para implementar interfaces al uso, con altaproliferación de imágenes y facilidades para que el programador utilice directamente Com-ponentes. Sin embargo, sí proporciona las herramientas suficientes como para que se pue-dan implementar cualquier tipo de Componentes, o modificar al gusto los ya existentes.Para mostrar un ejemplo, vamos a implementar un botón gráfico, BotonGrafico.java

Partimos de un botón normal al cual aplicaremos tres imágenes diferentes, para cada unode los tres estados en que puede encontrarse: pulsado, liberado e inhabilitado.

En el fichero fuente, podemos comprobar que las tres imágenes se pueden pasar cómoparámetro en la llamada APPLET, por ejemplo:

<APPLET CODE=BotonGrafico.class WIDTH=100 HEIGHT=50><PARAM NAME=IMAGEN0 VALUE=boton0.gif><PARAM NAME=IMAGEN1 VALUE=botonup.gif><PARAM NAME=IMAGEN2 VALUE=botondn.gif></APPLET>

Observando el código, se puede comprobar que una vez cargadas la imágenes, solamentese deben controlar los eventos del ratón, para que automáticamente se presenten las imá-genes del botón adecuadas y responda correctamente.

import java.awt.*;import java.applet.Applet;

public class BotonGrafico extends Applet { private MediaTracker tracker; private Image imagen[] = new Image[3]; private boolean BotActivo = false; private boolean BotPulsado = false; private boolean tresImg = false; private int Estado = 0;

public void init() { String istr; tracker = new MediaTracker( this );

// Recogemos las tres imágenes de los parámentros de llamada // al applet for( int i=0; i < 3; i++ ) { istr = getParameter( «IMAGEN» + i ); if( istr == null ) tresImg = false; else { // Registramos las imágenes con el Media Tracker imagen[i] = getImage( getCodeBase(),istr ); tracker.addImage( imagen[i],0 ); try { tracker.waitForAll(); } catch( InterruptedException e ) { System.out.println( «Error cargando imagen « + i ); }

Tutorial de Java 167

} } }

public void start() { repaint(); }

public void stop(){ }

// Controlamos la pulsación del ratón public boolean mouseDown( Event evt,int x,int y ) { BotPulsado = true; repaint(); return( true ); }

// Controlamos cuando el usuario suelta el botón del ratón public boolean mouseUp( Event evt,int x,int y ) { if( BotPulsado && BotActivo ) { BotPulsado = true; repaint(); } else { BotPulsado = false; repaint(); } return( true ); }

// Controlamos cuando el cursor del ratón entra en el // campo de acción del applet // Presentamos un mensaje en la línea de estado public boolean mouseEnter( Event evt,int x,int y ) { BotActivo = true; showStatus( «Tutorial de Java, Boton Grafico» ); repaint(); return( true ); }

// Controlamos cuando el cursor del ratón abandona el // lugar ocupado por el applet public boolean mouseExit( Event evt,int x,int y ) { BotActivo = false; showStatus( «» ); repaint(); return( true ); }

public void update( Graphics g ) { // Controlamos el estado en que se queda el botón // tras la acción que se haya hecho con el ratón if( !BotActivo )

168 Tutorial de Java

Estado = 0; else if( BotActivo && !BotPulsado ) Estado = 1; else Estado = 2; paint( g ); }

public void paint( Graphics g ) { g.drawImage( imagen[Estado],0,0,this ); } }

Tutorial de Java 169

Capítulo 8 Gráficos

Objetos Gráficos

En páginas anteriores ya se ha mostrado cómo escribir applets, cómo lanzarlos y los funda-mentos básicos de la presentación de información sobre ellos. Ahora, pues, querremoshacer cosas más interesantes que mostrar texto; ya que cualquier página HTML puedemostrar texto. Para ello, Java proporciona la clase Graphics , que permite mostrar texto através del método drawString(), pero también tiene muchos otros métodos de dibujo. Paracualquier programador, es esencial el entendimiento de la clase Graphics , antes deadentrarse en el dibujo de cualquier cosa en Java. Esta clase proporciona el entorno detrabajo para cualquier operación gráfica que se realice dentro del AWT. Juega dos importan-tes papeles: por un lado, es el contexto gráfico, es decir, contiene la información que va aafectar a todas las operaciones gráficas, incluyendo los colores de fondo y texto, la fuentede caracteres, la localización y dimensiones del rectángulo en que se va a pintar, e inclusodispone de información sobre el eventual destino de las operaciones gráficas (pantalla oimagen). Por otro lado, la clase Graphics proporciona métodos que permiten el dibujo deprimitivas, figuras y la manipulación de fonts de caracteres y colores. También hay clasespara la manipulación de imágenes, doble-buffering, etc.

Para poder pintar, un programa necesita un contexto gráfico válido, representado por unainstancia de la clase Graphics . Pero, como esta clase es abstracta, no se puede instanciardirectamente; así que debemos crear un componente y pasarlo al programa como un argu-mento a los métodos paint() o update().

Los dos métodos anteriores, paint() y update(), junto con el método repaint() son los queestán involucrados en la presentación de gráficos en pantalla. El AWT, para reducir el tiem-po que necesitan estos métodos para realizar el repintado en pantalla de gráficos, tiene dosaxiomas:

· Primero, el AWT repinta solamente aquellos Componentes que necesitan serrepintados, bien porque estuviesen cubiertos por otra ventana o porque se pida surepintado directamente

· Segundo, si un Componente estaba tapado y se destapa, el AWT repinta solamentela porción del Componente que estaba oculta

En la ejecución del applet que aparece a continuación, EjemploGraf.java, podemos obser-var como se realiza este proceso. Ignorar la zona de texto de la parte superior del applet demomento, y centrar la mirada en la parte coloreada. Utilizando otra ventana, tapar y desta-par parte de la zona que ocupa el applet. Se observará que solamente el trozo de applet que

170 Tutorial de Java

estaba cubierto es el que se repinta. Yendo un poco más allá, solamente aquellos compo-nentes que estén ocultos y se vuelvan a ver serán los que se repinten, sin tener en cuenta suposición dentro de la jerarquía de componentes.

La pantalla en Java se incrementa de izquierda a derecha y de arriba hacia abajo, tal comomuestra la figura:

Los pixels de la pantalla son pues: posición 0 + ancho de la pantalla - 1.

En los textos, el punto de inserción se encuentra en la línea base de la primera letra.

Métodos para Dibujos

Vamos a presentar métodos para dibujar varias figuras geométricas. Como estos métodosfuncionan solamente cuando son invocados por una instancia válida de la clase Graphics ,su ámbito de aplicación se restringe a los componentes que se utilicen en los métodospaint() y update(). Normalmente los métodos de dibujo de primitivas gráficas funcionan porpares: un método pinta la figura normal y el otro pinta la figura rellena.

drawLine( x1,y1,x2,y2 )drawRect( x,y,ancho,alto )

Tutorial de Java 171

fillRect( x,y,ancho,alto )clearRect( x,y,ancho.alto )drawRoundRect( x,y,ancho,alto,anchoArco,altoArco )fillRoundRect( x,y,ancho,alto,anchoArco,altoArco )draw3DRect( x,y,ancho,alto,boolean elevado )fill3DRect( x,y,ancho,alto,boolean elevado )drawOval( x,y,ancho,alto )fillOval( x,y,ancho,alto )drawArc( x,y,ancho,alto,anguloInicio,anguloArco )fillArc( x,y,ancho,alto,anguloInicio,anguloArco )drawPolygon( int[] puntosX,int[] puntosY[],numPuntos )fillPolygon( int[] puntosX,int[] puntosY[],numPuntos )drawString( string s,x,y )drawChars( char data[],offset,longitud,x,y )drawBytes( byte data[],offset,longitud,x,y )copyArea( xSrc,ySrc,ancho,alto,xDest,yDest )

LíneasSi se necesita dibujar una línea, se puede utilizar el método

g.drawLine( x1,y1,ancho,alto );

donde g es una instancia de la clase Graphics . graphics.drawLine(..) también sería legal yaque graphics es también una instancia de Graphics. Graphics es una clase abstracta por loque no se pueden crear objetos de esta clase, es decir, la siguiente sentencia es totalmenteilegal:

g = new Graphics();

porque no se puede utilizar new para obtener g.

En el código de la aplicación Cuadrados.java, podemos observar el uso del método dedibujar líneas. Es el más simple de todos los métodos gráficos, que pinta una línea recta deun pixel de ancho entre el punto inicial que se indica y el final, calculado en función delancho y alto de la región donde se pinta. En el siguiente applet, FigLinea.java, podemos verel método drawLine() en funcionamiento:

172 Tutorial de Java

RectangulosLos rectángulos, al igual que los óvalos, se definen por un punto de inicio de coordenadas xe y y el ancho y el alto de la caja que circunscribirá a la figura.

Para dibujar un rectángulo en pantalla, basta con llamar al método drawRect() indicando laposición en que deseamos colocar la esquina superior-izquierda del rectángulo y su anchoy alto. Por ejemplo, el código siguiente, Rect1.java, pinta un rectángulo alrededor del appletque lo contiene:

import java.awt.*;import java.applet.Applet;

public class Rect1 extends Applet { int ancho,alto;

public void init() { Dimension d = size(); // devuelve el tamaño del applet ancho = d.width; alto = d.height;

repaint(); }

public void paint( Graphics g ) { g.drawRect( 0,0,ancho-1,alto-1 ); } }

En este caso hemos tenido que utilizar ancho-1 y alto-1, para que fuese visible todo el rectán-gulo y no solamente la mitad, por causa de la referencia de coordenadas que utiliza Javacon respecto a la pantalla.

El método drawRect() pinta un rectángulo abierto, si queremos un rectángulo relleno, ten-dremos que invocar al método fillRect(), que tiene idéntica sintaxis. El código siguiente,Rect2.java, pinta un rectángulo relleno en el centro del applet que lo contiene:

import java.awt.*;import java.applet.Applet;

public class Rect2 extends Applet { int apAncho; int apAlto; int x,y,ancho,alto;

public void init() { Dimension d = size(); apAncho = d.width; apAlto = d.height; ancho = apAncho/3; alto = apAlto/3; x = (apAncho - ancho)/2; y = (apAlto - alto)/2;

repaint();

Tutorial de Java 173

}

public void paint( Graphics g ) { g.drawRect( 0,0,apAncho-1,apAlto-1 ); g.fillRect( x,y,ancho-1,alto-1 ); } }

Si queremos pintar un montón de rectángulos en posiciones y de tamaños aleatorios, alestilo de Mondrian, también podemos hacerlo. Para ello utilizaremos el método Math.random()de la clase Math . Este método devuelve un doble entre 0.0 y 1.0. El método aleatorio() sería:

private int aleatorio( int rango ) { double retornoMath;

retornoMath = Math.random(); return( (int)( retornoMath * rango ) ); }

Este método fuerza a que el resultado sea un entero en el rango que nosotros indiquemos.El casting o moldeo se realiza de double a int. El casting en Java es mucho más seguro queen C u otros lenguajes que permiten castings arbitrarios. En Java solamente se puedenrealizar castings que tengan sentido, por ejemplo entre un float y un int; y no podrían reali-zarse un casting, por ejemplo entre un int y un String.

Vamos también a pintar los rectángulos en diferentes colores, utilizando el método setColor()de la clase Graphics . El AWT predefine suficientes colores, con lo cual, además de pintarun rectángulo aleatorio, lo mostraremos en un color aleatorio. En el código, Rect3.java, quemostramos, se pinta un rectángulo con tamaño, posición y color aleatorios:

import java.awt.*;import java.applet.Applet;

public class Rect3 extends Applet { int apAncho,apAlto; int x,y,ancho,alto; Color color;

public void init() { Dimension d = size(); apAncho = d.width; apAlto = d.height; x = aleatorio( apAncho ); y = aleatorio( apAlto ); ancho = aleatorio( apAncho - x ); alto = aleatorio( apAlto - y ); color = new Color( aleatorio(255),aleatorio(255),aleatorio(255) );

repaint(); }

public void paint( Graphics g ) { g.setColor( color ); g.drawRect( 0,0,apAncho-1,apAlto-1 ); g.fillRect( x,y,ancho-1,alto-1 );

174 Tutorial de Java

}

private int aleatorio( int rango ) { double retornoMath;

retornoMath = Math.random(); return( (int)( retornoMath * rango ) ); } }

Si se carga repetidas veces el applet, aparecerán diferentes rectángulos, con distintos colo-res y en distintas posiciones, así que vamos a automatizar ese proceso y a complicar deltodo el ejemplo, permitiendo que se pinten multitud de rectángulos coloreados. Debido aque cada uno de los rectángulos va a ser diferente, tendremos que mover los cálculos deposición, tamaño y color de cada rectángulo al método paint(). Además, vamos a permitirespecificar en el fichero HTML que lance el applet, el número de rectángulos que se van adibujar. Para ello fijaremos un número por defecto y en caso de que se incluya el número derectángulos como parámetro de la invocación al applet, utilizaremos este último. He aquí elcódigo, Rect4.java, que realiza todo esto:

import java.awt.*;import java.applet.Applet;

public class Rect4 extends Applet { int apAncho,apAlto; int x,y,ancho,alto; Color color; int numRect = 100;

public void init() { Dimension d = size(); apAncho = d.width; apAlto = d.height;

String s = getParameter( «Numero» ); if( s != null ) numRect = Integer.valueOf( s ).intValue();

repaint(); }

public void paint( Graphics g ) { g.setColor( Color.black ); g.drawRect( 0,0,apAncho-1,apAlto-1 );

for( int i=0; i < numRect; i++ ) { x = aleatorio( apAncho ); y = aleatorio( apAlto ); ancho = aleatorio( apAncho - x ); alto = aleatorio( apAlto - y ); color = new Color( aleatorio(255),aleatorio(255), aleatorio(255) );

g.setColor( color );

Tutorial de Java 175

g.fillRect( x,y,ancho-1,alto-1 ); } }

private int aleatorio( int rango ) { double retornoMath;

retornoMath = Math.random(); return( (int)( retornoMath * rango ) ); } }

Los rectángulos redondeados necesitan dos parámetros adicionales, con respecto al méto-do habitual, para controlar el arco con que se redondearán las esquinas del rectángulo. Elmétodo que pinta rectángulos tridimensionales todavía necesitan un parámetro más queindica si el rectángulo estará sobresaliendo o hundiéndose en la pantalla. En el applet si-guiente, FigRectangulo.java, podemos ver y manipular los valores que pueden tomar estosmétodos:

Circulos, elipsesLos m‰todos definidos en Java para la realizaci•n de c•rculos y elipses, al igual que enla mayor•a de los lenguajes, reciben como parßmetros las coordenadas de la esquinasuperior-izquierda y el ancho y alto de la caja en la que se circunscribe el c•rculos o laelipse.

En el ejemplo siguiente, Ojos.java, podemos ver la r‰plica de la aplicaci•n conocida porlos usuarios de Motif, en la que los ojos siguen los movimientos del rat•n cuando seencuentra dentro del campo del applet.

176 Tutorial de Java

import java.awt.*;import java.applet.Applet;

public class Ojos extends Applet { int Mx = -1; int My = -1; int OjoR1; int OjoR2; int Ancho; int Alto; int OjoIzq; int OjoDch; int OjoPY; Color Pupila = Color.black; Color Iris = Color.green.brighter(); Color Orbita = Color.white; Image Imagen; Graphics OjoCG;

public void init() { setBackground( Color.darkGray ); Dimension d = size();

// Fijamos las variables que nos van a posicionar los // ojos sobre el applet OjoIzq = d.width >> 2; OjoDch = OjoIzq * 3; OjoPY = d.height >> 2;

OjoR2 = d.width >> 4; OjoR1 = d.width >> 5; Ancho = ( d.width >> 3 ) + OjoR1; Alto = Ancho >> 1; }

public void update( Graphics g ) { paint( g ); }

Tutorial de Java 177

// Funcion auxiliar, para que no se desmanden los valores // y no se los salgan los ojos de las orbitas int swap( int i,int c ) { if( i > c ) i = c; else if( i < -c ) i = -c; return( i ); }

// Pintamos el ojo sobre el applet void pintaOjo( Graphics g,int x ) { // Fijamos los desplazamientos, las nuevas posiciones de // referencia, en funcion de la posicion del cursor del // raton, determinada por Mx y My int dx = x-Mx; int dy = OjoPY-My;

// Pintamos el ojo solamente bordeado, es decir, cerrado if( dx < Ancho && dx > -Ancho && dy < Alto && dy > -Alto ) { g.setColor( getBackground() ); g.fillOval( x-Ancho,OjoPY-Alto,Ancho << 1,Alto << 1 ); g.setColor( getBackground().brighter() ); g.drawOval( x-Ancho,OjoPY-Alto,Ancho << 1,Alto << 1 ); } else { // Pintamos el hueco del ojo, por el que se movera el iris g.setColor( Orbita ); g.fillOval( x-Ancho,OjoPY-Alto,Ancho << 1,Alto << 1 );

int y = OjoPY; dx = swap( dx >> 3,OjoR1 << 1 ); dy = swap( dy >> 5,OjoR1 >> 1 ); if( Mx >= 0 && My >= 0 ) { x -= dx; y -= dy; }

// Pintamos el iris, sobre el que se movera la pupila g.setColor( Iris ); g.fillOval( x-OjoR2,y-OjoR2,OjoR2 << 1,OjoR2 << 1 ); if( Mx >= 0 && My >= 0 ) { x -= ( dx >> 1 ); y -= dy; }

// Pintamos la pupila dentro del iris g.setColor( Pupila ); g.fillOval( x-OjoR1,y-OjoR1,OjoR1 << 1,OjoR1 << 1 ); }

178 Tutorial de Java

}

public void paint( Graphics g ) { Dimension d = size();

// La primera vez que se llama a este metodo, todavia no // hay nada, asi que creamos el soporte de los ojos if( Imagen == null || OjoCG == null ) { Imagen = createImage( d.width,d.height >> 1 ); OjoCG = Imagen.getGraphics(); }

// Pintamos los ojos OjoCG.setColor( getBackground() ); OjoCG.fillRect( 0,0,d.width,d.height ); pintaOjo( OjoCG,OjoIzq ); pintaOjo( OjoCG,OjoDch ); g.drawImage( Imagen,0,0,this ); }

// Cuando movemos el cursos dentro del applet, recibimos las // coordenadas y repintamos el ojo public boolean mouseMove( Event evt,int x,int y ) { Mx = x; My = y; repaint();

return true; }

// Si nos llega el evento de que el raton se ha salido del applet // ponemos los todo en el centro, con los ojos espantados, // como si se hubiese visto al diablo public boolean mouseExit( Event evt,int x,int y ) { Mx = My = -1; repaint();

return true; } }

Cada uno de los métodos que nos permiten representar en pantalla elipses y arcos, re-quieren como parámetros las coordenadas del punto central del óvalo o arco y el ancho yalto, en valor positivo, del rectángulo que circunscribe al óvalo o al arco, como hemosdicho. Para pintar arcos, necesitamos dos parámetros adicionales, un ángulo de inicio yun ángulo para el arco; de este modo especificamos el inicio del arco y en tamaño delarco en grados (no en radianes). En la figura que sigue mostramos cómo se tiene encuanta el ángulo a la hora de las especificación de ángulos en los valores de losparámetros a pasar a los métodos drawArc() y fillArc().

Tutorial de Java 179

El applet siguiente, FigElipse.java, nos permite manipular el comportamiento de estosmétodos ante los valores que indiquemos en los parámetros que aceptan:

PolígonosLos polígonos son figuras creadas a partir de una secuencia de segmentos. Cada métodoque permite dibujar polígonos necesita como parámetros las coordenadas de los puntosdonde termina cada uno de los segmentos que constituyen el polígono. Estos puntos sepueden especificar como dos arrays paralelos de enteros, uno conteniendo las coordena-das x de los puntos y otro las coordenadas y; o mediante una instancia de la clase Polygon .Esta clase proporciona el método addPoint(), que permite ir construyendo el polígono puntoa punto.

El applet, FigPoligono.java, que aparece seguidamente permite ver los métodos de dibujode polígonos en acción.

180 Tutorial de Java

Funciones GráficasA través de líneas también se pueden realizar figuras más complicadas que resulten de larepresentación gráfica de funciones matemáticas. No pretendemos entrar en complicadasdisquisiciones matemáticas, sino presentar algunas de las particularidades de los gráficos yJava. Vamos a presentar el código siguiente, Grafico1.java, que pintará la curva de la fun-ción seno de izquierda a derecha de la ventana:import java.awt.*;import java.applet.Applet;

public class Grafico1 extends Applet { int x0,xN,y0,yN;

public void init() { Dimension d = size();

x0 = y0 = 0; xN = d.width-1; yN = d.height-1; }

public void paint( Graphics g ) { for( int x=x0; x < xN; x++ ) g.drawLine( x,(int)(yN*Math.sin(x)), x+1,(int)(yN*Math.sin(x+1)) ); } }

El corazón del funcionamiento del applet se encuentra en el bucle incluido en el métodopaint():

for( int x=x0; x < xN; x++ ) g.drawLine( x,(int)(yN*Math.sin(x)),x+1,(int)(yN*Math.sin(x+1)));

El bucle va desplazándose a través de cada pixel de la pantalla en el eje x, y en cada una delas posiciones, calcula el seno para ese pixel. También calcula el seno del pixel siguiente ypinta una línea que une esos dos puntos. Debido a que el seno de un número real estásiempre comprendido entre 1 y -1, escalamos el valor de y con yN. Y por fin hacemos uncasting de los valores del seno en el eje y porque son flotantes fundamentalmente y elmétodo drawLine() necesita enteros.

El applet anterior funciona correctamente, pero adolece de dos dos problemas: Primero, lossenos son operaciones en coma flotante, luego para que el applet tenga realmente utilidadnecesitamos utilizar números en coma flotante. Segundo, el sistema de coordenadas quemaneja el applet va desde el punto (0,0), de la esquina superior-izquierda hasta la inferior-derecha. Pero el sistema normal de coordenadas Cartesianas empieza a contar desde laesquina inferior-izquierda. Lo que haremos será mover el origen al centro del applet y trans-formar las coordenadas del eje y para que vayan en las direcciones adecuadas.

La solución a los dos problemas anteriores puede tomar un montón de caminos diferentes,sin embargo, la clave se encuentra en separar los datos de lo que es la pantalla. Es decir,vamos a realizar nuestros cálculos matemáticos suponiendo que estamos en el espacio deun rectángulo en coordenadas cartesianas y, por otro lado, tomaremos a la pantalla como un

Tutorial de Java 181

rectángulo de puntos de ancho y tamaño fijos. Así podremos realizar los cálculos en el planode los ejes cartesianos y presentarlos en el espacio reducido de la ventana del applet.Necesitaremos pues, un método para convertir un punto de la ventana del applet en unpunto en el mundo cartesiano, y otro que haga la función contraria. El código siguiente,GraficoSeno.java, hace todo esto:

import java.awt.*;import java.applet.Applet;

public class GraficoSeno extends Applet { int x0,xN,y0,yN; double xmin,xmax,ymin,ymax; int apAncho,apAlto;

public void init() { Dimension d = size(); apAncho = d.width; apAlto = d.height;

x0 = y0 = 0; xN = apAncho-1; yN = apAlto-1; xmin = -10.0; xmax = 10.0; ymin = -1.0; ymax = 1.0; }

public void paint( Graphics g ) { double x1,y1,x2,y2; int j1,j2;

j1 = ValorY( 0 ); for( int i=0; i < apAncho; i++ ) { j2 = ValorY( i+1 ); g.drawLine( i,j1,i+1,j2 ); j1 = j2; } }

private int ValorY( int valor ) { double x,y; int retorno;

// Cartesianas equivalentes al punto de la pantalla x = (valor * (xmax-xmin) / (apAncho-1)) + xmin; // Calculamos el seno de ese punto y = Math.sin( x ); // Escalamos la coordenada y dentro de los limites de la ventana retorno = (int)( (y-ymin) * (apAlto-1) / (ymax-ymin) ); // Reconvertinos el valor cartesiano a punto de pantalla retorno = apAlto - retorno;

return( retorno ); }

182 Tutorial de Java

}

Ejecutando este applet, podremos observar que lo que se presenta ya se parece muchísimomás a la imagen que todos conocemos de la onda senoidal.

FractalesNo vamos aquí a proporcionar una clase maestra sobre fractales, sino que vamos a mostrarque Java también se puede emplear para la implementación de algunos ejemplos clásicosde la geometría de fractales. Vamos a realizar por uno sencillito, en el que se trata de unconjunto definido por todos los números reales entre cero y uno, inclusive. Entonces, elimi-namos el tercio central de ese conjunto, es decir, todo lo que se encuentre entre el primertercio y el último, ambos exclusive.

Para visualizar lo anterior vamos a utilizar segmentos de línea. Eliminaremos el tercio mediodel segmento, es decir, nos quedaremos con el segmento que va desde el inicio del seg-mento hasta el segundo noveno, y entre el séptimo noveno y el final; y continuaremos esteproceso indefinidamente.

La verdad es que parece un poco confusa la explicación, pero si una imagen vale más quemil palabras, un buen programa Java vale más que mil imágenes. Por ello, aquí esta elcódigo Java, Tercio.java, que muestra sucesivas líneas en que se demuestra la explicacióndel párrafo anterior.

import java.awt.*;import java.applet.Applet;import java.util.Vector;

public class Tercio extendsApplet { int apAncho,apAlto; Vector finPuntos = new Vector();

public void init() { apAncho = size().width; apAlto = size().height;

finPuntos.addElement( new Float( 0.0f ) ); finPuntos.addElement( new Float( 1.0f ) );

Tutorial de Java 183

}

public void paint( Graphics g ) { float x1,x2; Float tmpFloat;

for( int i=0; i < apAncho; i+=5 ) { // Pintamos las lineas for( int j=0; j < finPuntos.size(); j+=2 ) { tmpFloat = (Float)finPuntos.elementAt( j ); x1 = tmpFloat.floatValue(); tmpFloat = (Float)finPuntos.elementAt( j+1 ); x2 = tmpFloat.floatValue(); g.drawLine( Math.round( x1*apAncho ),i, Math.round( x2*apAncho),i ); } // Eliminamos el tercio medio de las lineas BorraSegmentos(); // Comprobamos que no nos salimos de la pantalla tmpFloat = (Float)finPuntos.elementAt( 0 ); x1 = tmpFloat.floatValue(); tmpFloat = (Float)finPuntos.elementAt( 1 ); x2 = tmpFloat.floatValue(); if( Math.round( x1*apAncho ) == Math.round( x2*apAncho ) ) break; } }

private void BorraSegmentos() { int indice = 0; int fin = finPuntos.size();

for( int i=0; i < fin; i+=2 ) { BorraTercioMedio( indice,indice+1 ); indice += 4; } }

private void BorraTercioMedio( int izqda,int dcha ) { float gap; float x1,x2; Float tmpFloat1,tmpFloat2;

tmpFloat1 = (Float)finPuntos.elementAt( izqda ); tmpFloat2 = (Float)finPuntos.elementAt( dcha ); gap = tmpFloat2.floatValue() - tmpFloat1.floatValue(); x1 = tmpFloat1.floatValue() + gap/3.0f; x2 = tmpFloat2.floatValue() - gap/3.0f; finPuntos.insertElementAt( new Float( x2 ),dcha ); finPuntos.insertElementAt( new Float( x1 ),dcha ); } }

184 Tutorial de Java

Aquí vemos el resultado de la ejecución del applet. ¿Está claro? Desde luego no es larepresentación más perfecta del conjunto, debido a que tenemos que manejar puntos detamaño finito en lugar de puntos matemáticos genuinos. Dependiendo de lo larga que sea laventana del applet, probablemente sólo se vean entre seis y doce interacciones antes denecesitar de que los pixels se vuelvan fraccionarios.

Es de hacer notar que en este programa Java hemos utilizado el objeto Vector. Debido a queJava no dispone de punteros, este objeto y la clase asociada, disponen de los métodosnecesarios para poder implementar una lista enlazada, que es lo que nosotros hemos utili-zado.

Líneas flotantesEl ejemplo de las líneas flotantes es más difícil de describir que su código. Se ejecuta sobreun bucle infinito y son líneas que se están moviendo continuamente por la ventana queocupa el applet, rebotando en los bordes y moviéndose continuamente. El código del ficheroLineas.java, es el que contiene las sentencias que crean el applet que se visualiza a conti-nuación. Repasar el código delalgoritmo para entenderlo, que es bastante simple.

import java.awt.*;import java.applet.Applet;

public class Lineas extends Applet { int LINEAS = 25; int gSup = 3; int gInf = 3;

Tutorial de Java 185

int gIzq = 2; int gDch = 6; int apAncho,apAlto; int gLineas[][] = new int[LINEAS][4];

public void init() { Dimension d = size(); apAncho = d.width; apAlto = d.height; }

public void start() { gLineas[0][0] = aleatorio( apAncho ); gLineas[0][1] = aleatorio( apAlto ); gLineas[0][2] = aleatorio( apAncho ); gLineas[0][3] = aleatorio( apAlto );

for( int i=1; i < LINEAS; i++ ) { CopiaLinea( i,i-1 ); RecalcLinea( i ); } repaint(); }

public void paint( Graphics g ) { while( true ) { for( int i=LINEAS-1; i > 0; i— ) CopiaLinea( i,i-1 ); RecalcLinea( 0 ); g.setColor( Color.black ); g.drawLine( gLineas[0][0],gLineas[0][1], gLineas[0][2],gLineas[0][3] ); g.setColor( getBackground() ); g.drawLine( gLineas[LINEAS-1][0],gLineas[LINEAS-1][1], gLineas[LINEAS-1][2],gLineas[LINEAS-1][3] ); } }

private void CopiaLinea( int desde,int hasta ) { for( int i=0; i < 4; i++ ) gLineas[desde][i] = gLineas[hasta][i]; }

public int aleatorio( int rango ) { double retornoMath;

retornoMath = Math.random(); return( (int)(retornoMath * rango) ); }

private void RecalcLinea( int i ) { gLineas[i][1] += gSup; if( (gLineas[i][1] < 0 ) || (gLineas[i][1] > apAlto) )

186 Tutorial de Java

{ gSup *= -1; gLineas[i][1] += 2*gSup; }

gLineas[i][3] += gInf; if( (gLineas[i][3] < 0 ) || (gLineas[i][3] > apAlto) ) { gInf *= -1; gLineas[i][3] += 2*gInf; }

gLineas[i][0] += gIzq; if( (gLineas[i][0] < 0 ) || (gLineas[i][0] > apAncho) ) { gIzq *= -1; gLineas[i][0] += 2*gIzq; }

gLineas[i][2] += gDch; if( (gLineas[i][2] < 0 ) || (gLineas[i][2] > apAncho) ) { gDch *= -1; gLineas[i][2] += 2*gDch; } } }

Métodos para imagenesLos objetos Graphics pueden mostrar imágenes a través del método:

drawImage( Image img,int x,int y,ImageObserver observador );

Hay que tener en cuenta que el método drawImage() necesita un objeto Image y un objetoImageObserver. Podemos cargar una imagen desde un fichero de dibujo (actualmente sólose soportan formatos GIF y JPEG) con el método getImage():

Image img = getImage( getDocumentBase(),»fichero.gif» );

La forma de invocar al método getImage() es indicando un URL donde se encuentre elfichero que contiene la imagen que queremos presentar y el nombre de ese fichero:

getImage( URL directorioImagen,String ficheroImagen );

Un URL común para el método getImage() es el directorio donde está el fichero HTML. Sepuede acceder a esa localización a través del método getDocumentBase() de la clase Applet ,como ya se ha indicado.

Normalmente, se realiza el getImage() en el método init() del applet y se muestra la imagencargada en el método paint(), tal como se muestra en el ejemplo siguiente:

public void init() { img = getImage( getDocumentBase(),»pepe.gif» ); }

Tutorial de Java 187

public void paint( Graphics g ) { g.drawImage( img,x,y,this ); }

En el applet Imagen.java, podemos ver el ejemplo completo. Su ponemos en él la existenciadel fichero «Imagenes/pepe.gif»:

import java.awt.*;import sun.awt.image.URLImageSource;import java.applet.Applet;

public class Imagen extends Applet { Imagen pepe;

public void init() { pepe = getImage( getDocumentBase(),»Imagenes/pepe.gif» ); }

public void paint( Graphics g ) { g.drawString( pepe,25,25,this ); } }

DOBLE-BUFFERING de gráficosAl mostrar gráficos con las técnicas estándar, las imágenes suelen aparecer a trozos o conparpadeo. Las aplicaciones Java permiten que los programas dibujen en memoria, paraluego ir mostrando la imagen completa de forma suave.

Este es el proceso conocido como doble-buffering, y tiene dos ventajas fundamentales so-bre el proceso normal de pintar o dibujar directamente sobre la pantalla:

Primero, el usuario ve aparecer de golpe la imagen en la pantalla. Mientras el usuario estáviendo esa imagen, el programa está generando la siguiente para mostrarla de golpe acontinuación, y así una y otra vez.

Segundo, la técnica de doble-buffering involucra un objeto Image, que se puede pasar direc-tamente a varios métodos. Esta capacidad para manipular objetos Image permite descom-poner las rutinas de dibujo en componentes funcionales, en lugar de un enorme métodopaint().

No obstante, el doble-buffering sólo debe usarse para animaciones gráficas, no como méto-do normal. Lo usual en otras aplicaciones sería repintar la zona que interese solamente.

Contextos gráficos

Para entender el doble-buffering, primero se necesita comprender qué es un contexto gráfi-co. Un contexto gráfico es simplemente una estructura de datos que el sistema sabe utilizarcomo tablero de dibujo, es decir, es la zona en que se va a pintar. Ya hemos visto y utilizadocontextos gráfico en las declaraciones del método paint():

188 Tutorial de Java

public void paint( Graphics g ) {

El objeto Graphics g es el contexto gráfico. Se utiliza g para realizar todo el dibujo en elapplet. Por ejemplo:

g.drawString( «¡Hola!»,25,25 );g.drawRect( 15,15,50,10 );

Entonces, Java traduce todo lo que se dibuja en g en imágenes sobre la pantalla. Pararealizar doble-buffering, se necesita pues, primero crear un contexto gráfico que no es pre-sentado inmediatamente en la pantalla.

Creación de Contextos Gráficos

Crear contextos gráficos tiene dos pasos: Crear una imagen vacía con las dimensionesadecuadas y obtener un objeto Graphics de esa imagen. El objeto Graphics que se constru-ye en el segundo paso realiza la función de contexto gráfico.

Por ejemplo, CGrafico.java:

import java.awt.*;import java.applet.Applet;

public class CGrafico extends Applet { Image dobleBuffer; Graphics miCG;

public void init() { // Inicializa el doble buffer dobleBuffer = createImage( 300,300 ); miCG = dobleBuffer.getGraphics();

// Construye un área gráfica de trabajo miCG .setColor( Color.white ); miCG.fillRect( 0,0,300,300 ); resize( 500,450 ); }

Podemos utilizar miCG para dibujar cualquier cosa. Las imágenes se trazarán en doble bu-ffer. Cuando el dibujo esté terminado, se puede presentar el doble buffer en pantalla:

public void paint( Graphics g ) { // Sólo se tiene que presentar la imagen del buffer g.drawImage( dobleBuffer,0,0,this ); }

Utilización de Contextos Gráficos

Una vez definido un contexto gráfico, podemos usarlo en cualquier parte de nuestro progra-ma. Por ejemplo, podemos repartir la responsabilidad para dibujar sobre varias funciones:

public void titulo() { // Obtiene la fuente de texto actual y la guardamos Font f = miCG.getFont();

Tutorial de Java 189

// Seleccionamos otra fuente para el título miCG.setFont( new Font( «TimesRoman».Font.BOLD,36 ) ); miCG.drawString( «Ejemplo de Espiral»,15,50 ); miCG.drawString( «Círculos»,15,90 ); // Recuperamos la fuente original miCG.setFont( f ); }

public void espiral() { int x,y;

// Dibujamos circulos en los lados horizontales y = 100; for( x=100; x <= 200; x+=10 ) { miCG.drawOval( x,y,20,20 ); miCG.drawOval( x,y+100,20,20 ); } // Ahora en los verticales x = 100; for( y=100; y <= 200; y+=10 ) { miCG.drawOval( x,y,20,20 ); miCG.drawOval( x+100,y,20,20 ); } }

public void start() { // Hace el dibujo off-line titulo(); espiral(); // Ahora muestra la imagen de golpe repaint(); }

Nuevas clases para dibujoLa descomposición funcional de los métodos para dibujo proporcionados por el doble-bufferingse pueden extender a otras clases. Por ejemplo, se puede diseñar una clase especializadaen un tipo de salida (gráficos, informes, CAD, etc.). Pasándole el contexto gráfico comoargumento al constructor, la nueva clase podrá construir la imagen. Esta clase puede serreutilizada en cualquier otro applet que lo necesite. Por ejemplo, el código del fichero fuenteEspiralTest.java siguiente muestra la clase Espiral . El applet espiral crea y usa un objetoEspiral. La representación en pantalla se construye enteramente en la nueva clase:import java.awt.*;import java.applet.Applet;

// Clase de ejemplo para dibujar en contextos gráficosclass Espiral { // Al ser privada, las variables son sólo accesibles por esta clase private int iniX,iniY,iniAncho,iniAlto; private int incremento; private Graphics cg;

190 Tutorial de Java

// constructor. crea una referencia local al buffer gráfico y define // el azul como color de los objetos que se dibujen sobre ella public Espiral( Graphics g ) { cg = g; cg.setColor( Color.blue ); }

// Establece el tamaño y posición iniciales de la espiral public void setTamInicial( int x,int y,int w,int h ) { iniX = x; iniY = y; iniAncho = w; iniAlto = h; }

// Establece el incremento para espaciar los giros public void setTamEspiral( int inc ) { incremento = inc; }

// Construye la espiral public void creaEspiral() { int cX = iniX; int cY = iniY;

// Dibuja líneas por pares, cubriéndose entre sí para formar la // espiral cuadrada hasta alcanzar el tamaño del incremento while( iniAncho > incremento && iniAlto > incremnto ) { g.drawLine( cX,cY,(cX+iniAncho),cY ); g.drawLine( (cX+iniAncho),Cy,(cX+iniAncho),(cY+iniAlto) ); cX += iniAncho; cY += iniAlto; iniAncho -= incremento; iniAlto -= incremento;

g.drawLine( cX,cY,(cX-iniAncho),cY ); g.drawLine( (cX-iniAncho),Cy,(cX-iniAncho),(cY-iniAlto) ); cX -= iniAncho; cY -= iniAlto; iniAncho -= incremento; iniAlto -= incremento; } } }

// Esta es la llamada a la clase desde un Applet Htmlpublic class EspiralTest extends Applet { Image buffer; Espiral spiral; boolean cargado = false;

synchronized public void init() { // crea un buffer donde dibujar buffer = createImage( 300,300 );

Tutorial de Java 191

// crea una nueva espiral spiral = new Espiral( buffer.getGraphics() ); // establece el punto inicial y dimensiones spiral.setTamInicial( 10,10,200,200 ); // establece el incremento spiral.setTamEspiral( 10 ); // construye la espiral spiral.creaEspiral(); }

synchronized public void paint( Graphics g ) { g.drawImage( buffer,25,25,this ); } }

La clase MediaTrackerSi nuestro applet tiene que tratar con imágenes almacenadas en ficheros gif/jpeg, tendre-mos que recurrir a la clase MediaTracker . Esta clase proporciona muchos métodos paramanejar objetos multimedia y grupos de objetos.

Manejo de Imágenes para Animación

Combinando MediaTracker con la técnica de doble-buffering, se pueden conseguir anima-ciones. Estos son algunos métodos de MediaTracker útiles para ver gráficos

void addImage( Image img,int id )

Marca la imagen como parte de un grupo de identificadores id.

boolean checkID( int id )

Comprueba las imágenes pertenecientes al grupo id. Devuelve true si han sido carga-das todas, o false en otro caso (no carga los ficheros que falten). Se utiliza para sabercuando se han cargado todas las imágenes que componen una animación, antes deecharla a andar sobre la pantalla.

boolean checkID( int id,boolean load )

Comprueba las imágenes pertenecientes al grupo id. Devuelve true si han sido carga-das todas, o false en otro caso. Carga las imágenes que falten si load es true.

boolean checkAll( boolean load )

Comprueba todas las imágenes. Devuelve true si todas están cargadas, o false enotro caso. Carga las imágenes que falten si load es true.

void waitForID( int id )

Espera a que se carguen todas las imágenes del grupo id.

void waitForAll()

Espera a que se carguen todas las imágenes.

Se debería utilizar addImage() con cada imagen que necesite un applet. MediaTracker sólo

192 Tutorial de Java

supervisa imágenes asociadas mediante el método addImage().

Si se quiere saber si un determinado gráfico ha sido cargado, se pueden utilizar los métodoscheck. Si se quiere que un gráfico sea cargado antes de hacer cualquier otra cosa, usar losmétodos waitFor. El uso de estos métodos es especialmente útil ya que cuando realizamosla carga de una imagen con getImage(), esta carga se realiza en un thread aparte del denuestro applet, con lo cual, aunque la imagen se encuentre en el otro lado del mundo,getImage() devuelve inmediatamente el control con lo cual podemos comenzar la ejecuciónde la animación, sin haberse cargado todavía todas las imágenes.

Creación de un Objeto MediaTracker

Los objetos MediaTracker necesitan saber qué ImageObserver verá las imágenes que seestán supervisando. Para los applets, el ImageObserver es el propio applet:

miTracker = new MediaTracker( this );

Ahora ya se puede usar el objeto miTracker para manejar todas las imágenes de este applet.

Ejemplo de animación

Veamos ahora un ejemplo de animación, Taza.java. Utilizaremos MediaTracker para asegu-rarnos de que se cargan todas las imágenes. De este modo, cuando se ejecute la anima-ción, no se verá parpadeo o que falta algún fotograma.

Y el código siguiente es el que corresponde a la animación anterior:

import java.awt.*;import java.applet.Applet;

public class Taza extends Applet { Image Images[]; MediaTracker tracker; int index = 0; int maxAncho,maxAlto;

// Componentes off-screen para el doble buffering Image offScrImage; Graphics offScrCG; boolean cargado = false;

// Inicializa el applet. Establece el tamaño y carga las imágenes public void init() { // Establece el supervisor de imágenes y dimensiones tracker = new MediaTracker( this ); maxAncho = 78; maxAlto = 128; imagenes = new Image[6];

// Establece el doble buffer y cambia el tamaño del applet try {

Tutorial de Java 193

offScrImage = createImage( maxAnho,maxAlto ); offScrCG = offScrImage.getGraphics(); offScrCG.setColor( Color.lightGray ); offScrCG.fillRect( 0,0,maxAncho,maxAlto ); resize( maxAncho,maxAlto ); } catch( exception e ) { e.printStackTrace(); }

// Carga las imágenes en un array for( int i=0; i < 33; i++ ) { String imageFich = new String( «taza»+String.valueOf(i+1)+».gif» ); imagenes[i] = getImage( getDocumentBase(),imageFich ); // Pasamos esta imagen al tracker tracker.addImage( imagenes[i],i ); }

try { // Utilizamos el tracker para asegurar que se // cargaran todas las imágenes tracker.waitForAll(); } catch( InterruptedException e ) { } cargado = true; }

// Pinta el fotograma actual public void paint( Graphics g ) { if( cargado ) { // Copia del doble buffer a la pantalla g.drawImage( offScrImage,0,0,this ); // Hacemos una pausa y cogemos la siguiente imagen timerloop(); } }

// Establecemos la primera imagen public void start() { index = 0;

if( tracker.checkID( index ) ) // Pintamos en el doble buffer offScrCG.drawImage( imagenes[index],0,0,this ); }

// Actualiza los fotogramas para que avance la animación public void timerloop() { // Se asegura que la imagen esté presente y la mete en el buffer if( tracker.checkID( index ) ) { // Borra el fondo y obtiene la siguiente imagen

194 Tutorial de Java

offScrCG.fillRect( 0,0,100,100 ); offScrCG.drawImage( imagenes[index],0,0,this ); index++;

// Vuelve al principio de la animación if( index <= images.length ) index = 0; } // Bucle de retardo for( int retardo=0; retardo < 200000; retardo++ ); // Dibujamos el siguiente fotograma repaint(); } }

que tratar con imágenes almacenadas en ficheros gif/jpeg,

Sonido en JavaJava también posee métodos predefinidos para reproducir sonido. El ordenador remoto nonecesita tener un reproductor de audio; Java realizará la reproducción (evidentemente, elordenador remoto, en donde se ejecuta el applet, necesitará disponer de hardware de soni-do).

Reproducción de sonido

La forma más fácil de reproducir sonido es a través del método play():

play( URL directorioSonido,String ficheroSonido );

o, simplemente:

play( URL unURLdeSonido );

Un URL común para el método play() es el directorio donde está el fichero HTML. Se puedeacceder a esa localización a través del método getDocumentBase() de la clase Applet:

play( getDocumentBase(),»sonido.au» );

para que esto funcione, el fichero de la clase y el fichero sonido.au deberían estar en el mismodirectorio.

En el applet Sonido.java, podemos ver el ejemplo completo. Su ponemos en él la existenciadel fichero sonidos/gong.au:

import java.awt.Graphics;import java.applet.Applet;

public class Sonido extends Applet {

public void paint( Graphics g ) { g.drawString( «Prueba de Sonido»,25,25 ); play( getDocumentBase(),»sonidos/gong.au» ); } }

Tutorial de Java 195

Reproducción Repetitiva

Se puede manejar el sonido como si de imágenes se tratara. Se pueden cargar y reproducirmás tarde.

Para cargar un clip de sonido, se utiliza el método getAudioClip():

AudoClip sonido;sonido = getAudioClip( getDocumentBase(),»risas.au» );

Una vez que se carga el clip de sonido, se pueden utilizar tres métodos:

sonido.play();

para reproducir el clip de sonido.

sonido.loop();

para iniciar la reproducción del clip de sonido y que entre en un blucle de reproduc-ción, es decir, en una repetición automática del clip.

sonido.stop();

para detener el clip de sonido que se encuentre en ese instante en reproducción.

Veamos como en el applet Bucle.java utilizamos estos métodos para repetir automáticamenteuna pista de audio.

import java.awt.Graphics;import java.applet.Applet;

public class Bucle extends Applet { AudioClip sonido;

public void init() { sonido = getAudioClip( getDocumentBase(),»sonidos/risas.au» ); }

public void paint( Graphics g ) { g.drawString( «Prueba de Repetición»,25,25 ); }

public void start() { sonido.loop(); }

public void stop() { sonido.stop(); } }

Y, finalmente, el applet ControlSonido.java, nos permite a través de una interface basada enbotones, poder controlar la reproducción de audioclips. El checkbox de carga se usa comoindicador para saber cuando el fichero que contiene el clip de sonido a reproducir, se en-cuentra ya cargado en el sistema.

196 Tutorial de Java

Entrada por ratónUna de las características más útiles que ofrece Java es el soporte directo de la interactividad.La aplicación puede reaccionar a los cambios producidos en el ratón, por ejemplo, sin nece-sidad de escribir ninguna línea de código para su control, solamente indicando qué se quie-re hacer cuando el ratón haga algo.

El evento más común en el ratón es el click. Este evento es gobernado por dos métodos:mouseDown() (botón pulsado) y mouseUp() (botón soltado). Ambos métodos son parte dela clase Applet , pero se necesita definir sus acciones asociadas, de la misma forma que serealiza con init() o con paint().

public boolean mouseDown( Event evt,int x,int y ) { /* ratón pulsado en x,y */ /* hacer algo */ }

public boolean mouseUp( Event evt,int x,int y ) { /* ratón soltado en x,y */ /* hacer algo */ }

Para ambos métodos, las coordenadas x e y de la posición en que ha ocurrido el evento sonrelativas a la esquina superior izquierda del applet.

En el applet Raton.java, se muestra la utilización del ratón para recibir las coordenadas endonde se ha pulsado el ratón, y será en esa posición donde se repinte el saludo habitual:

import java.awt.*;import java.applet.Applet;

public class Raton extends Applet { int ratonX = 25; int ratonY = 25;

public void paint( Graphics g ) { g.drawString( «Hola Mundo!»,ratonX,ratonY ); }

public boolean mouseDown( Event evt,int x, int y ) { ratonX = x; ratonY = y; repaint(); return true; } }

Tutorial de Java 197

Capítulo 9Excepciones en Java

Las excepciones en Java están destinadas, al igual que en el resto de los lenguajes que lassoportan, para la detección y corrección de errores. Si hay un error, la aplicación no deberíamorirse y generar un core (o un crash en caso del DOS). Se debería lanzar (throw) unaexcepción que nosotros deberíamos capturar (catch) y resolver la situación de error. Javasigue el mismo modelo de excepciones que se utiliza en C++. Utilizadas en forma adecuada,las excepciones aumentan en gran medida la robustez de las aplicaciones.

Manejo de las excepcionesVamos a mostrar como se utilizan las excepciones, reconvirtiendo nuestro applet de saludoa partir de la versión iterativa de HolaIte.java:import java.awt.*;import java.applet.Applet;

public class HolaIte extends Applet { private int i = 0; private String Saludos[] = { «Hola Mundo!», «HOLA Mundo!», «HOLA MUNDO!!» };

public void paint( Graphics g ) { g.drawString( Saludos[i],25,25 ); i++; } }

Normalmente, un programa termina con un mensaje de error cuando se lanza una excep-ción. Sin embargo, Java tiene mecanismos para excepciones que permiten ver qué excep-ción se ha producido e intentar recuperarse de ella.

Vamos a reescribir el método paint() de nuestra versión iterativa del saludo:

public void paint( Graphics g ) { try { g.drawString( Saludos[i],25,25 ); } catch( ArrayIndexOutOfBoundsException e ) { g.drawString( «Saludos desbordado»,25,25 ); } catch( Exception e ) { // Cualquier otra excepción

198 Tutorial de Java

System.out.println( e.toString() ); } finally { System.out.println( «Esto se imprime siempre!» ); } i++; }

La palabra clave finally define un bloque de código que se quiere que sea ejecutado siem-pre, de acuerdo a si se capturó la excepción o no. En el ejemplo anterior, la salida en laconsola, con i=4 sería:

Saludos desbordado¡Esto se imprime siempre!

Generar excepciones en JavaCuando se produce un error se debería generar, o lanzar, una excepción. Para que unmétodo en Java, pueda lanzar excepciones, hay que indicarlo expresamente.

void MetodoAsesino() throws NullPointerException,CaidaException

Se pueden definir excepciones propias, no hay por qué limitarse a las predefinidas; bastarácon extender la clase Exception y proporcionar la funcionalidad extra que requiera el trata-miento de esa excepción.

También pueden producirse excepciones no de forma explícita como en el caso anterior,sino de forma implícita cuando se realiza alguna acción ilegal o no válida.

Las excepciones, pues, pueden originarse de dos modos: el programa hace algo ilegal (casonormal), o el programa explícitamente genera una excepción ejecutando la sentencia throw(caso menos normal). La sentencia throw tiene la siguiente forma:

throw ObtejoExcepction;

El objeto ObjetoException es un objeto de una clase que extiende la clase Exception .

El siguiente código de ejemplo origina una excepción de división por cero:

class melon { public static void main( String[] a ) { int i=0, j=0, k;

k = i/j; // Origina un error de division-by-zero } }

Si compilamos y ejecutamos esta aplicación Java, obtendremos la siguiente salida por pan-talla:

> javac melon.java> java melon java.lang.ArithmeticException: / by zero at melon.main(melon.java:5)

Las excepciones predefinidas, como ArithmeticException, se conocen como excepcionesruntime. Actualmente, como todas las excepciones son eventos runtime, sería mejor llamar-las excepciones irrecuperables. Esto contrasta con las excepciones que generamos explíci-

Tutorial de Java 199

tamente, que suelen ser mucho menos severas y en la mayoría de los casos podemosrecuperarnos de ellas. Por ejemplo, si un fichero no puede abrirse, preguntamos al usuarioque nos indique otro fichero; o si una estructura de datos se encuentra completa, podremossobreescribir algún elemento que ya no se necesite.

Excepciones predefinidasLas excepciones predefinidas y su jerarquía de clases es la que se muestra en la figura:

200 Tutorial de Java

Los nombres de las excepciones indican la condición de error que representan. Las siguien-tes son las excepciones predefinidas más frecuentes que se pueden encontrar:

ArithmeticException

Las excepciones aritméticas son típicamente el resultado de una división por 0:

int i = 12 / 0;

NullPointerException

Se produce cuando se intenta acceder a una variable o método antes de ser definido:

class Hola extends Applet { Image img;

paint( Graphics g ) { g.drawImage( img,25,25,this ); } }

IncompatibleClassChangeException

El intento de cambiar una clase afectada por referencias en otros objetos,específicamente cuando esos objetos todavía no han sido recompilados.

ClassCastException

El intento de convertir un objeto a otra clase que no es válida.

y = (Prueba)x; // dondex no es de tipo Prueba

NegativeArraySizeException

Puede ocurrir si hay un error aritmético al intentar cambiar el tamaño de un array.

OutOfMemoryException

¡No debería producirse nunca! El intento de crear un objeto con el operador new hafallado por falta de memoria. Y siempre tendría que haber memoria suficiente porqueel garbage collector se encarga de proporcionarla al ir liberando objetos que no seusan y devolviendo memoria al sistema.

NoClassDefFoundException

Se referenció una clase que el sistema es incapaz de encontrar.

ArrayIndexOutOfBoundsException

Es la excepción que más frecuentemente se produce. Se genera al intentar acceder aun elemento de un array más allá de los límites definidos inicialmente para ese array.

UnsatisfiedLinkException

Se hizo el intento de acceder a un método nativo que no existe. Aquí no existe unmétodo a.kk

Tutorial de Java 201

class A { native void kk(); }

y se llama a a.kk(), cuando debería llamar a A.kk().

InternalException

Este error se reserva para eventos que no deberían ocurrir. Por definición, el usuarionunca debería ver este error y esta excepción no debería lanzarse.

Crear excepciones propiasTambién podemos lanzar nuestras propias excepciones, extendiendo la claseSystem.exception . Por ejemplo, consideremos un programa cliente/servidor. El código clientese intenta conectar al servidor, y durante 5 segundos se espera a que conteste el servidor.Si el servidor no responde, el servidor lanzaría la excepción de time-out:

class ServerTimeOutException extends Exception {}

public void conectame( String nombreServidor ) throws Exception { int exito; int puerto = 80;

exito = open( nombreServidor,puerto ); if( exito == -1 ) throw ServerTimeOutException; }

Si se quieren capturar las propias excepciones, se deberá utilizar la sentencia try:

public void encuentraServidor() { ... try { conectame( servidorDefecto ); catch( ServerTimeOutException e ) { g.drawString( «Time-out del Servidor, intentando alternativa», 5,5 ); conectame( servidorAlterno ); } ... }

Cualquier método que lance una excepción también debe capturarla, o declararla comoparte de la interface del método. Cabe preguntarse entonces, el porqué de lanzar una ex-cepción si hay que capturarla en el mismo método. La respuesta es que las excepciones nosimplifican el trabajo del control de errores. Tienen la ventaja de que se puede tener muylocalizado el control de errores y no tenemos que controlar millones de valores de retorno,pero no van más allá.

202 Tutorial de Java

Capturar excepcionesLas excepciones lanzadas por un método que pueda hacerlo deben recoger en bloque try/catch o try/finally.

int valor;try { for( x=0,valor = 100; x < 100; x ++ ) valor /= x; }catch( ArithmeticException e ) { System.out.println( «Matemáticas locas!» ); }catch( Exception e ) { System.out.println( «Se ha producido un error» ); }

try

Es el bloque de código donde se prevé que se genere una excepción. Es como si dijésemos«intenta estas sentencias y mira a ver si se produce una excepción». El bloque try tiene queir seguido, al menos, por una cláusula catch o una cláusula finally

catch

Es el código que se ejecuta cuando se produce la excepción. Es como si dijésemos «contro-lo cualquier excepción que coincida con mi argumento». En este bloque tendremos queasegurarnos de colocar código que no genere excepciones. Se pueden colocar sentenciascatch sucesivas, cada una controlando una excepción diferente. No debería intentarse cap-turar todas las excepciones con una sola cláusula, como esta:

catch( Excepcion e ) { ...

Esto representaría un uso demasiado general, podrían llegar muchas más excepciones delas esperadas. En este caso es mejor dejar que la excepción se propague hacia arriba y darun mensaje de error al usuario.

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

class Limites extends Exception {}class demasiadoCalor extends Limites {}class demasiadoFrio extends Limites {}class demasiadoRapido extends Limites {}class demasiadoCansado extends Limites {} . . . try { if( temp > 40 ) throw( new demasiadoCalor() );

Tutorial de Java 203

if( dormir < 8 ) throw( new demasiado Cansado() ); } catch( Limites lim ) { if( lim instanceof demasiadoCalor ) { System.out.println( «Capturada excesivo calor!» ); return; } if( lim instanceof demasiadoCansado ) { System.out.println( «Capturada excesivo cansancio!» ); return; } } finally System.out.println( «En la clausula finally» );

La cláusula catch comprueba los argumentos en el mismo orden en que aparezcan en elprograma. Si hay alguno que coincida, se ejecuta el bloque. El operador instanceof se utilizapara identificar exactamente cual ha sido la identidad de la excepción.

finally

Es el bloque de código que se ejecuta siempre, haya o no excepción. Hay una cierta contro-versia entre su utilidad, pero, por ejemplo, podría servir para hacer un log o un seguimientode lo que está pasando, porque como se ejecuta siempre puede dejarnos grabado si seproducen excepciones y nos hemos recuperado de ellas o no.

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

Cuando vamos a tratar una excepción, se nos plantea el problema de qué acciones vamos atomar. En la mayoría de los casos, bastará con presentar una indicación de error al usuarioy un mensaje avisándolo de que se ha producido un error y que decida si quiere o nocontinuar con la ejecución del programa.

Por ejemplo, podríamos disponer de un diálogo como el que se presenta en el código si-guiente:

public class DialogoError extends Dialog { DialogoError( Frame padre ) { super( padre,true ); setLayout( new BorderLayout() );

// Presentamos un panel con continuar o salir Panel p = new Panel(); p.add( new Button( «¿Continuar?» ) ); p.add( new Button( «Salir» ) );

add( «Center»,new Label( «Se ha producido un error. ¿Continuar?» ) ) add( «South»,p ); }

204 Tutorial de Java

public boolean action( Event evt,Object obj ) { if( «Salir».equals( obj ) ) { dispose(); System.exit( 1 ); } return false; } }

Y la invocación, desde algún lugar en que se suponga que se generarán errores, podría sercomo sigue:

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

Propagación de excepcionesLa cláusula catch comprueba los argumentos en el mismo orden en que aparezcan en elprograma. Si hay alguno que coincida, se ejecuta el bloque y sigue el flujo de control por elbloque finally (si lo hay) y concluye el control de la excepción.

Si ninguna de las cláusulas catch coincide con la excepción que se ha producido, entoncesse ejecutará el código de la cláusula finally (en caso de que la haya). Lo que ocurre en estecaso, es exactamente lo mismo que si la sentencia que lanza la excepción no se encontraseencerrada en el bloque try.

El flujo de control abandona este método y retorna prematuramente al método que lo llamó.Si la llamada estaba dentro del ámbito de una sentencia try, entonces se vuelve a intentar elcontrol de la excepción, y así continuamente.

Veamos lo que sucede cuando una excepción no es tratada en la rutina en donde se produ-ce. El sistema Java busca un bloque try..catch más allá de la llamada, pero dentro del méto-do que lo trajo aquí. Si la excepción se propaga de todas formas hasta lo alto de la pila dellamadas sin encontrar un controlador específico para la excepción, entonces la ejecuciónse detendrá dando un mensaje. Es decir, podemos suponer que Java nos está proporcio-nando un bloque catch por defecto, que imprime un mensaje de error y sale.

No hay ninguna sobrecarga en el sistema por incorporar sentencias try al código. La sobre-carga se produce cuando se genera la excepción.

Hemos dicho ya que un método debe capturar las excepciones que genera, o en todo caso,declararlas como parte de su llamada, indicando a todo el mundo que es capaz de generarexcepciones. Esto debe ser así para que cualquiera que escriba una llamada a ese métodoesté avisado de que le puede llegar una excepción, en lugar del valor de retorno normal.Esto permite al programador que llama a ese método, elegir entre controlar la excepción opropagarla hacia arriba en la pila de llamadas. La siguiente línea de código muestra la forma

Tutorial de Java 205

general en que un método declara excepciones que se pueden propagar fuera de él:

tipo_de_retorno( parametros ) throws e1,e2,e3 { }

Los nombres e1,e2,... deben ser nombres de excepciones, es decir, cualquier tipo que seaasignable al tipo predefinido Throwable. Observar que, como en la llamada al método seespecifica el tipo de retorno, se está especificando el tipo de excepción que puede generar(en lugar de un objeto exception).

He aquí un ejemplo, tomado del sistema Java de entrada/salida:

byte readByte() throws IOException;short readShort() throws IOException;char readChar() throws IOException;

void writeByte( int v ) throws IOException;void writeShort( int v ) throws IOException;void writeChar( int v ) throws IOException;

Lo más interesante aquí es que la rutina que lee un char, puede devolver un char; no elentero que se requiere en C. C necesita que se devuelva un int, para poder pasar cualquiervalor a un char, y además un valor extra (-1) para indicar que se ha alcanzado el final delfichero. Algunas de las rutinas Java lanzan una excepción cuando se alcanza el fin delfichero.

En el siguiente diagrama se muestra gráficamente cómo se propaga la excepción que segenera en el código, a través de la pila de llamadas durante la ejecución del código:

Cuando se crea una nueva excepción, derivando de una clase Exception ya existente, se

206 Tutorial de Java

puede cambiar el mensaje que lleva asociado. La cadena de texto puede ser recuperada através de un método. Normalmente, el texto del mensaje proporcionará información pararesolver el problema o sugerirá una acción alternativa. Por ejemplo:

class SinGasolina extends Exception { SinGasolina( String s ) { // constructor super( s ); } ....

// Cuando se use, aparecerá algo como esto try { if( j < 1 ) throw new SinGasolina( «Usando deposito de reserva» ); } catch( SinGasolina e ) { System.out.println( o.getMessage() ); }

Esto, en tiempo de ejecución originaría la siguiente salida por pantalla:

> Usando deposito de reserva

Otro método que es heredado de la superclase Throwable es printStackTrace(). Invocandoa este método sobre una excepción se volcará a pantalla todas las llamadas hasta el mo-mento en donde se generó la excepción (no donde se maneje la excepción). Por ejemplo:

// Capturando una excepción en un métodoclass testcap { static int slice0[] = { 0,1,2,3,4 };

public static void main( String a[] ) { try { uno(); } catch( Exception e ) { System.out.println( «Captura de la excepcion en main()» ); e.printStackTrace(); } }

static void uno() { try { slice0[-1] = 4; } catch( NullPointerException e ) { System.out.println( «Captura una excepcion diferente» ); } } }

Cuando se ejecute ese código, en pantalla observaremos la siguiente salida:

> Captura de la excepcion en main()> java.lang.ArrayIndexOutOfBoundsException: -1 at testcap.uno(test5p.java:19) at testcap.main(test5p.java:9)

Con todo el manejo de excepciones podemos concluir que proporciona un método másseguro para el control de errores, además de representar una excelente herramienta paraorganizar en sitios concretos todo el manejo de los errores y, además, que podemos propor-

Tutorial de Java 207

cionar mensajes de error más decentes al usuario indicando qué es lo que ha fallado y porqué, e incluso podemos, a veces, recuperarnos de los errores.

La degradación que se produce en la ejecución de programas con manejo de excepcionesestá ampliamente compensada por las ventajas que representa en cuanto a seguridad defuncionamiento de esos mismos programas.

208 Tutorial de Java

Tutorial de Java 209

Capítulo 10Threads y Multithreading

Considerando el entorno multithread, cada thread (hilo, flujo de control del programa) repre-senta un proceso individual ejecutándose en un sistema. A veces se les llama procesosligeros o contextos de ejecución. Típicamente, cada thread controla un único aspecto dentrode un programa, como puede ser supervisar la entrada en un determinado periférico o con-trolar toda la entrada/salida del disco. Todos los threads comparten los mismos recursos, alcontrario que los procesos en donde cada uno tiene su propia copia de código y datos(separados unos de otros). Gráficamente, los threads se parecen en su funcionamiento a loque muestra la figura siguiente:

Flujo en programas

Programas de flujo único

Un programa de flujo único o mono-hilvanado (single-thread) utiliza un único flujo de control(thread) para controlar su ejecución. Muchos programas no necesitan la potencia o utilidadde múltiples flujos de control. Sin necesidad de especificar explícitamente que se quiere unúnico flujo de control, muchos de los applets y aplicaciones son de flujo único.

Por ejemplo, en nuestra aplicación estándar de saludo:

public class HolaMundo {

210 Tutorial de Java

static public void main( String args[] ) { System.out.println( «Hola Mundo!» ); } }

Aquí, cuando se llama a main(), la aplicación imprime el mensaje y termina. Esto ocurredentro de un único thread.

Programas de flujo múltiple

En nuestra aplicación de saludo, no vemos el thread que ejecuta nuestro programa. Sinembargo, Java posibilita la creación y control de threads explícitamente. La utilización dethreads en Java, permite una enorme flexibilidad a los programadores a la hora de plantear-se el desarrollo de aplicaciones. La simplicidad para crear, configurar y ejecutar threads,permite que se puedan implementar muy poderosas y portables aplicaciones/applets que nose puede con otros lenguajes de tercera generación. En un lenguaje orientado a Internetcomo es Java, esta herramienta es vital.

Si se ha utilizado un navegador con soporte Java, ya se habrá visto el uso de múltiplesthreads en Java. Habrá observado que dos applet se pueden ejecutar al mismo tiempo, oque puede desplazar la página del navegador mientras el applet continúa ejecutándose.Esto no significa que el applet utilice múltiples threads, sino que el navegador es multithreaded.

Las aplicaciones (y applets) multithreaded utilizan muchos contextos de ejecución para cumplirsu trabajo. Hacen uso del hecho de que muchas tareas contienen subtareas distintas eindependientes. Se puede utilizar un thread para cada subtarea.

Mientras que los programas de flujo único pueden realizar su tarea ejecutando las subtareassecuencialmente, un programa multithreaded permite que cada thread comience y terminetan pronto como sea posible. Este comportamiento presenta una mejor respuesta a la entra-da en tiempo real.

Vamos a modificar nuestro programa de saludo creando tres threads individuales, que impri-men cada uno de ellos su propio mensaje de saludo, MultiHola.java:

// Definimos unos sencillos threads. Se detendrán un rato// antes de imprimir sus nombres y retardos

class TestTh extends Thread { private String nombre; private int retardo;

// Constructor para almacenar nuestro nombre // y el retardo public TestTh( String s,int d ) { nombre = s; retardo = d; }

Tutorial de Java 211

// El metodo run() es similar al main(), pero para // threads. Cuando run() termina el thread muere public void run() { // Retasamos la ejecución el tiempo especificado try { sleep( retardo ); } catch( InterruptedException e ) { ; }

// Ahora imprimimos el nombre System.out.println( «Hola Mundo! «+nombre+» «+retardo ); } }

public class MultiHola { public static void main( String args[] ) { TestTh t1,t2,t3;

// Creamos los threads t1 = new TestTh( «Thread 1»,(int)(Math.random()*2000) ); t2 = new TestTh( «Thread 2»,(int)(Math.random()*2000) ); t3 = new TestTh( «Thread 3»,(int)(Math.random()*2000) );

// Arrancamos los threads t1.start(); t2.start(); t3.start(); } }

Y ya mßs como espectáculo que otra cosa, aunque también podemos tomarlo por el ladoilustrativo, vemos a continuación la elecución del applet Figuras.java que muestra un mon-tón de círculos, cada uno de ellos ejecutándose en un thread diferente y con distinta priori-dad cada uno de ellos. La clase Circulo es la que se utiliza para lanzarla todas las vecesque se quiere, de tal forma que cada uno de los círculos presentes en el applet son instan-cias de la misma clase Circulo .

Creación y control de THREADS

Creación de un Thread

Hay dos modos de conseguir threads en Java. Una es implementando la interface Runnable ,la otra es extender la clase Thread .

212 Tutorial de Java

La implementación de la interface Runnable es la forma habitual de crear threads. Lasinterfaces proporcionan al programador una forma de agrupar el trabajo de infraestructurade una clase. Se utilizan para diseñar los requerimientos comunes al conjunto de clases aimplementar. La interface define el trabajo y la clase, o clases, que implementan la interfacerealizan ese trabajo. Los diferentes grupos de clases que implementen la interface tendránque seguir las mismas reglas de funcionamiento.

Hay una cuantas diferencias entre interface y clase. Primero, una interface solamente pue-de contener métodos abstractos y/o variables estáticas y finales (constantes). Las clases,por otro lado, pueden implementar métodos y contener variables que no sean constantes.Segundo, una interface no puede implementar cualquier método. Una clase que implementeuna interface debe implementar todos los métodos definidos en esa interface. Una interfacetiene la posibilidad de poder extenderse de otras interfaces y, al contrario que las clases,puede extenderse de múltiples interfaces. Además, una interface no puede ser instanciadacon el operador new; por ejemplo, la siguiente sentencia no está permitida:

Runnable a = new Runnable(); // No se permite

El primer método de crear un thread es simplemente extender la clase Thread :

class MiThread extends Thread { public void run() { . . . }

El ejemplo anterior crea una nueva clase MiThread que extiende la clase Thread y sobre-carga el método Thread.run() por su propia implementación. El método run() es donde serealizará todo el trabajo de la clase. Extendiendo la clase Thread , se pueden heredar losmétodos y variables de la clase padre. En este caso, solamente se puede extender o derivaruna vez de la clase padre. Esta limitación de Java puede ser superada a través de laimplementación de Runnable:

public class MiThread implements Runnable { Thread t; public void run() { // Ejecución del thread una vez creado } }

En este caso necesitamos crear una instancia de Thread antes de que el sistema puedaejecutar el proceso como un thread. Además, el método abstracto run() está definido en lainterface Runnable tiene que ser implementado. La única diferencia entre los dos métodoses que este último es mucho más flexible. En el ejemplo anterior, todavía tenemos oportuni-dad de extender la clase MiThread , si fuese necesario. La mayoría de las clases creadasque necesiten ejecutarse como un thread , implementarán la interface Runnable , ya queprobablemente extenderán alguna de su funcionalidad a otras clases.

No pensar que la interface Runnable está haciendo alguna cosa cuando la tarea se estáejecutando. Solamente contiene métodos abstractos, con lo cual es una clase para dar ideasobre el diseño de la clase Thread . De hecho, si vemos los fuentes de Java, podremoscomprobar que solamente contiene un método abstracto:

package java.lang;public interface Runnable {

Tutorial de Java 213

public abstract void run() ;}

Y esto es todo lo que hay sobre la interface Runnable . Como se ve, una interface sóloproporciona un diseño para las clases que vayan a ser implementadas. En el caso deRunnable, fuerza a la definición del método run(), por lo tanto, la mayor parte del trabajo sehace en la clase Thread . Un vistazo un poco más profundo a la definición de la clase Threadnos da idea de lo que realmente está pasando:

public class Thread implements Runnable { ... public void run() { if( tarea != null ) tarea.run() ; } } ... }

De este trocito de código se desprende que la clase Thread también implemente la interfaceRunnable . tarea.run() se asegura de que la clase con que trabaja (la clase que va a ejecu-tarse como un thread) no sea nula y ejecuta el método run() de esa clase. Cuando estosuceda, el método run() de la clase hará que corra como un thread.

Arranque de un Thread

Las aplicaciones ejecutan main() tras arrancar. Esta es la razón de que main() sea el lugarnatural para crear y arrancar otros threads. La línea de código:

t1 = new TestTh( «Thread 1»,(int)(Math.random()*2000) );

crea un nuevo thread. Los dos argumentos pasados representan el nombre del thread y eltiempo que queremos que espere antes de imprimir el mensaje.

Al tener control directo sobre los threads, tenemos que arrancarlos explícitamente. En nues-tro ejemplo con:

t1.start();

start(), en realidad es un método oculto en el thread que llama al método run().

Manipulación de un Thread

Si todo fue bien en la creación del thread, t1 debería contener un thread válido, que contro-laremos en el método run().

Una vez dentro de run(), podemos comenzar las sentencias de ejecución como en otrosprogramas. run() sirve como rutina main() para los threads; cuando run() termina, también lohace el thread. Todo lo que queramos que haga el thread ha de estar dentro de run(), poreso cuando decimos que un método es Runnable, nos obliga a escribir un método run().

En este ejemplo, intentamos inmediatamente esperar durante una cantidad de tiempo aleatoria

214 Tutorial de Java

(pasada a través del constructor):

sleep( retardo );

El método sleep() simplemente le dice al thread que duerma durante los milisegundos espe-cificados. Se debería utilizar sleep() cuando se pretenda retrasar la ejecución del thread.sleep() no consume recursos del sistema mientras el thread duerme. De esta forma otrosthreads pueden seguir funcionando. Una vez hecho el retardo, se imprime el mensaje «HolaMundo!» con el nombre del thread y el retardo.

Suspensión de un Thread

Puede resultar útil suspender la ejecución de un thread sin marcar un límite de tiempo. Si,por ejemplo, está construyendo un applet con un thread de animación, querrá permitir alusuario la opción de detener la animación hasta que quiera continuar. No se trata de termi-nar la animación, sino desactivarla. Para este tipo de control de thread se puede utilizar elmétodo suspend().

t1.suspend();

Este método no detiene la ejecución permanentemente. El thread es suspendido indefinida-mente y para volver a activarlo de nuevo necesitamos realizar una invocación al métodoresume():

t1.resume();

Parada de un Thread

El último elemento de control que se necesita sobre threads es el método stop(). Se utilizapara terminar la ejecución de un thread:

t1.stop();

Esta llamada no destruye el thread, sino que detiene su ejecución. La ejecución no se puedereanudar ya con t1.start(). Cuando se desasignen las variables que se usan en el thread, elobjeto thread (creado con new) quedará marcado para eliminarlo y el garbage collector seencargará de liberar la memoria que utilizaba.

En nuestro ejemplo, no necesitamos detener explícitamente el thread. Simplemente se ledeja terminar. Los programas más complejos necesitarán un control sobre cada uno de losthreads que lancen, el método stop() puede utilizarse en esas situaciones.

Si se necesita, se puede comprobar si un thread está vivo o no; considerando vivo un threadque ha comenzado y no ha sido detenido.

t1.isAlive();

Este método devolverá true en caso de que el thread t1 esté vivo, es decir, ya se hayallamado a su método run() y no haya sido parado con un stop() ni haya terminado el métodorun() en su ejecución.

Tutorial de Java 215

Arrancar y parar THREADSAhora que ya hemos visto por encima como se arrancan, paran y manipulan threads, vamosa mostrar un ejemplo un poco más gráfico, se trata de un contador, cuyo código(App1Thread.java) es el siguiente:import java.awt.*;import java.applet.Applet;

public class App1Thread extends Applet implements Runnable { Thread t; int contador;

public void init() { contador = 0; t = new Thread( this ); t.start(); }

public void run() { while( true ) { contador++; repaint(); try { t.sleep( 10 ); } catch( InterruptedException e ) { ; }; } }

public boolean mouseDown( Event evt,int x,int y ) { t.stop(); return( true ); }

public void paint( Graphics g ) { g.drawString( Integer.toString( contador ),10,10 ); System.out.println( «Contador = «+contador ); }

public void stop() { t.stop(); } }

Este applet arranca un contador en 0 y lo incrementa, presentando su salida tanto en lapantalla gráfica como en la consola. Una primera ojeada al código puede dar la impresión deque el programa empezará a contar y presentará cada número, pero no es así. Una revisiónmás profunda del flujo de ejecución del applet, nos revelará su verdadera identidad.

En este caso, la clase App1Thread está forzada a implementar Runnable sobre la claseApplet que extiende. Como en todos los applets, el método init() es el primero que se ejecu-ta. En init(), la variable contador se inicializa a cero y se crea una nueva instancia de la clase

216 Tutorial de Java

Thread. Pasándole this al constructor de Thread, el nuevo thread ya conocerá al objeto queva a correr. En este caso this es una referencia a App1Thread. Después de que hayamoscreado el thread, necesitamos arrancarlo. La llamada a start(), llamará a su vez al métodorun() de nuestra clase, es decir, a App1Thread.run(). La llamada a start() retornará con éxitoy el thread comenzará a ejecutarse en ese instante. Observar que el método run() es unbucle infinito. Es infinito porque una vez que se sale de él, la ejecución del thread se detiene.En este método se incrementará la variable contador, se duerme 10 milisegundos y envía unapetición de refresco del nuevo valor al applet.

Es muy importante dormirse en algún lugar del thread, porque sino, el thread consumirátodo el tiempo de la CPU para su proceso y no permitirá que entren otros métodos de otrosthreads a ejecutarse. Otra forma de detener la ejecución del thread es hacer una llamada almétodo stop(). En el contador, el thread se detiene cuando se pulsa el ratón mientras elcursor se encuentre sobre el applet. Dependiendo de la velocidad del ordenador, se presen-tarán los números consecutivos o no, porque el incremento de la variable contador es inde-pendiente del refresco en pantalla. El applet no se refresca a cada petición que se le hace,sino que el sistema operativo encolará las peticiones y las que sean sucesivas las converti-rán en un único refresco. Así, mientras los refescos se van encolando, la variable contador seestará todavía incrementando, pero no se visualiza en pantalla.

Suspender y reanudar THREADSUna vez que se para un thread, ya no se puede rearrancar con el comando start(), debido aque stop() concluirá la ejecución del thread. Por ello, en ver de parar el thread, lo quepodemos hacer es dormirlo, llamando al método sleep(). El thread estará suspendido uncierto tiempo y luego reanudará su ejecución cuando el límite fijado se alcance. Pero esto noes útil cuando se necesite que el thread reanude su ejecución ante la presencia de ciertoseventos. En estos casos, el método suspend() permite que cese la ejecución del thread y elmétodo resume() permite que un método suspendido reanude su ejecución. En la siguienteversión de nuestra clase contador , App2Thread.java, modificamos el applet para que utilicelos métodos suspend() y resume():public class App2Thread extends Applet implements Runnable { Thread t; int contador; boolean suspendido;

...

public boolean mouseDown( Event evt,int x,int y ) { if( suspendido ) t.resume(); else t.suspend(); suspendido = !suspendido;

return( true ); } ...

Tutorial de Java 217

Para controlar el estado del applet, hemos introducido la variable suspendido. Diferenciar losdistintos estados de ejecución del applet es importante porque algunos métodos puedengenerar excepciones si se llaman desde un estado erróneo. Por ejemplo, si el applet ha sidoarrancado y se detiene con stop(), si se intenta ejecutar el método start(), se generará unaexcepción IllegalThreadStateException.

Estados de un THREADDurante el ciclo de vida de un thread, éste se puede encontrar en diferentes estados. Lafigura siguiente muestra estos estados y los métodos que provocan el paso de un estado aotro. Este diagrama no es una máquina de estados finita, pero es lo que más se aproxima alfuncionamiento real de un thread .

Nuevo Thread

La siguiente sentencia crea un nuevo thread pero no lo arranca, lo deja en el estado de«Nuevo Thread»:

Thread MiThread = new MiClaseThread();

Cuando un thread está en este estado, es simplemente un objeto Thread vacío. El sistemano ha destinado ningún recurso para él. Desde este estado solamente puede arrancarsellamando al método start(), o detenerse definitivamente, llamando al método stop(); la llama-da a cualquier otro método carece de sentido y lo único que provocará será la generación deuna excepción de tipo IllegalThreadStateException.

218 Tutorial de Java

Ejecutable

Ahora veamos las dos línea de código que se presentan a continuación:

Thread MiThread = new MiClaseThread();MiThread.start();

La llamada al método start() creará los recursos del sistema necesarios para que el threadpuede ejecutarse, lo incorpora a la lista de procesos disponibles para ejecución del sistemay llama al método run() del thread. En este momento nos encontramos en el estado «Ejecu-table» del diagrama. Y este estado es Ejecutable y no En Ejecución, porque cuando elthread está aquí no esta corriendo. Muchos ordenadores tienen solamente un procesador loque hace imposible que todos los threads estén corriendo al mismo tiempo. Java implementaun tipo de scheduling o lista de procesos, que permite que el procesador sea compartidoentre todos los procesos o threads que se encuentran en la lista. Sin embargo, para nues-tros propósitos, y en la mayoría de los casos, se puede considerar que este estado esrealmente un estado «En Ejecución», porque la impresión que produce ante nosotros esque todos los procesos se ejecutan al mismo tiempo.

Cuando el thread se encuentra en este estado, todas las instrucciones de código que seencuentren dentro del bloque declarado para el método run(), se ejecutarán secuencialmente.

Parado

El thread entra en estado «Parado» cuando alguien llama al método suspend(), cuando sellama al método sleep(), cuando el thread está bloqueado en un proceso de entrada/salida ocuando el thread utiliza su método wait() para esperar a que se cumpla una determinadacondición. Cuando ocurra cualquiera de las cuatro cosas anteriores, el thread estará Para-do.

Por ejemplo, en el trozo de código siguiente:

Thread MiThread = new MiClaseThread();MiThread.start();try { MiThread.sleep( 10000 );} catch( InterruptedException e ) { ; }

la línea de código que llama al método sleep():

MiThread.sleep( 10000 );

hace que el thread se duerma durante 10 segundos. Durante ese tiempo, incluso aunque elprocesador estuviese totalmente libre, MiThread no correría. Después de esos 10 segundos.MiThread volvería a estar en estado «Ejecutable» y ahora sí que el procesador podría hacerlecaso cuando se encuentre disponible.

Para cada una de los cuatro modos de entrada en estado Parado, hay una forma específicade volver a estado Ejecutable. Cada forma de recuperar ese estado es exclusiva; por ejem-plo, si el thread ha sido puesto a dormir, una vez transcurridos los milisegundos que se

Tutorial de Java 219

especifiquen, él solo se despierta y vuelve a estar en estado Ejecutable. Llamar al métodoresume() mientras esté el thread durmiendo no serviría para nada.

Los métodos de recuperación del estado Ejecutable, en función de la forma de llegar alestado Parado del thread, son los siguientes:

· Si un thread está dormido, pasado el lapso de tiempo· Si un thread está suspendido, luego de una llamada al método resume()· Si un thread está bloqueado en una entrada/salida, una vez que el comando E/S

concluya su ejecución· Si un thread está esperando por una condición, cada vez que la variable que controla

esa condición varíe debe llamarse a notify() o notifyAll()

Muerto

Un thread se puede morir de dos formas: por causas naturales o porque lo maten (constop()). Un thread muere normalmente cuando concluye de forma habitual su método run().Por ejemplo, en el siguiente trozo de código, el bucle while es un bucle finito -realiza laiteración 20 veces y termina-:

public void run() { int i=0; while( i < 20 ) { i++; System.out.println( «i = «+i ); } }

Un thread que contenga a este método run(), morirá naturalmente después de que se com-plete el bucle y run() concluya.

También se puede matar en cualquier momento un thread, invocando a su método stop(). Enel trozo de código siguiente:

Thread MiThread = new MiClaseThread();MiThread.start();try { MiThread.sleep( 10000 );} catch( InterruptedException e ) { ; }MiThread.stop();

se crea y arranca el thread MiThread, lo dormimos durante 10 segundos y en el momento dedespertarse, la llamada a su método stop(), lo mata.

El método stop() envía un objeto ThreadDeath al thread que quiere detener. Así, cuando unthread es parado de este modo, muere asíncronamente. El thread morirá en el momento enque reciba la excepción ThreadDeath.

Los applets utilizarán el método stop() para matar a todos sus threads cuando el navegadorcon soporte Java en el que se están ejecutando le indica al applet que se detengan, por

220 Tutorial de Java

ejemplo, cuando se minimiza la ventana del navegador o cuando se cambia de página.

El método isAlive()

La interface de programación de la clase Thread incluye el método isAlive(), que devuelvetrue si el thread ha sido arrancado (con start()) y no ha sido detenido (con stop()). Por ello,si el método isAlive() devuelve false, sabemos que estamos ante un «Nuevo Thread» o anteun thread «Muerto». Si nos devuelve true, sabemos que el thread se encuentra en estado«Ejecutable» o «Parado». No se puede diferenciar entre «Nuevo Thread» y «Muerto», nientre un thread «Ejecutable» o «Parado».

SchedulingJava tiene un Scheduler , una lista de procesos, que monitoriza todos los threads que seestán ejecutando en todos los programas y decide cuales deben ejecutarse y cuales debenencontrarse preparados para su ejecución. Hay dos características de los threads que elscheduler identifica en este proceso de decisión. Una, la más importante, es la prioridad delthread; la otra, es el indicador de demonio. La regla básica del scheduler es que si solamen-te hay threads demonio ejecutándose, la Máquina Virtual Java (JVM) concluirá. Los nuevosthreads heredan la prioridad y el indicador de demonio de los threads que los han creado. Elscheduler determina qué threads deberán ejecutarse comprobando la prioridad de todos losthreads, aquellos con prioridad más alta dispondrán del procesador antes de los que tienenprioridad más baja.

El scheduler puede seguir dos patrones, preemptivo y no-preemptivo. Los schedulerspreemtivos proporcionan un segmento de tiempo a todos los threads que están corriendo enel sistema. El scheduler decide cual será el siguiente thread a ejecutarse y llama a resume()para darle vida durante un período fijo de tiempo. Cuando el thread ha estado en ejecuciónese período de tiempo, se llama a suspend() y el siguiente thread en la lista de procesosserá relanzado (resume()). Los schedulers no-preemtivos deciden que thread debe correr ylo ejecutan hasta que concluye. El thread tiene control total sobre el sistema mientras estéen ejecución. El método yield() es la forma en que un thread fuerza al scheduler a comenzarla ejecución de otro thread que esté esperando. Dependiendo del sistema en que esté co-rriendo Java, el scheduler será preemtivo o no-preemptivo.

En el siguiente ejemplo, SchThread.java, mostramos la ejecución de dos threads con dife-rentes prioridades. Un thread se ejecuta a prioridad más baja que el otro. Los threadsincrementarán sus contadores hasta que el thread que tiene prioridad más alta alcance alcontador que corresponde a la tarea con ejecución más lenta.

Tutorial de Java 221

Prioridades, demonios...

Prioridades

El scheduler determina el thread que debe ejecutarse en función de la prioridad asignada acada uno de ellos. El rango de prioridades oscila entre 1 y 10. La prioridad por defecto de unthread es Thread.NORM_PRIORITY, que tiene asignado un valor de 5. Hay otras dos varia-bles estáticas disponibles, que son Thread.MIN_PRORITY, fijada a 1, yThread.MAX_PRIORITY, aque tiene un valor de 10. El método getPriority() puede utilizarsepara conocer el valor actual de la prioridad de un thread.

Threads Demonio

Los threads demonio también se llaman servicios, porque se ejecutan, normalmente, conprioridad baja y proporcionan un servicio básico a un programa o programas cuando laactividad de la máquina es reducida. Un ejemplo de thread demonio que está ejecutándosecontinuamente es el recolector de basura (garbage collector). Este thread, proporcionadopor la Máquina Virtual Java, comprueba las variables de los programas a las que no seaccede nunca y libera estos recursos, devolviéndolos al sistema. Un thread puede fijar suindicador de demonio pasando un valor true al método setDaemon(). Si se pasa false a estemétodo, el thread será devuelto por el sistema como un thread de usuario. No obstante, estoúltimo debe realizarse antes de que se arranque el thread (start()).

Diferencia de threads con fork()

fork() en Unix crea un proceso hijo que tiene su propia copia de datos y código del padre.Esto funciona correctamente si estamos sobrados de memoria y disponemos de una CPUpoderosa, y siempre que mantengamos el número de procesos hijos dentro de un límitemanejable, porque se hace un uso intensivo de los recursos del sistema. Los applets Javano pueden lanzar ningún proceso en el cliente, porque eso sería una fuente de inseguridady no está permitido. Las aplicaciones y los applets deben utilizar threads.

La multi-tarea pre-emptiva tiene sus problemas. Un thread puede interrumpir a otro en cual-quier momento, de ahí lo de pre-emptive. Imaginarse lo que pasaría si un thread está escri-biendo en un array, mientras otro thread lo interrumpe y comienza a escribir en el mismoarray. Los lenguajes como C y C++ necesitan de las funciones lock() y unlock() para antes ydespués de leer o escribir datos. Java también funciona de este modo, pero oculta el blo-queo de datos bajo la sentencia synchronized:

synchronized int MiMetodo();

Otro área en que los threads son muy útiles es en los interfaces de usuario. Permiten incre-mentar la respuesta del ordenador ante el usuario cuando se encuentra realizando compli-cados cálculos y no puede atender a la entrada de usuario. Estos cálculos se pueden reali-

222 Tutorial de Java

zar en segundo plano, o realizar varios en primer plano (música y animaciones) sin que sedé apariencia de pérdida de rendimiento.

Ejemplo de animación

Este es un ejemplo de un applet, Animacion.java, que crea un thread de animación que nospresenta el globo terráqueo en rotación. Aquí podemos ver que estamos creando un threadde sí mismo, concurrencia . Además, animacion.start() llama al start() del thread, no delapplet, que automáticamente llamará a run():import java.awt.*;import java.applet.Applet;

public class Animacion extends Applet implements Runnable { Image imagenes[]; MediaTracker tracker; int indice = 0; Thread animacion;

int maxAncho,maxAlto; Image offScrImage; // Componente off-screen para doble buffering Graphics offScrGC;

// Nos indicará si ya se puede pintar boolean cargado = false;

// Inicializamos el applet, establecemos su tamaño y // cargamos las imágenes public void init() { // Establecemos el supervisor de imágenes tracker = new MediaTracker( this ); // Fijamos el tamaño del applet maxAncho = 100; maxAlto = 100; imagenes = new Image[36];

// Establecemos el doble buffer y dimensionamos el applet try { offScrImage = createImage( maxAncho,maxAlto ); offScrGC = offScrImage.getGraphics(); offScrGC.setColor( Color.lightGray ); offScrGC.fillRect( 0,0,maxAncho,maxAlto ); resize( maxAncho,maxAlto ); } catch( Exception e ) { e.printStackTrace(); }

// Cargamos las imágenes en un array for( int i=0; i < 36; i++ ) { String fichero = new String( «Tierra»+String.valueOf(i+1)+».gif» ); imagenes[i] = getImage( getDocumentBase(),fichero );

Tutorial de Java 223

// Registramos las imágenes con el tracker tracker.addImage( imagenes[i],i ); }

try { // Utilizamos el tracker para comprobar que todas las // imágenes están cargadas tracker.waitForAll(); } catch( InterruptedException e ) { ; } cargado = true; }

// Pintamos el fotograma que corresponda public void paint( Graphics g ) { if( cargado ) g.drawImage( offScrImage,0,0,this ); }

// Arrancamos y establecemos la primera imagen public void start() { if( tracker.checkID( indice ) ) offScrGC.drawImage( imagenes[indice],0,0,this ); animacion = new Thread( this ); animacion.start(); }

// Aquí hacemos el trabajo de animación // Muestra una imagen, para, muestra la siguiente... public void run() { // Obtiene el identificador del thread Thread thActual = Thread.currentThread();

// Nos aseguramos de que se ejecuta cuando estamos en un // thread y además es el actual while( animacion != null && animacion == thActual ) { if( tracker.checkID( indice ) ) { // Obtenemos la siguiente imagen offScrGC.drawImage( imagenes[indice],0,0,this ); indice++; // Volvemos al principio y seguimos, para el bucle if( indice >= imagenes.length ) indice = 0; }

// Ralentizamos la animación para que parezca normal try { animacion.sleep( 200 ); } catch( InterruptedException e ) { ; } // Pintamos el siguiente fotograma repaint();

224 Tutorial de Java

} } }

En el ejemplo podemos observar más cosas. La variable thActual es propia de cada threadque se lance, y la variable animacion la estarán viendo todos los threads. No hay duplicidadde procesos, sino que todos comparten las mismas variables; cada thread, sin embargo,tiene su pila local de variables, que no comparte con nadie y que son las que están declara-das dentro de las llaves del método run() .

La excepción InterruptedExcepcion salta en el caso en que se haya tenido al thread paradomás tiempo del debido. Es imprescindible recoger esta excepción cuando se estánimplementando threads, tanto es así, que en el caso de no recogerla, el compilador genera-rá un error.

Comunicaciones entre THREADSOtra clave para el éxito y la ventaja de la utilización de múltiples threads en una aplicación,o aplicación multithreaded, es que pueden comunicarse entre sí. Se pueden diseñar threadspara utilizar objetos comunes, que cada thread puede manipular independientemente de losotros threads.

El ejemplo clásico de comunicación de threads es un modelo productor/consumidor. Unthread produce una salida, que otro thread usa (consume), sea lo que sea esa salida. Vamosentonces a crear un productor, que será un thread que irá sacando caracteres por su salida;crearemos también un consumidor que ira recogiendo los caracteres que vaya sacando elproductor y un monitor que controlará el proceso de sincronización entre los threads. Fun-cionará como una tubería, insertando el productor caracteres en un extremos y leyéndolosel consumidor en el otro, con el monitor siendo la propia tubería.

Productor

El productor extenderá la clase Thread , y su código es el siguiente:

class Productor extends Thread { private Tuberia tuberia; private String alfabeto = «ABCDEFGHIJKLMNOPQRSTUVWXYZ»;

public Productor( Tuberia t ) {

Tutorial de Java 225

// Mantiene una copia propia del objeto compartido tuberia = t; }

public void run() { char c;

// Mete 10 letras en la tubería for( int i=0; i < 10; i++ ) { c = alfabeto.charAt( (int)(Math.random()*26 ) ); tuberia.lanzar( c ); // Imprime un registro con lo añadido System.out.println( «Lanzado «+c+» a la tuberia.» ); // Espera un poco antes de añadir más letras try { sleep( (int)(Math.random() * 100 ) ); } catch( InterruptedException e ) { ; } } } }

Notar que creamos una instancia de la clase Tuberia , y que se utiliza el método tuberia.lanzar()para que se vaya construyendo la tubería, en principio de 10 caracteres.

Consumidor

Veamos ahora el código del consumidor, que también extenderá la clase Thread :

class Consumidor extends Thread { private Tuberia tuberia;

public Consumidor( Tuberia t ) { // Mantiene una copia propia del objeto compartido tuberia = t; }

public void run() { char c;

// Consume 10 letras de la tubería for( int i=0; i < 10; i++ ) { c = tuberia.recoger(); // Imprime las letras retiradas System.out.println( «Recogido el caracter «+c ); // Espera un poco antes de coger más letras try { sleep( (int)(Math.random() * 2000 ) ); } catch( InterruptedException e ) { ; } } } }

226 Tutorial de Java

En este caso, como en el del productor, contamos con un método en la clase Tuberia ,tuberia.recoger(), para manejar la información.

Monitor

Una vez vistos el productor de la información y el consumidor, nos queda por ver qué es loque hace la clase Tuberia .

Lo que realiza la clase Tuberia , es una función de supervisión de las transacciones entre losdos threads, el productor y el consumidor. Los monitores, en general, son piezas muy impor-tantes de las aplicaciones multithreaded, porque mantienen el flujo de comunicación entrelos threads.

class Tuberia { private char buffer[] = new char[6]; private int siguiente = 0; // Flags para saber el estado del buffer private boolean estaLlena = false; private boolean estaVacia = true;

// Método para retirar letras del buffer public synchronized char recoger() { // No se puede consumir si el buffer está vacío while( estaVacia == true ) { try { wait(); // Se sale cuando estaVacia cambia a false } catch( InterruptedException e ) { ; } } // Decrementa la cuenta, ya que va a consumir una letra siguiente—; // Comprueba si se retiró la última letra if( siguiente == 0 ) estaVacia = true; // El buffer no puede estar lleno, porque acabamos de consumir estaLlena = false; notify();

// Devuelve la letra al thread consumidor return( buffer[siguiente] ); }

// Método para añadir letras al buffer public synchronized void lanzar( char c ) { // Espera hasta que haya sitio para otra letra while( estaLlena == true ) { try { wait(); // Se sale cuando estaLlena cambia a false } catch( InterruptedException e ) { ; } }

Tutorial de Java 227

// Añade una letra en el primer lugar disponible buffer[siguiente] = c; // Cambia al siguiente lugar disponible siguiente++; // Comprueba si el buffer está lleno if( siguiente == 6 ) estaLlena = true; estaVacia = false; notify(); } }

En la clase Tuberia vemos dos características importantes: los miembros dato (buffer[]) sonprivados, y los métodos de acceso (lanzar() y recoger()) son sincronizados.

Aquí vemos que la variable estaVacia es un semáforo, como los de toda la vida. La naturalezaprivada de los datos evita que el productor y el consumidor accedan directamente a éstos. Sise permitiese el acceso directo de ambos threads a los datos, se podrían producir proble-mas; por ejemplo, si el consumidor intenta retirar datos de un buffer vacío, obtendrá excep-ciones innecesarias, o se bloqueará el proceso.

Los métodos sincronizados de acceso impiden que los productores y consumidores corrom-pan un objeto compartido. Mientras el productor está añadiendo una letra a la tubería, elconsumidor no la puede retirar y viceversa. Esta sincronización es vital para mantener laintegridad de cualquier objeto compartido. No sería lo mismo sincronizar la clase en vez delos métodos, porque esto significaría que nadie puede acceder a las variables de la clase enparalelo, mientras que al sincronizar los métodos, sí pueden acceder a todas las variablesque están fuera de los métodos que pertenecen a la clase.

Se pueden sincronizar incluso variables, para realizar alguna acción determinada sobreellas, por ejemplo:

sincronized( p ) { // aquí se colocaría el código // los threads que estén intentando acceder a p se pararán // y generarán una InterruptedException }

El método notify() al final de cada método de acceso avisa a cualquier proceso que estéesperando por el objeto, entonces el proceso que ha estado esperando intentará acceder denuevo al objeto. En el método wait() hacemos que el thread se quede a la espera de que lellegue un notify(), ya sea enviado por el thread o por el sistema.

Ahora que ya tenemos un productor, un consumidor y un objeto compartido, necesitamosuna aplicación que arranque los threads y que consiga que todos hablen con el mismoobjeto que están compartiendo. Esto es lo que hace el siguiente trozo de código, del fuenteTubTest.java:

class TubTest { public static void main( String args[] ) { Tuberia t = new Tuberia(); Productor p = new Productor( t ); Consumidor c = new Consumidor( t );

228 Tutorial de Java

p.start(); c.start(); } }

Compilando y ejecutando esta aplicación, podremos observar nuestro modelo el pleno fun-cionamiento.

Monitorización del Productor

Los programas productor/consumidor a menudo emplean monitorización remota, que permi-te al consumidor observar el thread del productor interaccionando con un usuario o con otraparte del sistema. Por ejemplo, en una red, un grupo de threads productores podrían traba-jar cada uno en una workstation. Los productores imprimirían documentos, almacenandouna entrada en un registro (log). Un consumidor (o múltiples consumidores) podría procesarel registro y realizar durante la noche un informe de la actividad de impresión del día ante-rior.

Otro ejemplo, a pequeña escala podría ser el uso de varias ventanas en una workstation.Una ventana se puede usar para la entrada de información (el productor), y otra ventanareaccionaría a esa información (el consumidor).

Peer, es un observador general del sistema.

Tutorial de Java 229

Capítulo 11Código Nativo

Un método nativo es un método Java (una instancia de un objeto o una clase) cuyaimplementación se ha realizado en otro lenguaje de programación, por ejemplo, C. Vamos aver cómo se integran métodos nativos en clases Java. Actualmente, el lenguaje Java sola-mente proporciona mecanismos para integrar código C en programas Java.

Veamos pues los pasos necesarios para mezclar código nativo C y programas Java. Recu-rriremos (¡Cómo no!) a nuestro saludo; en este caso, el programa HolaMundo tiene dosclases Java: la primera implementa el método main() y la segunda, HolaMundo , tiene unmétodo nativo que presenta el mensaje de saludo. La implementación de este segundométodo la realizaremos en C.

Las acciones que debemos realizar, para conseguir que nuestra nueva versión del saludofuncione, serán las que desarrollaremos en las páginas siguientes del Tutorial.

Escribir código JavaEn primer lugar, debemos crear una clase Java, HolaMundo , que declare un método nativo.También debemos crear el programa principal que cree el objeto HolaMundo y llame almétodo nativo.

Las siguientes líneas de código definen la clase HolaMundo , que consta de un método y unsegmento estático de código:

class HolaMundo { public native void presentaSaludo(); static { System.loadLibrary( «hola» ); } }

Podemos decir que la implementación del método presentaSaludo() de la clase HolaMundoestá escrito en otro lenguaje, porque la palabra reservada native aparece como parte de ladefinición del método. Esta definición, proporciona solamente la definición parapresentaSaludo() y no porporciona ninguna implementación para él. La implementación laproporcionaremos desde un fichero fuente separado, escrito en lenguaje C.

La definición para presentaSaludo() también indica que el método es un método público, noacepta argumentos y no devuelve ningún valor. Al igual que cualquier otro método, los mé-

230 Tutorial de Java

todos nativos deben estar definidos dentro de una clase Java.

El código C que implementa el método presentaSaludo() debe ser compilado en una libreríadinámica y cargado en la clase Java que lo necesite. Esta carga, mapea la implementacióndel método nativo sobre su definición.

El siguiente bloque de código carga la librería dinámica, en este caso hola. El sistema Javaejecutará un bloque de código estático de la clase cuando la cargue.

Todo el código anterior forma parte del fichero HolaMundo.java, que contiene la claseHolaMundo . En un fichero separado, Main.java, vamos a crear una aplicación Java queinstancie a la clase HolaMundo y llame al método nativo presentaSaludo().

class Main { public static void main( String args[] ) { new HolaMundo().presentaSaludo(); } }

Como se puede observar, llamamos al método nativo del mismo modo que a cualquier otrométodo Java; añadimos el nombre del método al final del nombre del objeto con un punto(«.»). El conjunto de paréntesis que sigue al nombre del método encierra los argumentosque se le pasen. En este caso, el método presentaSaludo() no recibe ningún tipo de argu-mento.

Compilar el Código Java

Utilizaremos ahora el compilador javac para compilar el código Java que hemos desarrolla-do.

Compilaremos los dos ficheros fuentes de código Java que hemos creado, tecleando lossiguientes comandos:

> javac HolaMundo.java> javac Main.java

Crear el fichero de cabeceraAhora debemos utilizar la aplicación javah para conseguir el fichero de cabecera .h. Elfichero de cabecera define una estructura que representa la clase HolaMundo sobre códigoC y proporciona la definición de una función C para la implementación del método nativopresentaSaludo() definido en ese clase.

Ejecutamos javah sobre la clase HolaMundo , con el siguiente comando:

> javah HolaMundo

Por defecto, javah creará el nuevo fichero .h en el mismo directorio en que se encuentra elfichero .class, obtenido al compilar con javac el código fuente Java correspondiente a laclase. El fichero que creará, será un fichero de cabecera del mismo nombre que la clase ycon extensión .h. Por ejemplo, el comando anterior habrá creado el fichero HolaMundo.h,

Tutorial de Java 231

cuyo contenido será el siguiente:

/* DO NOT EDIT THIS FILE - it is machine generated */#include <native.h>/* Header for class HolaMundo */

#ifndef _Included_HolaMundo#define _Included_HolaMundo

typedef struct ClassHolaMundo { char PAD; /* ANSI C requires structures to have a least one member */} ClassHolaMundo;HandleTo(HolaMundo);

#ifdef __cplusplusextern «C» {#endif__declspec(dllexport) void HolaMundo_presentaSaludo(struct HHolaMundo *);#ifdef __cplusplus}#endif#endif

Este fichero de cabecera contiene la definición de una estructura llamada ClassHolaMundo.Los miembros de esta estructura son paralelos a los miembros de la clase Java correspon-diente; es decir, los campos en la estructura corresponden a las variables de la clase. Perocomo HolaMundo no tiene ninguna variable, la estructura se encuentra vacía. Se puedenutilizar los miembros de la estructura para referenciar a variables instanciadas de la clasedesde las funciones C.

Además de la estructura C similar a la clase Java, vemos que la llamada de la función C estádeclarada como:

extern void HolaMundo_presentaSaludo( struct HHolaMundo *);

Esta es la definición de la función C que deberemos escribir para implementar el métodonativo presentaSaludo() de la clase HolaMundo . Debemos utilizar esa definición cuando loimplementemos. Si HolaMundo llamase a otros métodos nativos, las definiciones de lasfunciones también aparecerían aquí.

El nombre de la función C que implementa el método nativo está derivado del nombre delpaquete, el nombre de la clase y el nombre del método nativo. Así, el método nativopresentaSaludo() dentro de la clase HolaMundo es HolaMundo_presentaSaludo(). En esteejemplo, no hay nombre de paquete porque HolaMundo se considera englobado dentro delpaquete por defecto.

La función C acepta un parámetro, aunque el método nativo definido en la clase Java noacepte ninguno. Se puede pensar en este parámetro como si fuese la variable this de C++.En nuestro caso, ignoramos el parámetro this.

232 Tutorial de Java

Crear el fichero de STUBSVolvemos a utilizar la aplicación javah para crear el fichero de stubs, que contiene todas lasdeclaraciones de métodos, con sus llamadas y argumentos, listos para que nosotros relle-nemos el cuerpo de los métodos con los algoritmos que necesitemos implementar. Propor-ciona la unión entre la clase Java y su estructura C paralela.

Para generar este fichero, debemos indicar el parámetro .stubs al ejecutar la aplicaciónjavah sobre la clase HolaMundo , de la siguiente forma:

> javah -stubs HolaMundo

Del mismo modo que se generaba el fichero .h; el nombre del fichero de stubs será elnombre de la clase con la extensión .c. En nuestro ejemplo, será HolaMundo.c, y su conte-nido será el siguiente:

/* DO NOT EDIT THIS FILE - it is machine generated */#include <StubPreamble.h>

/* Stubs for class HolaMundo *//* SYMBOL: «HolaMundo/presentaSaludo()V»,Java_HolaMundo_presentaSaludo_stub */__declspec(dllexport) stack_item*Java_HolaMundo_presentaSaludo_stub(stack_item *_P_,struct execenv *_EE_) { extern void HolaMundo_presentaSaludo(void *); (void) HolaMundo_presentaSaludo(_P_[0].p); return _P_;}

Escribir la función CEscribiremos la función C para el método nativo en un fichero fuente de código C. Laimplementación será una función habitual C, que luego integraremos con la clase Java. Ladefinición de la función C debe ser la misma que la que se ha generado con javah en elfichero HolaMundo.h.

La implementación que proponemos la guardaremos en el fichero HolaImp.c, y contendrálas siguientes línea de código:

#include <StubPreamble.h>#include «HolaMundo.h>#include <stdio.h>

void HolaMundo_presentaSaludo( struct HHolaMundo *this ) { printf( «Hola Mundo, desde el Tutorial de Java\n» ); return; }

Como se puede ver, la implementación no puede ser más sencilla: hace una llamada a lafunción printf() para presentar el saludo y sale.

En el código se incluyen tres ficheros de cabecera:

· StubsPreamble.h

Tutorial de Java 233

Proporciona la información para que el código C pueda interactuar con el sistemaJava. Cuando se escriben métodos nativos, siempre habrá que incluir este fichero enel código fuente C.

· HolaMundo.h

Es el fichero de cabecera que hemos generado para nuestra clase. Contiene la es-tructura C que representa la clase Java para la que estamos escribiendo el métodonativo y la definición de la función para ese método nativo.

· stdio.h

Es necesario incluirlo porque utilizamos la función printf() de la librería estándar de C,cuya declaración se encuentra en este fichero de cabecera.

Crear la librería dinámicaUtilizaremos el compilador C para compilar el fichero .h, el fichero de stubs y el ficherofuente .c; para crear una librería dinámica. Para crearla, utilizaremos el compilador C denuestro sistema, haciendo que los ficheros HolaMundo.c y HolaImp.c generen una libreríadinámica de nombre hola, que será la que el sistema Java cargue cuando ejecute la aplica-ción que estamos construyendo.

Vamos a ver cómo generamos esta librería en Unix y en Windows ’95.

Unix

Teclearemos el siguiente comando:

% cc -G HolaMundo.c HolaImp.c -o libhola.so

En caso de que no encuentre el compilador los ficheros de cabecera, se puede utilizar elflag -I para indicarle el camino de búsqueda, por ejemplo:

% cc -G -I$JAVA_HOME/include HolaMundo.c HolaImp.c -o libhola.so

donde $JAVA_HOME es el directorio donde se ha instalado la versión actual del JavaDevelopment Kit.

Windows ‘95

El comando a utilizar en este caso es el siguiente:

c:\>cl HolaMundo.c HolaImp.c -Fhola.dll -MD -LD javai.lib

Este comando funciona con Microsoft Visual C++ 2.x y posteriores. Si queremos indicar alcompilador donde se encuentran los ficheros de cabecera y las librerías, tendremos que fijardos variables de entorno:

c:\>SET INCLUDE=%JAVAHOME%\include;%INCLUDE%c:\>SET LIB=%JAVAHOME%\lib;%LIB%

234 Tutorial de Java

donde %JAVAHOME% es el directorio donde se ha instalado la versión actual del JavaDevelopment Kit.

Ejecutar el programaY, por fin, utilizaremos el intérprete de Java, java, para ejecutar el programa que hemosconstruido siguiendo todos los pasos anteriormente descritos. Si tecleamos el comando:

> java Main

obtendremos el resultado siguiente:

% Hola Mundo, desde el Tutorial de Java

Si no aparece este mensaje de saludo y lo que aparece en pantalla son expresiones comoUnsatisfiedLinkError , es porque no tenemos fijado correctamente el camino de la libreríadinámica que hemos generado. Este camino es la lista de directorios que el sistema Javautilizará para buscar las librerías que debe cargar. Debemos asegurarnos de que el directo-rio donde se encuentra nuestra librería hola recién creada, figura entre ellos.

Si fijamos el camino correcto y ejecutamos de nuevo el programa, veremos que ahora síobtenemos el mensaje de saludo que esperábamos.

Con ello, hemos visto como integrar código C en programas Java. Quedan muchas cuestio-nes por medio, como la equivalencia de tipos entre Java y C, el paso de parámetros, elmanejo de cadenas, etc. Pero eso supondría entrar en mucha más profundidad dentro deJava de la que aquí pretendemos (por ahora).

Tutorial de Java 235

Capítulo 12Entrada/Salida Estándar

Los usuarios de Unix, y aquellos familiarizados con las líneas de comandos de otros siste-mas como DOS, han utilizado un tipo de entrada/salida conocida comúnmente por entrada/salida estándar. El fichero de entrada estándar (stdin ) es simplemente el teclado. El ficherode salida estándar (stdout ) es típicamente la pantalla (o la ventana del terminal). El ficherode salida de error estándar (stderr ) también se dirige normalmente a la pantalla, pero seimplementa como otro fichero de forma que se pueda distinguir entre la salida normal y (sies necesario) los mensajes de error.

La clase System

Java tiene acceso a la entrada/salida estándar a través de la clase System . En concreto, lostres ficheros que se implementan son:

Stdin

System.in implementa stdin como una instancia de la clase InputStream . Con System.in, seaccede a los métodos read() y skip(). El método read() permite leer un byte de la entrada.skip( long n ), salta n bytes de la entrada.

Stdout

System.out implementa stdout como una instancia de la clase PrintStream . Se puedenutilizar los métodos print() y println() con cualquier tipo básico Java como argumento.

Stderr

System.err implementa stderr de la misma forma que stdout. Como con System.out, se tieneacceso a los métodos de PrintStream.

Vamos a ver un pequeño ejemplo de entrada/salida en Java. El código siguiente, miType.java,reproduce, o funciona como la utilidad cat de Unix o type de DOS:

import java.io.*;

236 Tutorial de Java

class miType { public static void main( String args[] ) throws IOException { int c; int contador = 0;

while( (c = System.in.read() ) != ‘\n’ ) { contador++; System.out.print( (char)c ); } System.out.println(); // Línea en blanco System.err.println( «Contados «+ contador +» bytes en total.» ); } }

Clases comunes de Entrada/Salida

Además de la entrada por teclado y salida por pantalla, se necesita entrada/salida por fiche-ro, como son:

FileInputStream DataInputStream FileOutputStream DataOutputStream

También existen otras clases para aplicaciones más específicas, que no vamos a tratar, porser de un uso muy concreto:

PipedInputStream BufferedInputStream PushBackInputStream StreamTokenizer PipedOutputStream BufferedOutputStream

Tutorial de Java 237

Capítulo 13Ficheros en Java

Todos los lenguajes de programación tienen alguna forma de interactuar con los sistemasde ficheros locales; Java no es una excepción.

Cuando se desarrollan applets para utilizar en red, hay que tener en cuenta que la entrada/salida directa a fichero es una violación de seguridad de acceso. Muchos usuarios configu-rarán sus navegadores para permitir el acceso al sistema de ficheros, pero otros no.

Por otro lado, si se está desarrollando una aplicación Java para uso interno, probablementeserá necesario el acceso directo a ficheros.

Ficheros

Antes de realizar acciones sobre un fichero, necesitamos un poco de información sobre esefichero. La clase File proporciona muchas utilidades relacionadas con ficheros y con laobtención de información básica sobre esos ficheros.

Creación de un objeto File

Para crear un objeto File nuevo, se puede utilizar cualquiera de los tres constructores si-guientes:

File miFichero; miFichero = new File( «/etc/kk» );

o

miFichero = new File( «/etc»,»kk» );

o

File miDirectorio = new File( «/etc» ); miFichero = new File( miDirectorio,»kk» );

El constructor utilizado depende a menudo de otros objetos File necesarios para el acceso.Por ejemplo, si sólo se utiliza un fichero en la aplicación, el primer constructor es el mejor. Sien cambio, se utilizan muchos ficheros desde un mismo directorio, el segundo o tercer cons-tructor serán más cómodos. Y si el directorio o el fichero es una variable, el segundo cons-tructor será el más útil.

238 Tutorial de Java

Comprobaciones y Utilidades

Una vez creado un objeto File, se puede utilizar uno de los siguientes métodos para reunirinformación sobre el fichero:

· Nombres de fichero String getName() String getPath() String getAbsolutePath() String getParent() boolean renameTo( File nuevoNombre )

· Comprobaciones boolean exists() boolean canWrite() boolean canRead() boolean isFile() boolean isDirectory() boolean isAbsolute()

· Información general del fichero long lastModified() long length()

· Utilidades de directorio boolean mkdir() String[] list()

Vamos a desarrollar una pequeña aplicación que muestra información sobre los ficherospasados como argumentos en la línea de comandos, InfoFichero.java:

import java.io.*;

class InfoFichero {

public static void main( String args[] ) throws IOException { if( args.length > 0 ) { for( int i=0; i < args.length; i++ ) { File f = new File( args[i] ); System.out.println( «Nombre: «+f.getName() ); System.out.println( «Camino: «+f.getPath() ); if( f.exists() ) { System.out.print( «Fichero existente « ); System.out.print( (f.canRead() ? « y se puede Leer» : «» ) ); System.out.print( (f.canWrite() ? « y se puese Escribir» : «» ) ); System.out.println( «.» ); System.out.println( «La longitud del fichero son «+ f.length()+» bytes» ); } else System.out.println( «El fichero no existe.» ); } } else

Tutorial de Java 239

System.out.println( «Debe indicar un fichero.» ); } }

Streams de EntradaHay muchas clases dedicadas a la obtención de entrada desde un fichero. Este es el esque-ma de la jerarquía de clases de entrada por fichero:

Objetos FileInputStream

Los objetos FileInputStream típicamente representan ficheros de texto accedidos en ordensecuencial, byte a byte. Con FileInputStream, se puede elegir acceder a un byte, variosbytes o al fichero completo.

Apertura de un FileInputStream

Para abrir un FileInputStream sobre un fichero, se le da al constructor un String o un objetoFile:

FileInputStream mi FicheroSt; miFicheroSt = new FileInputStream( «/etc/kk» );

También se puede utilizar:

File miFichero FileInputStream miFicheroSt; miFichero = new File( «/etc/kk» ); miFicheroSt = new FileInputStream( miFichero );

240 Tutorial de Java

Lectura de un FileInputStream

Una vez abierto el FileInputStream, se puede leer de él. El método read() tiene muchasopciones:

int read();

Lee un byte y devuelve -1 al final del stream.

int read( byte b[] );

Llena todo el array, si es posible. Devuelve el número de bytes leídos o -1 si sealcanzó el final del stream.

int read( byte b[],int offset,int longitud );

Lee longitud bytes en b comenzando por b[offset]. Devuelve el número de bytes leídoso -1 si se alcanzó el final del stream.

Cierre de FileInputStream

Cuando se termina con un fichero, existen dos opciones para cerrarlo: explícitamente, oimplícitamente cuando se recicla el objeto (el garbage collector se encarga de ello).

Para cerrarlo explícitamente, se utiliza el método close():

miFicheroSt.close();

Ejemplo: Visualización de un fichero

Si la configuración de la seguridad de Java permite el acceso a ficheros, se puede ver elcontenido de un fichero en un objeto TextArea. El código siguiente contiene los elementosnecesarios para mostrar un fichero:

FileInputStream fis; TextArea ta;

public void init() { byte b[] = new byte[1024]; int i;

// El buffer de lectura se debe hacer lo suficientemente grande // o esperar a saber el tamaño del fichero String s;

try { fis = new FileInputStream( «/etc/kk» ); } catch( FileNotFoundException e ) { /* Hacer algo */ }

try {

Tutorial de Java 241

i = fis.read( b ); } catch( IOException e ) { /* Hacer algo */ }

s = new String( b,0 ); ta = new TextArea( s,5,40 ); add( ta ); }

Hemos desarrollado un ejemplo, Agenda.java, en el que partimos de un fichero agenda quedispone de los datos que nosotros deseamos de nuestros amigos, como son: nombre, telé-fono y dirección. Si tecleamos un nombre, buscará en el fichero de datos si existe esenombre y presentará la información que se haya introducido. Para probar, intentar que apa-rezca la información de Pepe.

Objetos DataInputStream

Los objetos DataInputStream se comportan como los FileInputStream. Los streams de datospueden leer cualquiera de las variables de tipo nativo, como floats, ints o chars. General-mente se utilizan DataInputStream con ficheros binarios.

Apertura y cierre de DataInputStream

Para abrir y cerrar un objeto DataInputStream, se utilizan los mismos métodos que paraFileInputStream:

DataInputStream miDStream;FileInputStream miFStream;

// Obtiene un controlador de ficheromiFStream = new FileInputStream «/etc/ejemplo.dbf» );//Encadena un fichero de entrada de datosmiDStream = new DataInputStream( miFStream );

// Ahora se pueden utilizar los dos streams de entrada para// acceder al fichero (si se quiere...)miFStream.read( b );i = miDStream.readInt();

// Cierra el fichero de datos explícitamente//Siempre se cierra primero el fichero stream de mayor nivelmiDStream.close();miFStream.close();

Lectura de un DataInputStream

Al acceder a un fichero como DataInputStream, se pueden utilizar los mismos métodos read()de los objetos FileInputStream. No obstante, también se tiene acceso a otros métodos dise-

242 Tutorial de Java

ñados para leer cada uno de los tipos de datos:

byte readByte() int readUnsignedByte() short readShort() int readUnsignedShort() char readChar() int readInt() long readLong() float readFloat() double readDouble() String readLine()

Cada método leerá un objeto del tipo pedido.

Para el método String readLine(), se marca el final de la cadena con \n, \r, \r\n o con EOF.

Para leer un long, por ejemplo:

long numeroSerie; ... numeroSerie = miDStream.readLong();

Streams de entrada de URLs

Además del acceso a ficheros, Java proporciona la posibilidad de acceder a URLs como unaforma de acceder a objetos a través de la red. Se utiliza implícitamente un objeto URL alacceder a sonidos e imágenes, con el método getDocumentBase() en los applets:

String imagenFich = new String( «imagenes/pepe.gif» ); imagenes[0] = getImage( getDocumentBase(),imagenFich );

No obstante, se puede proporcionar directamente un URL, si se quiere:

URL imagenSrc; imagenSrc = new URL( «http://enterprise.com/~info» ); imagenes[0] = getImage( imagenSrc,»imagenes/pepe.gif» );

Apertura de un Stream de entrada de URL

También se puede abrir un stream de entrada a partir de un URL. Por ejemplo, se puedeutilizar un fichero de datos para un applet:

ImputStream is; byte buffer[] = new byte[24]; is = new URL( getDocumentBase(),datos).openStream();

Ahora se puede utilizar is para leer información de la misma forma que se hace con unobjeto FileInputStream:

is.read( buffer,0,buffer.length );

NOTA: Debe tenerse muy en cuenta que algunos usuarios pueden haber configuradola seguridad de sus navegadores para que los applets no accedan a ficheros .

Tutorial de Java 243

Streams de SalidaLa contrapartida necesaria de la lectura de datos es la escritura de datos. Como con losStreams de entrada, las clases de salida están ordenadas jerárquicamente:

Examinaremos las clases FileOutputStream y DataOutputStream para complementar losstreams de entrada que se han visto. En los ficheros fuente del directorio $JAVA_HOME/src/java/io se puede ver el uso y métodos de estas clases, así como de los streams de entrada($JAVA_HOME es el directorio donde se haya instalado el Java Development Kit, en sistemasUNIX).

Objetos FileOutputStream

Los objetos FileOutputStream son útiles para la escritura de ficheros de texto. Como con losficheros de entrada, primero se necesita abrir el fichero para luego escribir en él.

Apertura de un FileOutputStream

Para abrir un objeto FileOutputStream, se tienen las mismas posibilidades que para abrir unfichero stream de entrada. Se le da al constructor un String o un objeto File.

FileOutputStream miFicheroSt; miFicheroSt = new FileOutputStream( «/etc/kk» );

Como con los streams de entrada, también se puede utilizar:

File miFichero FileOutputStream miFicheroSt;

miFichero = new File( «/etc/kk» ); miFicheroSt = new FileOutputStream( miFichero );

Escritura en un FileOutputStream

Una vez abierto el fichero, se pueden escribir bytes de datos utilizando el método write().Como con el método read() de los streams de entrada, tenemos tres posibilidades:

void write( int b );

244 Tutorial de Java

Escribe un byte.

void write( byte b[] );

Escribe todo el array, si es posible.

void write( byte b[],int offset,int longitud );

Escribe longitud bytes en b comenzando por b[offset].

Cierre de FileOutputStream

Cerrar un stream de salida es similar a cerrar streams de entrada. Se puede utilizar elmétodo explícito:

miFicheroSt.close();

O, se puede dejar que el sistema cierre el fichero cuando se recicle miFicheroSt.

Ejemplo: Almacenamiento de Información

Este programa, Telefonos.java, pregunta al usuario una lista de nombres y números de telé-fono. Cada nombre y número se añade a un fichero situado en una localización fija. Paraindicar que se ha introducido toda la lista, el usuario especifica «Fin» ante la solicitud deentrada del nombre.

Una vez que el usuario ha terminado de teclear la lista, el programa creará un fichero desalida que se mostrará en pantalla o se imprimirá. Por ejemplo:

95-4751232,Juanito 564878,Luisa 123456,Pepe 347698,Antonio 91-3547621,Maria

El código fuente del programa es el siguiente:

import java.io.*;

class Telefonos { static FileOutputStream fos; public static final int longLinea = 81;

public static void main( String args[] ) throws IOException { byte tfno[] = new byte[longLinea]; byte nombre[] = new byte[longLinea];

fos = new FileOutputStream( «telefono.dat» ); while( true ) { System.err.println( «Teclee un nombre (‘Fin’ termina)» ); leeLinea( nombre ); if( «fin».equalsIgnoreCase( new String( nombre,0,0,3 ) ) )

Tutorial de Java 245

break;

System.err.println( «Teclee el numero de telefono» ); leeLinea( tfno ); for( int i=0; tfno[i] != 0; i++ ) fos.write( tfno[i] ); fos.write( ‘,’ ); for( int i=0; nombre[i] != 0; i++ ) fos.write( nombre[i] ); fos.write( ‘\n’ ); } fos.close(); }

private static void leeLinea( byte linea[] ) throws IOException { int b = 0; int i = 0;

while( (i < ( longLinea-1) ) && ( ( b = System.in.read() ) != ‘\n’ ) ) linea[i++] = (byte)b; linea[i] = (byte)0; } }

Streams de salida con buffer

Si se trabaja con gran cantidad de datos, o se escriben muchos elementos pequeños, seráuna buena idea utilizar un stream de salida con buffer. Los streams con buffer ofrecen losmismos métodos de la clase FileOutputStream , pero toda salida se almacena en un buffer.Cuando se llena el buffer, se envía a disco con una única operación de escritura; o, en casonecesario, se puede enviar el buffer a disco en cualquier momento.

Creación de Streams de salida con buffer

Para crear un stream BufferedOutput, primero se necesita un stream FileOutput normal;entonces se le añade un buffer al stream:

FileOutputStream miFileStream; BufferdOutpurStream miBufferStream; // Obtiene un controlador de fichero miFileStream = new FileOutputStream( «/tmp/kk» ); // Encadena un stream de salida con buffer miBufferStream = new BufferedOutputStream( miFileStream );

Volcado y Cierre de Streams de salida con buffer

Al contrario que los streams FileOutput, cada escritura al buffer no se corresponde con unaescritura en disco. A menos que se llene el buffer antes de que termine el programa, cuandose quiera volcar el buffer explícitamente se debe hacer mediante una llamada a flush():

246 Tutorial de Java

// Se fuerza el volcado del buffer a disco miBufferStream.flush(); // Cerramos el fichero de datos. Siempre se ha de cerrar primero el // fichero stream de mayor nivel miBufferStream.close(); miFileStream.close();

Streams DataOutput

Java también implementa una clase de salida complementaria a la clase DataInputStream .Con la clase DataOutputStream, se pueden escribir datos binarios en un fichero.

Apertura y cierre de objetos DataOutputStream

Para abrir y cerrar objetos DataOutputStream, se utilizan los mismos métodos que para losobjetos FileOutputStream:

DataOutputStream miDataStream; FileOutputStream miFileStream; BufferedOutputStream miBufferStream;

// Obtiene un controlador de fichero miFileStream = new FileOutputStream( «/tmp/kk» ); // Encadena un stream de salida con buffer (por eficiencia) miBufferStream = new BufferedOutputStream( miFileStream ); // Encadena un fichero de salida de datos miDataStream = new DataOutputStream( miBufferStream );

// Ahora se pueden utilizar los dos streams de entrada para // acceder al fichero (si se quiere) miBufferStream.write( b ); miDataStream.writeInt( i );

// Cierra el fichero de datos explícitamente. Siempre se cierra // primero el fichero stream de mayor nivel miDataStream.close(); miBufferStream.close(); miFileStream.close();

Escritura en un objeto DataOutputStream

Cada uno de los métodos write() accesibles por los FileOutputStream también lo son através de los DataOutputStream. También encontrará métodos complementarios a los deDataInputStream:

void writeBoolean( boolean b ); void writeByte( int i ); void writeShort( int i ); void writeChar( int i ); void writeInt( int i ); void writeFloat( float f );

Tutorial de Java 247

void writeDouble( double d ); void writeBytes( String s ); void writeChars( string s );

Para las cadenas, se tienen dos posibilidades: bytes y caracteres. Hay que recordar que losbytes son objetos de 8 bits y los caracteres lo son de 16 bits. Si nuestras cadenas utilizancaracteres Unicode, debemos escribirlas con writeChars().

Contabilidad de la salida

Otra función necesaria durante la salida es el método size(). Este método simplemente de-vuelve el número total de bytes escritos en el fichero. Se puede utilizar size() para ajustar eltamaño de un fichero a múltiplo de cuatro. Por ejemplo, de la forma siguiente:

. . . int numBytes = miDataStream.size() % 4; for( int i=0; i < numBytes; i++ ) miDataStream.write( 0 ); . . .

Ficheros de acceso aleatorioA menudo, no se desea leer un fichero de principio a fin; sino acceder al fichero como unabase de datos, donde se salta de un registro a otro; cada uno en diferentes partes delfichero. Java proporciona una clase RandomAccessFile para este tipo de entrada/salida.

Creación de un Fichero de Acceso Aleatorio

Hay dos posibilidades para abrir un fichero de acceso aleatorio:

Con el nombre del fichero:

miRAFile = new RandomAccessFile( String nombre,String modo );

Con un objeto File:

miRAFile = new RandomAccessFile( File fichero,String modo );

El argumento modo determina si se tiene acceso de sólo lectura (r) o de lectura/escritura (r/w). Por ejemplo, se puede abrir un fichero de una base de datos para actualización:

RandomAccessFile miRAFile; miRAFile = new RandomAccessFile( «/tmp/kk.dbf»,»rw» );

Acceso a la Información

Los objetos RandomAccessFile esperan información de lectura/escritura de la misma mane-ra que los objetos DataInput/DataOutput. Se tiene acceso a todas las operaciones read() ywrite() de las clases DataInputStream y DataOutputStream .

248 Tutorial de Java

También se tienen muchos métodos para moverse dentro de un fichero:

long getFilePointer();

Devuelve la posición actual del puntero del fichero

void seek( long pos );

Coloca el puntero del fichero en una posición determinada. La posición se da como undesplazamiento en bytes desde el comienzo del fichero. La posición 0 marca el comienzo deese fichero.

long length();

Devuelve la longitud del fichero. La posición length() marca el final de ese fichero.

Actualización de Información

Se pueden utilizar ficheros de acceso aleatorio para añadir información a ficheros existen-tes:

miRAFile = new RandomAccessFile( «/tmp/kk.log»,»rw» ); miRAFile.seek( miRAFile.length() ); // Cualquier write() que hagamos a partir de este punto del código // añadirá información al fichero

Vamos a ver un pequeño ejemplo, Log.java, que añadeuna cadena a un fichero existente:

import java.io.*;

// Cada vez que ejecutemos este programita, se incorporara una nueva// linea al fichero de log que se crea la primera vez que se ejecuta//class Log { public static void main( String args[] ) throws IOException { RandomAccessFile miRAFile; String s = «Informacion a incorporar\nTutorial de Java\n»;

// Abrimos el fichero de acceso aleatorio miRAFile = new RandomAccessFile( «/tmp/java.log»,»rw» ); // Nos vamos al final del fichero miRAFile.seek( miRAFile.length() ); // Incorporamos la cadena al fichero miRAFile.writeBytes( s ); // Cerramos el fichero miRAFile.close(); } }

Tutorial de Java 249

Capítulo 14Comunicaciones en Java

En este capítulo no nos vamos a extender demasiado en profundidades sobre la comunica-ción y funcionamiento de redes, aunque sí proporcionaremos un breve baño inicial parasentar, o recordar, los fundamentos de la comunicación en red, tomando como base Unix.Presentaremos un ejemplo básico de cliente/servidor sobre sockets TCP/IP, proporcionan-do un punto de partida para el desarrollo de otras aplicaciones cliente/servidor basadas ensockets, que posteriormente implementaremos.

Comunicaciones en UNIXEl sistema de Entrada/Salida de Unix sigue el paradigma que normalmente se designa comoAbrir-Leer-Escribir-Cerrar. Antes de que un proceso de usuario pueda realizar operacionesde entrada/salida, debe hacer una llamada a Abrir (open ) para indicar, y obtener permisospara su uso, el fichero o dispositivo que quiere utilizar. Una vez que el objeto está abierto, elproceso de usuario realiza una o varias llamadas a Leer (read ) y Escribir (write ), para con-seguir leer y escribir datos. Leer coge datos desde el objeto y los transfiere al proceso deusuario, mientras que Escribir transfiere datos desde el proceso de usuario al objeto. Unavez que todos estos intercambios de información estén concluidos, el proceso de usuariollamará a Cerrar (close ) para informar al sistema operativo que ha finalizado la utilizacióndel objeto que antes había abierto.

Cuando se incorporan las características a Unix de comunicación entre procesos (IPC) y elmanejo de redes, la idea fue implementar la interface con IPC similar a la que se estabautilizando para la entrada/salida de ficheros, es decir, siguiendo el paradigma del párrafoanterior. En Unix, un proceso tiene un conjunto de descriptores de entrada/salida desdedonde Leer y por donde Escribir. Estos descriptores pueden estar referidos a ficheros, dis-positivos, o canales de comunicaciones (sockets). El ciclo de vida de un descriptor, aplicadoa un canal de comunicación (socket), está determinado por tres fases (siguiendo el paradig-ma):

· Creación, apertura del socket· Lectura y Escritura, recepción y envío de datospor el socket· Destrucción, cierre del socket

La interface IPC en Unix-BSD está implementada sobre los protocolos de red TCP y UDP.Los destinatarios de los mensajes se especifican como direcciones de socket; cada direc-ción de socket es un identificador de comunicación que consiste en una dirección Internet yun número de puerto.

250 Tutorial de Java

Las operaciones IPC se basan en pares de sockets. Se intercambian información transmi-tiendo datos a través de mensajes que circulan entre un socket en un proceso y otro socketen otro proceso. Cuando los mensajes son enviados, se encolan en el socket hasta que elprotocolo de red los haya transmitido. Cuando llegan, los mensajes son encolados en elsocket de recepción hasta que el proceso que tiene que recibirlos haga las llamadas nece-sarias para recoger esos datos.

SocktesLos sockets son puntos finales de enlaces de comunicaciones entre procesos. Los procesoslos tratan como descriptores de ficheros, de forma que se pueden intercambiar datos conotros procesos transmitiendo y recibiendo a través de sockets.

El tipo de sockets describe la forma en la que se transfiere información a través de esesocket.

Sockets Stream (TCP, Transport Control Protocol)

Son un servicio orientado a conexión donde los datos se transfieren sin encuadrarlos enregistros o bloques. Si se rompe la conexión entre los procesos, éstos serán informados.

El protocolo de comunicaciones con streams es un protocolo orientado a conexión, ya quepara establecer una comunicación utilizando el protocolo TCP, hay que establecer en primerlugar una conexión entre un par de sockets. Mientras uno de los sockets atiende peticionesde conexión (servidor), el otro solicita una conexión (cliente). Una vez que los dos socketsestén conectados, se pueden utilizar para transmitir datos en ambas direcciones.

Sockets Datagrama (UDP, User Datagram Protocol)

Son un servicio de transporte sin conexión. Son más eficientes que TCP, pero no está ga-rantizada la fiabilidad. Los datos se envían y reciben en paquetes, cuya entrega no estágarantizada. Los paquetes pueden ser duplicados, perdidos o llegar en un orden diferente alque se envió.

El protocolo de comunicaciones con datagramas es un protocolo sin conexión, es decir,cada vez que se envíen datagramas es necesario enviar el descriptor del socket local y ladirección del socket que debe recibir el datagrama. Como se puede ver, hay que enviardatos adicionales cada vez que se realice una comunicación.

Sockets Raw

Son sockets que dan acceso directo a la capa de software de red subyacente o a protocolosde más bajo nivel. Se utilizan sobre todo para la depuración del código de los protocolos.

Tutorial de Java 251

Diferencias entre Sockets Stream y Datagrama

Ahora se nos presenta un problema, ¿qué protocolo, o tipo de sockets, debemos usar - UDPo TCP? La decisión depende de la aplicación cliente/servidor que estemos escribiendo.Vamos a ver algunas diferencias entre los protocolos para ayudar en la decisión.

En UDP, cada vez que se envía un datagrama, hay que enviar también el descriptor delsocket local y la dirección del socket que va a recibir el datagrama, luego éstos son másgrandes que los TCP. Como el protocolo TCP está orientado a conexión, tenemos que esta-blecer esta conexión entre los dos sockets antes de nada, lo que implica un cierto tiempoempleado en el establecimiento de la conexión, que no existe en UDP.

En UDP hay un límite de tamaño de los datagramas, establecido en 64 kilobytes, que sepueden enviar a una localización determinada, mientras que TCP no tiene límite; una vezque se ha establecido la conexión, el par de sockets funciona como los streams: todos losdatos se leen inmediatamente, en el mismo orden en que se van recibiendo.

UDP es un protocolo desordenado, no garantiza que los datagramas que se hayan enviadosean recibidos en el mismo orden por el socket de recepción. Al contrario, TCP es un proto-colo ordenado, garantiza que todos los paquetes que se envíen serán recibidos en el socketdestino en el mismo orden en que se han enviado.

Los datagramas son bloques de información del tipo lanzar y olvidar. Para la mayoría de losprogramas que utilicen la red, el usar un flujo TCP en vez de un datagrama UDP es mássencillo y hay menos posibilidades de tener problemas. Sin embargo, cuando se requiere unrendimiento óptimo, y está justificado el tiempo adicional que supone realizar la verificaciónde los datos, los datagramas son un mecanismo realmente útil.

En resumen, TCP parece más indicado para la implementación de servicios de red como uncontrol remoto (rlogin, telnet) y transmisión de ficheros (ftp); que necesitan transmitir datosde longitud indefinida. UDP es menos complejo y tiene una menor sobrecarga sobre laconexión; esto hace que sea el indicado en la implementación de aplicaciones cliente/servi-dor en sistemas distribuidos montados sobre redes de área local.

Uso de socketsPodemos pensar que un Servidor Internet es un conjunto de sockets que proporciona capa-cidades adicionales del sistema, los llamados servicios.

Puertos y Servicios

Cada servicio está asociado a un puerto. Un puerto es una dirección numérica a través de lacual se procesa el servicio. Sobre un sistema Unix, los servicios que proporciona ese siste-ma se indican en el fichero /etc/services, y algunos ejemplos son:

252 Tutorial de Java

daytime 13/udp ftp 21/tcp telnet 23/tcp telnet smtp 25/tcp mail http 80/tcp

La primera columna indica el nombre del servicio. La segunda columna indica el puerto y elprotocolo que está asociado al servicio. La tercera columna es un alias del servicio; porejemplo, el servicio smtp, también conocido como mail, es la implementación del servicio decorreo electrónico.

Las comunicaciones de información relacionada con Web tienen lugar a través del puerto80 mediante protocolo TCP. Para emular esto en Java, usaremos la clase Socket . La fecha(daytime). Sin embargo, el servicio que coge la fecha y la hora del sistema, está ligado alpuerto 13 utilizando el protocolo UDP. Un servidor que lo emule en Java usaría un objetoDatagramSocket .

La clase URLLa clase URL contiene contructores y métodos para la manipulación de URL (UniversalResource Locator): un objeto o servicio en Internet. El protocolo TCP necesita dos tipos deinformación: la dirección IP y el número de puerto. Vamos a ver como podemos recibir puesla página Web principal de nuestro buscador favorito al teclear:

http://www.yahoo.com

En primer lugar, Yahoo tiene registrado su nombre, permitiendo que se use yahoo.com comosu dirección IP, o lo que es lo mismo, cuando indicamos yahoo.com es como si hubiesemosindicado 205.216.146.71, su dirección IP real.

La verdad es que la cosa es un poco más complicada que eso. Hay un servicio, el DNS(Domain Name Service), que traslada www.yahoo.com a 205.216.146.71, lo que nos permi-te teclear www.yahoo.com, en lugar de tener que recordar su dirección IP.

Si queremos obtener la dirección IP real de la red en que estamos corriendo, podemosrealizar llamadas a los métodos getLocalHost() y getAddress(). Primero, getLocalHost() nosdevuelve un objeto iNetAddress , que si usamos con getAddress() generará un array conlos cuatro bytes de la dirección IP, por ejemplo:

InetAddress direccion = InetAddress.getLocalHost(); byte direccionIp[] = direccion.getAddress();

Si la dirección de la máquina en que estamos corriendo es 150.150.112.145, entonces:

direccionIp[0] = 150 direccionIp[1] = 150 direccionIp[2] = 112 direccionIp[3] = 145

Una cosa interesante en este punto es que una red puede mapear muchas direcciones IP.Esto puede ser necesario para un Servidor Web, como Yahoo, que tiene que soportar gran-des cantidades de tráfico y necesita más de una dirección IP para poder atender a todo esetráfico. El nombre interno para la dirección 205.216.146.71, por ejemplo, es www7.yahoo.com.

Tutorial de Java 253

El DNS puede trasladar una lista de direcciones IP asignadas a Yahoo en www.yahoo.com.Esto es una cualidad útil, pero por ahora abre un agujero en cuestión de seguridad.

Ya conocemos la dirección IP, nos falta el número del puerto. Si no se indica nada, seutilizará el que se haya definido por defecto en el fichero de configuración de los serviciosdel sistema. En Unix se indican en el fichero /etc/services, en Windows-NT en el ficheroservices y en otros sistemas puede ser diferente.

El puerto habitual de los servicios Web es el 80, así que si no indicamos nada, entraremosen el servidor de Yahoo por el puerto 80. Si tecleamos la URL siguiente en un navegador:

http://www.yahoo.com:80

también recibiremos la página principal de Yahoo. No hay nada que nos impida cambiar elpuerto en el que residirá el servidor Web; sin embargo, el uso del puerto 80 es casi estándar,porque elimina pulsaciones en el teclado y, además, las direcciones URL son lo suficiente-mente difíciles de recordar como para añadirle encima el número del puerto.

Si necesitamos otro protocolo, como:

ftp://ftp.microsoft.com

el puerto se derivará de ese protocolo. Así el puerto FTP de Microsoft es el 21, según sufichero services. La primera parte, antes de los dos puntos, de la URL, indica el protocolo quese quiere utilizar en la conexión con el servidor. El protocolo http (HyperText TransmissionProtocol), es el utilizado para manipular documentos Web. Y si no se especifica ningúndocumento, muchos servidores están configurados para devolver un documento de nombreindex.html.

Con todo esto, Java permite los siguientes cuatro constructores para la clase URL:

public URL( String spec ) throws MalformedURLException;public URL( String protocol,String host,int port,String file ) throws MalformedURLException;public URL( String protocol,String host,String file ) throws MalformedURLException;public URL( URL context,String spec ) throws MalformedURLException;

Así que podríamos especificar todos los componenetes del URL como en:

URL( «http»,»www.yahoo.com»,»80",»index.html» );

o dejar que los sistemas utilicen todos los valores por defecto que tienen definidos, como en:

URL( «http://www.yahoo.com» );

y en los dos casos obtendríamos la visualización de la página principal de Yahoo en nuestronavegador.

Dominios de comunicacionesEl mecanismo de sockets está diseñado para ser todo lo genérico posible. El socket por símismo no contiene información suficiente para describir la comunicación entre procesos.Los sockets operan dentro de dominios de comunicación, entre ellos se define si los dosprocesos que se comunican se encuentran en el mismo sistema o en sistemas diferentes ycómo pueden ser direccionados.

254 Tutorial de Java

Dominio Unix

Bajo Unix, hay dos dominios, uno para comunicaciones internas al sistema y otro para co-municaciones entre sistemas.

Las comunicaciones intrasistema (entre dos procesos en el mismo sistema) ocurren (en unamáquina Unix) en el dominio Unix. Se permiten tanto los sockets stream como los datagrama.Los sockets de dominio Unix bajo Solaris 2.x se implementan sobre TLI (Transport LevelInterface).

En el dominio Unix no se permiten sockets de tipo Raw.

Dominio Internet

Las comunicaciones intersistemas proporcionan acceso a TCP, ejecutando sobre IP (InternetProtocol). De la misma forma que el dominio Unix, el dominio Internet permite tanto socketsstream como datagrama, pero además permite sockets de tipo Raw.

Los sockets stream permiten a los procesos comunicarse a través de TCP. Una vez estable-cidas las conexiones, los datos se pueden leer y escribir a/desde los sockets como un flujo(stream) de bytes. Algunas aplicaciones de servicios TCP son:

· File Tranfer Protocol, FTP· Simple Mail Transfer Protocol, SMTP· TELNET, servicio de conexión de terminal remoto

Los sockets datagrama permiten a los procesos utilizar el protocolo UDP para comunicarsea y desde esos sockets por medio de bloques. UDP es un protocolo no fiable y la entrega delos paquetes no está garantizada. Servicios UDP son:

· Simple Network Management Protocol, SNMP· Trivial File Transfer Protocol, TFTP (versión de FTP sin conexión)· Versatile Message Transaction Protocol, VMTP (servicio fiable de entrega punto a

punto de datagramas independiente de TCP)

Los sockets raw proporcionan acceso al Internet Control Message Protocol, ICMP, y seutiliza para comunicarse entre varias entidades IP.

MODELO DE COMUNICACIONES CON JAVAEn Java, crear una conexión socket TCP/IP se realiza directamente con el paquete java.net .A continuación mostramos un diagrama de lo que ocurre en el lado del cliente y del servidor:

Tutorial de Java 255

El modelo de sockets más simple es:

· El servidor establece un puerto y espera durante un cierto tiempo (timeout segun-dos), a que el cliente establezca la conexión. Cuando el cliente solicite una conexión,el servidor abrirá la conexión socket con el método accept().

· El cliente establece una conexión con la máquina host a través del puerto que sedesigne en puerto#

· El cliente y el servidor se comunican con manejadores InputStream y OutputStream

Hay una cuestión al respecto de los sockets, que viene impuesta por la implementación delsistema de seguridad de Java. Actualmente, los applets sólo pueden establecer conexionescon el nodo desde el cual se transfirió su código. Esto está implementado en el JDK y en elintérprete de Java de Netscape. Esto reduce en gran manera la flexibilidad de las fuentes dedatos disponibles para los applets. El problema si se permite que un applet se conecte acualquier máquina de la red, es que entonces se podrían utilizar los applets para inundar lared desde un ordenador con un cliente Netscape del que no se sospecha y sin ningunaposibilidad de rastreo.

Apertura de socketsSi estamos programando un cliente , el socket se abre de la forma:

Socket miCliente; miCliente = new Socket( «maquina»,numeroPuerto );

Donde maquina es el nombre de la máquina en donde estamos intentando abrir la conexión ynumeroPuerto es el puerto (un número) del servidor que está corriendo sobre el cual nosqueremos conectar. Cuando se selecciona un número de puerto, se debe tener en cuentaque los puertos en el rango 0-1023 están reservados para usuarios con muchos privilegios(superusuarios o root). Estos puertos son los que utilizan los servicios estándar del sistema

256 Tutorial de Java

como email, ftp o http. Para las aplicaciones que se desarrollen, asegurarse de seleccionarun puerto por encima del 1023.

En el ejemplo anterior no se usan excepciones; sin embargo, es una gran idea la captura deexcepciones cuando se está trabajando con sockets. El mismo ejemplo quedaría como:

Socket miCliente; try { miCliente = new Socket( «maquina»,numeroPuerto ); } catch( IOException e ) { System.out.println( e ); }

Si estamos programando un servidor , la forma de apertura del socket es la que muestra elsiguiente ejemplo:

Socket miServicio; try { miServicio = new ServerSocket( numeroPuerto ); } catch( IOException e ) { System.out.println( e ); }

A la hora de la implementación de un servidor también necesitamos crear un objeto socketdesde el ServerSocket para que esté atento a las conexiones que le puedan realizar clien-tes potenciales y poder aceptar esas conexiones:

Socket socketServicio = null; try { socketServicio = miServicio.accept(); } catch( IOException e ) { System.out.println( e ); }

Creación de Streams

Creación de Streams de Entrada

En la parte cliente de la aplicación, se puede utilizar la clase DataInputStream para crearun stream de entrada que esté listo a recibir todas las respuestas que el servidor le envíe.

DataInputStream entrada; try { entrada = new DataInputStream( miCliente.getInputStream() ); } catch( IOException e ) { System.out.println( e ); }

La clase DataInputStream permite la lectura de líneas de texto y tipos de datos primitivosde Java de un modo altamente portable; dispone de métodos para leer todos esos tiposcomo: read(), readChar(), readInt(), readDouble() y readLine(). Deberemos utilizar la fun-ción que creamos necesaria dependiendo del tipo de dato que esperemos recibir del servi-dor.

Tutorial de Java 257

En el lado del servidor, también usaremos DataInputStream , pero en este caso para recibirlas entradas que se produzcan de los clientes que se hayan conectado:

DataInputStream entrada; try { entrada = new DataInputStream( socketServicio.getInputStream() ); } catch( IOException e ) { System.out.println( e ); }

Creación de Streams de Salida

En el lado del cliente, podemos crear un stream de salida para enviar información al socketdel servidor utilizando las clases PrintStream o DataOutputStream :

PrintStream salida; try { salida = new PrintStream( miCliente.getOutputStream() ); } catch( IOException e ) { System.out.println( e ); }

La clase PrintStream tiene métodos para la representación textual de todos los datos primi-tivos de Java. Sus métodos write y println() tienen una especial importancia en este aspecto.No obstante, para el envío de información al servidor también podemos utilizarDataOutputStream :

DataOutputStream salida; try { salida = new DataOutputStream( miCliente.getOutputStream() ); } catch( IOException e ) { System.out.println( e ); }

La clase DataOutputStream permite escribir cualquiera de los tipos primitivos de Java,muchos de sus métodos escriben un tipo de dato primitivo en el stream de salida. De todosesos métodos, el más útil quizás sea writeBytes().

En el lado del servidor, podemos utilizar la clase PrintStream para enviar información alcliente:

PrintStream salida; try { salida = new PrintStream( socketServicio.getOutputStream() ); } catch( IOException e ) { System.out.println( e ); }

Pero también podemos utilizar la clase DataOutputStream como en el caso de envío deinformación desde el cliente.

258 Tutorial de Java

Cierre de socketsSiempre deberemos cerrar los canales de entrada y salida que se hayan abierto durante laejecución de la aplicación. En la parte del cliente:

try { salida.close(); entrada.close(); miCliente.close(); } catch( IOException e ) { System.out.println( e ); }

Y en la parte del servidor:

try { salida.close(); entrada.close(); socketServicio.close(); miServicio.close(); } catch( IOException e ) { System.out.println( e ); }

Mínimo cliente SMTPVamos a desarrollar un mínimo cliente SMTP (simple mail transfer protocol), de forma quepodamos encapsular todos los datos en la aplicación. El código es libre de modificaciónpara las necesidades que sean; por ejemplo, una modificación interesante sería que aceptaseargumentos desde la línea de comandos y también capturase el texto del mensaje desde laentrada estándar del sistema. Con estas modificaciones tendríamos casi la misma aplica-ción de correo que utiliza Unix. Veamos el código de nuestro cliente, smtpCliente.java:import java.net.*;import java.io.*;

class smtpCliente { public static void main( String args[] ) { Socket s = null; DataInputStream sIn = null; DataOutputStream sOut = null;

// Abrimos una conexión con breogan en el puerto 25 // que es el correspondiente al protocolo smtp, e intentamos // abrir los streams de entrada y salida try { s = new Socket( «breogan»,25 ); sIn = new DataInputStream( s.getInputStream() ); sOut = new DataOutputStream( s.getOutputStream() ); } catch( UnknownHostException e ) { System.out.println( «No conozco el host» ); } catch( IOException e ) { System.out.println( e ); }

// Si todo está inicializado correctamente, vamos a escribir

Tutorial de Java 259

// algunos datos en el canal de salida que se ha establecido // con el puerto del protocolo smtp del servidor if( s != null && sIn != null && sOut != null ) { try { // Tenemos que respetar la especificación SMTP dada en // RFC1822/3, de forma que lo que va en mayúsculas // antes de los dos puntos tiene un significado especial // en el protocolo sOut.writeBytes( «MAIL From: [email protected]\n» ); sOut.writeBytes( «RCPT To: [email protected]\n» ); sOut.writeBytes( «DATA\n» ); sOut.writeBytes( «From: [email protected]\n» ); sOut.writeBytes( «Subject: Pruebas\n» ); // Ahora el cuerpo del mensaje sOut.writeBytes( «Hola, desde el Tutorial de Java\n» ); sOut.writeBytes( «\n.\n» );

// Nos quedamos a la espera de recibir el «Ok» del // servidor para saber que ha recibido el mensaje // correctamente, momento en el cual cortamos String respuesta; while( ( respuesta = sIn.readLine() ) != null ) { System.out.println( «Servidor: «+respuesta ); if( respuesta.indexOf( «Ok» ) != -1 ) break; }

// Cerramos todo lo que hemos abierto sOut.close(); sIn.close(); s.close(); } catch( UnknownHostException e ) { System.out.println( «Intentando conectar: «+e ); } catch( IOException e ) { System.out.println( e ); } } } }

Servidor de ECOEn el siguiente ejemplo, vamos a desarrollar un servidor similar al que se ejecuta sobre elpuerto 7 de las máquinas Unix, el servidor echo. Básicamente, este servidor recibe textodesde un cliente y reenvía ese mismo texto al cliente. Desde luego, este es el servidor mássimple de los simples que se pueden escribir. El ejemplo que presentamos, ecoServidor.java,maneja solamente un cliente. Una modificación interesante sería adecuarlo para que aceptasemúltiples clientes simultáneos mediante el uso de threads.import java.net.*;import java.io.*;

260 Tutorial de Java

class ecoServidor { public static void main( String args[] ) { ServerSocket s = null; DataInputStream sIn; PrintStream sOut; Socket cliente = null; String texto;

// Abrimos una conexión con breogan en el puerto 9999 // No podemos elegir un puerto por debajo del 1023 si no somos // usuarios con los máximos privilegios (root) try { s = new ServerSocket( 9999 ); } catch( IOException e ) { }

// Creamos el objeto desde el cual atenderemos y aceptaremos // las conexiones de los clientes y abrimos los canales de // comunicación de entrada y salida try { cliente = s.accept(); sIn = new DataInputStream( cliente.getInputStream() ); sOut = new PrintStream( cliente.getOutputStream() );

// Cuando recibamos datos, se los devolvemos al cliente // que los haya enviado while( true ) { texto = sIn.readLine(); sOut.println( texto ); } } catch( IOException e ) { System.out.println( e ); } } }

Cliente/Servidor TCP/IP

Mínimo Servidor TCP/IP

Veamos el código que presentamos en el siguiente ejemplo, minimoServidor.java, dondedesarrollamos un mínimo servidor TCP/IP, para el cual desarrollaremos después su contra-partida cliente TCP/IP. La aplicación servidor TCP/IP depende de una clase de comunica-ciones proporcionada por Java: ServerSocket . Esta clase realiza la mayor parte del trabajode crear un servidor.

import java.awt.*;import java.net.*;import java.io.*;

class minimoServidor { public static void main( String args[] ) {

Tutorial de Java 261

ServerSocket s = (ServerSocket)null; Socket s1; String cadena = «Tutorial de Java!»; int longCad; OutputStream s1out;

// Establece el servidor en el socket 4321 (espera 300 segundos) try { s = new ServerSocket( 4321,300 ); } catch( IOException e ) { System.out.println( e ); }

// Ejecuta un bucle infinito de listen/accept while( true ) { try { // Espera para aceptar una conexión s1 = s.accept(); // Obtiene un controlador de fichero de salida asociado // con el socket s1out = s1.getOutputStream();

// Enviamos nuestro texto longCad = sendString.length(); for( int i=0; i < longCad; i++ ) s1out.write( (int)sendString.charAt( i ) );

// Cierra la conexión, pero no el socket del servidor s1.close(); } catch( IOException e ) { System.out.println( e ); } } } }

Mínimo Cliente TCP/IP

El lado cliente de una aplicación TCP/IP descansa en la clase Socket . De nuevo, mucho deltrabajo necesario para establecer la conexión lo ha realizado la clase Socket . Vamos apresentar ahora el código de nuestro cliente más simple, minimoCliente.java, que encajacon el servidor presentado antes. El trabajo que realiza este cliente es que todo lo querecibe del servidor lo imprime por la salida estándar del sistema.

import java.awt.*;import java.net.*;import java.io.*;

class minimoCliente { public static void main( String args[] ) throws IOException { int c; Socket s; InputStream sIn;

262 Tutorial de Java

// Abrimos una conexión con breogan en el puerto 4321 try { s = new Socket( «breogan»,4321 ); } catch( IOException e ) { System.out.println( e ); }

// Obtenemos un controlador de fichero de entrada del socket y // leemos esa entrada sIn = s.getInputStream(); while( ( c = sIn.read() ) != -1 ) System.out.print( (char)c );

// Cuando se alcance el fin de fichero, cerramos la conexión y // abandonamos s.close(); } }

Servidor simple de HTTPVamos a implementar un servidor de HTTP básico, sólo le permitiremos admitir operacionesGET y un rango limitado de tipos MIME codificados. Los tipos MIME son los descriptores detipo para contenido multimedia. Esperamos que este ejemplo sirva como base para un en-tretenido ejercicio de ampliación y exploración porque, desde luego, lo que no pretendemoses inquietar a los Consejos de Dirección de Microsoft o Netscape.

La aplicación va a crear un ServerSocket conectado al puerto 80, que en caso de no tenerprivilegios para su uso, podemos cambiar, por ejemplo al 8080; y después entra en un bucleinfinito. Dentro del bucle, espera dentro del método accept() del ServerSocket hasta que seestablece una conexión cliente. Después asigna un flujo de entrada y salida al socket. Acontinuación lee la solicitud del cliente utilizando el método getRawRequest(), que devolve-rá un null si hay un error de entrada/salida o el cliente corta la conexión. Luego se identificael tipo de solicitud y se gestiona mediante el método handlget() o handleUnsup(). Finalmen-te se cierran los sockets y se comienza de nuevo.

Cuando se ejecuta el programa completo, se escribe en pantalla lo que el navegador clienteenvía al servidor. Aunque se capturan varias condiciones de error, en la práctica no apare-cen. El ampliar este servidor para que soporte una carga de millones de visitas al día requie-re bastante trabajo; no obstante, en el ordenador en que estoy escribiendo esto, no seenlenteció demasiado con una carga de hasta diez entradas por segundo, lo que permitiríaalrededor de un millón de visitas al día. Se podría mejorar mediante el uso de threads ycontrol de la memoria caché para gestionar esas visitas, pero eso ya forma parte del ejerci-cio sobre el que se puede trabajar.

El código fuente de nuestro mini servidor de HTTP se encuentra en el fichero TutHttp.java,que reproducimos a continuación:

import java.net.*;import java.io.*;

Tutorial de Java 263

import java.util.*;

// Clase de utilidades donde declaramos los tipos MIME y algunos gestores// de los errores que se pueden generar en HTMLclass HttpUtilidades { final static String version = «1.0»; final static String mime_text_plain = «text/plain»; final static String mime_text_html = «text/html»; final static String mime_image_gif = «image/gif»; final static String mime_image_jpg = «image/jpg»; final static String mime_app_os = «application/octet-stream»; final static String CRLF = «\r\n»;

// Método que convierte un objeto String en una matriz de bytes. // Java gestiona las cadenas como objetos, por lo que es necesario // convertir las matrices de bytes que se obtienen a Strings y // viceversa public static byte aBytes( String s )[] { byte b[] = new byte[ s.length() ]; s.getBytes( 0,b.length,b,0 ); return( b ); }

// Este método concatena dos matrices de bytes. El método // arraycopy() asombra por su rapidez public static byte concatenarBytes( byte a[],byte b[] )[] { byte ret[] = new byte[ a.length+b.length ]; System.arraycopy( a,0,ret,0,a.length ); System.arraycopy( b,0,ret,a.length,b.length ); return( ret ); }

// Este método toma un tipo de contenido y una longitud, para // devolver la matriz de bytes que contiene el mensaje de cabecera // MIME con formato public static byte cabMime( String ct,int tam )[] { return( cabMime( 200,»OK»,ct,tam ) ); }

// Es el mismo método anterior, pero permite un ajuste más fino // del código que se devuelve y el mensaje de error de HTTP public static byte cabMime(int codigo,String mensaje,String ct, int tam )[] { Date d = new Date(); return( aBytes( «HTTP/1.0 «+codigo+» «+mensaje+CRLF+ «Date: «+d.toGMTString()+CRLF+ «Server: Java/»+version +CRLF+ «Content-type: «+ct+CRLF+ ( tam > 0 ? «Content-length: «+tam+CRLF : «» )+CRLF ) ); }

// Este método construye un mensaje HTML con un formato decente // para presentar una condición de error y lo devuelve como // matriz de bytes public static byte error( int codigo,String msg,String fname)[] { String ret = «<BODY>»+CRLF+»<H1>»+codigo+» «+msg+»</H1>»+CRLF;

264 Tutorial de Java

if( fname != null ) ret += «Error al buscar el URL: «+fname+CRLF; ret += «</BODY>»+CRLF; byte tmp[] = cabMime( codigo,msg,mime_text_html,0 ); return( concatenarBytes( tmp,aBytes( ret ) ) ); }

// Devuelve el tipo MIME que corresponde a un nombre de archivo dado public static String mimeTypeString( String fichero ) { String tipo;

if( fichero.endsWith( «.html» ) || fichero.endsWith( «.htm» ) ) tipo = mime_text_html; else if( fichero.endsWith( «.class» ) ) tipo = mime_app_os; else if( fichero.endsWith( «.gif» ) ) tipo = mime_image_gif; else if( fichero.endsWith( «.jpg» ) ) tipo = mime_image_jpg; else tipo = mime_text_plain; return( tipo ); } }

// Esta clase sirve para que nos enteremos de lo que está haciendo// nuestro servidor. En una implementación real, todos estos mensajes// deberían registrarse en algún ficheroclass HTTPlog { public static void error( String entrada ) { System.out.println( «Error: «+entrada ); }

public static void peticion( String peticion ) { System.out.println( peticion ); } }

// Esta es la clase principal de nuestro servidor Httpclass TutHttp { public static final int puerto = 80; final static String docRaiz = «/html»; final static String fichIndice = «index.html»; final static int buffer = 2048; public static final int RT_GET=1; public static final int RT_UNSUP=2; public static final int RT_END=4;

// Indica que la petición no está soportada, por ejemplo POST y HEAD private static void ctrlNoSop(String peticion,OutputStream sout) { HTTPlog.error( «Peticion no soportada: «+peticion ); }

// Este método analiza gramaticalmente la solicitud enviada con el // GET y la descompone en sus partes para extraer el nombre del

Tutorial de Java 265

// archivo que se está solicitando. Entonces lee el fichero que // se pide private static void ctrlGet( String peticion,OutputStream sout ) { int fsp = peticion.indexOf( ‘ ‘ ); int nsp = peticion.indexOf( ‘ ‘,fsp+1 ); String fich = peticion.substring( fsp+1,nsp );

fich = docRaiz+fich+( fich.endsWith(«/») ? fichIndice : «» ); try { File f = new File( fich ); if( !f.exists() ) { sout.write( HttpUtilidades.error( 404, «No Encontrado»,fich ) ); return; }

if( !f.canRead() ) { sout.write( HttpUtilidades.error( 404, «Permiso Denegado»,fich ) ); return; }

// Ahora lee el fichero que se ha solicitado InputStream sin = new FileInputStream( f ); String cabmime = HttpUtilidades.mimeTypeString( fich ); int n = sin.available(); sout.write( HttpUtilidades.cabMime( cabmime,n ) ); byte buf[] = new byte[buffer]; while( ( n = sin.read( buf ) ) >= 0 ) sout.write( buf,0,n );

sin.close(); } catch( IOException e ) { HTTPlog.error( «Excepcion: «+e ); } }

// Devuelve la cabecera de la solicitud completa del cliente al // método main de nuestro servidor private static String getPeticion( InputStream sin ) { try { byte buf[] = new byte[buffer]; boolean esCR = false; int pos = 0; int c;

while( ( c = sin.read() ) != -1 ) { switch( c ) { case ‘\r’: break; case ‘\n’: if( esCR ) return( new String( buf,0,0,pos ) );

266 Tutorial de Java

esCR = true; // Continúa, se ha puesto el primer \n en la cadena default: if( c != ‘\n’ ) esCR = false; buf[pos++] = (byte)c; } } } catch( IOException e ) { HTTPlog.error( «Error de Recepcion» ); } return( null ); }

private static int tipoPeticion( String peticion ) { return( peticion.regionMatches( true,0,»get «,0,4 ) ? RT_GET : RT_UNSUP ); }

// Función principal de nuestro servidor, que se conecta al socket // y se embucla indefinidamente public static void main( String args[] ) throws Exception { ServerSocket ss = new ServerSocket( puerto ); while( true ) { String peticion; Socket s = ss.accept(); OutputStream sOut = s.getOutputStream(); InputStream sIn = s.getInputStream();

if( ( peticion = getPeticion( sIn ) ) != null ) { switch( tipoPeticion( peticion ) ) { case RT_GET: ctrlGet( peticion,sOut ); break; case RT_UNSUP: default: ctrlNoSop( peticion,sOut ); break; } HTTPlog.peticion( peticion ); } sIn.close(); sOut.close(); s.close(); } } }

Ejecutar TCP/IP EN Windows ‘95Las indicaciones que se proporcionan a continuación, van a permitirnos fijar los parámetrosde Windows ’95 para que se puedan ejecutar programas cliente y servidor, sin necesidad de

Tutorial de Java 267

que el ordenador en el cual se está ejecutando Windows ’95 esté conectado a una red deárea local. Esto puede resultar útil para mucha gente que está probando Java en su casa yno dispone de red, o incluso para aquellos programadores o estudiosos que quieren probarsus nuevos programas distribuidos pero no disponen de red o de Internet.

Advertencia : Si los siguientes parámetros se van a fijar en un ordenador portátil que enocasiones sí se conecte a una red, sería conveniente anotar los parámetros actuales de laconfiguración de Windows ’95, para que sean fácilmente recuperables cuando este ordena-dor se vuelva a conectar a la red.

Configuración del TCP/IP de Windows ‘95

Hay que seguir los pasos que vamos a relatar a continuación, suponemos que se estáejecutando la versión española de Windows ’95, en otras versiones puede que las opcionestengan nombre diferente:

En el Panel de Control, seleccionar Red

Picar Agregar, luego Protocolo y luego Agregar

Seleccionar Microsoft y luego TCP/IP y picar en Aceptar

En este momento probablemente se solicite la introducción de los discos de Windows’95, o del CD-ROM

Seleccionar la pestaña Configuración de la ventana Red

Seleccionar TCP/IP en la lista que aparece y picar Propiedades

Seleccionar la pestaña Dirección IP

· Crearse una dirección IP, como por ejemplo: 102.102.102.102· Crearse una Máscara de subred, como: 255.255.255.0

Seleccionar la pestaña Configuración DNS y desactivar DNS

Los valores correspondientes a las otras cuatro pestañas pueden dejarse los que haypor defecto

Picar Aceptar

(Opcional) Seleccionar la pestaña Identificación de la ventana Red

(Opcional) Introducir un nombre para el ordenador, como por ejemplo: breogan

(Opcional) Picar Aceptar

Crear una entrada en la «red»

Editar el fichero hosts.sam que está en el directorio de Windows

268 Tutorial de Java

Al final del fichero incorporar la dirección IP y el nombre del ordenador que se hanintroducido antes, en nuestro caso: 102.102.102.102 breogan

Asegurarse de que la dirección IP y el nombre coinciden con la dirección IP que se hafijado en el paso 7a de antes y que el nombre es el mismo que el indicado en el paso12 anterior

Salvar el fichero con el nombre «hosts» y reiniciar Windows ’95

Comprobación de la red

Abrir una sesión MS-DOS

Teclear «ping breogan»

Debería aparecer:

Pinging breogan [102.102.102.102] with 32 bytes of data: Reply from 102.102.102.102: bytes=32 time=1ms TTL=32 Reply from 102.102.102.102: bytes=32 time<10ms TTL=32 Reply from 102.102.102.102: bytes=32 time<10ms TTL=32 Reply from 102.102.102.102: bytes=32 time<10ms TTL=32

Teclear «tracert 102.102.102.102»

Debería aparecer:

Tracing route to 102.102.102.102 over a maximum of 30 hops 1 <10 ms 1 ms <10 ms 102.102.102.102 Trace complete.

En este instante, si todo ha ido bien, el ordenador está listo para funcionar como si estuvieraen red. Dos o más programas que se comuniquen en red a través de sockets debería poderejecutarse ahora dentro de los dominios del ordenador que acabamos de configurar

Problemas más frecuentes

Los tres problemas que pueden presentarse cuando intentemos comprobar el funciona-miento correcto de la red interna que acabamos de montar son:

Cuando hacemos «ping» obtenemos «Bad IP address breogan»

Intentar teclear «ping 102.102.102.102». Si ahora sí se obtiene réplica, la causa delproblema es que el los pasos 12 de la Configuración y 3 de la Creación de la entradaen la tabla de hosts, no se ha introducido correctamente el nombre de la máquina.Comprobar esos pasos y que todo coincide.

El programa cliente o el servidor fallan al intentar el «connect»

La causa podría estar en que se produzca un fallo por fichero no encontrado en eldirectorio Windows/System de las librerías WINSOCK.DLL o WSOCK32.DLL. Muchos pro-gramas que se utilizan en Internet reemplazan estos ficheros cuando se instalan.

Tutorial de Java 269

Asegurarse de que están estos ficheros y que son los originales que vienen con ladistribución de Windows ’95.

El programa servidor dice que no puede «bind» a un socket

Esto sucede porque tiene el DNS activado y no puede encontrar ese DNS o servidorde direcciones, porque estamos solos en la red. Asegurarse de que en el paso 8 de laConfiguración la opción de DNS está deshabilitada.

Clases útiles en comunicacionesVamos a exponer otras clases que resultan útiles cuando estamos desarrollando programasde comunicaciones, aparte de las que ya se han visto. El problema es que la mayoría deestas clases se prestan a discusión, porque se encuentran bajo el directorio sun. Esto quieredecir que son implementaciones Solaris y, por tanto, específicas del Unix Solaris. Ademássu API no está garantizada, pudiendo cambiar. Pero, a pesar de todo, resultan muy intere-santes y vamos a comentar un grupo de ellas solamente que se encuentran en el paquetesun.net

Socket

Es el objeto básico en toda comunicación a través de Internet, bajo el protocolo TCP.Esta clase proporciona métodos para la entrada/salida a través de streams que ha-cen la lectura y escritura a través de sockets muy sencilla.

ServerSocket

Es un objeto utilizado en las aplicaciones servidor para escuchar las peticiones querealicen los clientes conectados a ese servidor. Este objeto no realiza el servicio, sinoque crea un objeto Socket en función del cliente para realizar toda la comunicación através de él.

DatagramSocket

La clase de sockets datagrama puede ser utilizada para implementar datagramas nofiables (sockets UDP), no ordenados. Aunque la comunicación por estos sockets esmuy rápida porque no hay que perder tiempo estableciendo la conexión entre clientey servidor.

DatagramPacket

Clase que representa un paquete datagrama conteniendo información de paquete,longitud de paquete, direcciones Internet y números de puerto.

MulticastSocket

Clase utilizada para crear una versión multicast de las clase socket datagrama. Múl-tiples clientes/servidores pueden transmitir a un grupo multicast (un grupo de direc-ciones IP compartiendo el mismo número de puerto).

270 Tutorial de Java

NetworkServer

Una clase creada para implementar métodos y variables utilizadas en la creación deun servidor TCP/IP.

NetworkClient

Una clase creada para implementar métodos y variables utilizadas en la creación deun cliente TCP/IP.

SocketImpl

Es un Interface que nos permite crearnos nuestro propio modelo de comunicación.Tendremos que implementar sus métodos cuando la usemos. Si vamos a desarrollaruna aplicación con requerimientos especiales de comunicaciones, como pueden sela implementación de un cortafuegos (TCP es un protocolo no seguro), o acceder aequipos especiales (como un lector de código de barras o un GPS diferencial), nece-sitaremos nuestra propia clase Socket .

Vamos a ver un ejemplo de utilización, presentando un sencillo ejemplo, servidorUDP.java,de implementación de sockets UDP utilizando la clase DatagramSocket .

import java.net.*;import java.io.*;import sun.net.*;

// Implementación del servidor de datagramas UDP. Envía una cadena// tras petición//class servidorUDP { public static void main( String args[] ) { DatagramSocket s = (DatagramSocket)null; DatagramPacket enviap,recibep; byte ibuffer[] = new byte[100]; String cadena = «Hola Tutorial de Java!\n»; InetAddress IP = (InetAddress)null; int longitud = sendString.length(); int puertoEnvio = 4321; int puertoRecep = 4322; int puertoRemoto;

// Intentamos conseguir la dirección IP del host try { IP = InetAddress.getByName( «bregogan» ); } catch( UnknownHostException e ) { System.out.println( «No encuentro al host breogan» ); System.exit( -1 ); }

// Establecemos el servidor para escuchar en el socket 4322 try { s = new DatagramSocket( puertoRecep ); } catch( SocketException e ) { System.out.println( «Error - «+e.toString() ); }

Tutorial de Java 271

// Creamos un paquete de solicitud en el cliente // y nos quedamos esperando a sus peticiones recibep = new DatagramPacket( ibuffer,longitud ); try { s.receive( recibep ); } catch( IOException e ) { System.out.println( «Error - «+e.toString() ); }

// Creamos un paquete para enviar al cliente y lo enviamos sendString.getBytes( 0,longitud,ibuffer,0 ); enviap = new DatagramPacket( ibuffer,longitud,IP,puertoEnvio ); try { s.send( enviap ); } catch( IOException e ) { System.out.println( «Error - «+e.toString() ); System.exit( -1 ); }

// Cerramos el socket s.close(); } }

Y también vamos a implementar el cliente, clienteUDP.java, del socket UDP correspondienteal servidor que acabamos de presentar:

import java.net.*;import java.io.*;import sun.net.*;

// Implementación del cliente de datagramas UDP. Devuelve la salida// de los servidores//class clienteUDP { public static void main( String args[] ) { int longitud = 100; DatagramSocket s = (DatagramSocket)null; DatagramPacket enviap,recibep; byte ibuffer[] = new byte[100]; InetAddress IP = (InetAddress)null; int puertoEnvio = 4321; int puertoRecep = 4322;

// Abre una conexión y establece el cliente para recibir // una petición en el socket 4321 try { s = new DatagramSocket( puertoRecep ); } catch( SocketException e ) { System.out.println( «Error - «+e.toString() ); }

// Crea una petición para enviar bytes. Intenta conseguir // la dirección IP del host try { IP = InetAddress.getByName( «depserver» );

272 Tutorial de Java

} catch( UnknownHostException e ) { System.out.println( «No encuentro el host depserver» ); System.exit( -1 ); }

// Envía una petición para que responda el servidor try { enviap = new DatagramPacket( ibuffer,ibuffer.length, IP,4322 ); s.send( enviap ); } catch( IOException e ) { System.out.println( «Error - «+e.toString() ); }

// Consigue un controlador de fichero de entrada del socket y lee // dicha entrada. Creamos un paquete descriptor para recibir el // paquete UDP recibep = new DatagramPacket( ibuffer,longitud );

// Espera a recibir un paquete try { s.receive( recibep ); } catch( IOException e ) { System.out.println( «Error - «+e.toString() ); System.exit( -1 ); }

// Imprimimos los resultados de lo que conseguimos System.out.println( «Recibido: «+recibep.getLength()+» bytes» ); String datos = new String( recibep.getData(),0 ); System.out.println( «Datos: «+datos ); System.out.println( «Recibido por puerto: «+recibep.getPort() );

// Cerramos la conexión y abandonamos s.close(); } }

La salida que se producirá cuando ejecutemos primero el servidor y luego el cliente será lamisma que reproducimos a continuación:

%java clienteUDPRecibido: 17 bytesDatos: Hola Tutorial de Java!

Recibido por puerto: 4322

Tutorial de Java 273

Capítulo 15Arquitectura MVC

Vamos a presentar, por curiosidad más que por otra cosa, una introducción a la interfaceObserver y a la clase Observable que proporciona Java. Vamos a implementar programasbasados en la arquitectura Modelo/Vista/Controlador, popularizada por el lenguaje Smalltalk.

Presentamos el problema: queremos diseñar un programa que visualice los datos de unaescena en tres dimensiones y lo pase a dos dimensiones. El programa debe ser modular ypermitir vistas múltiples y simultáneas de la misma escena. Cada vista debe ser capaz depresentar la escena desde diferentes puntos de vista y distintas condiciones de iluminación.Y, más importante todavía, si alguna porción de una escena cambia, las vistas deben actua-lizarse automáticamente. Otra versión del problema sería el de una hoja de cálculo, endonde tenemos una serie de datos que queremos ver representados gráficamente, para ellodispondremos de varias vistas con gráficos de línea, de barra o de tarta, y deben actualizar-se automáticamente según vayan cambiando los datos que figuran en la hoja de cálculo.

Ninguno de los requerimientos anteriores presenta una carga de programación imposible.Si el código que controla cada uno de los requerimientos ha de ser escrito de nuevo, sinembargo, si que supondría un esfuerzo significativo. Afortunadamente, el soporte para es-tas tareas ya esta proporcionado por la librería de Java, a través del interface Observer y laclase Observable . Las funcionalidades de las dos se han inspirado, en parte, en la arquitec-tura Modelo/Vista/Controlador.

Arquitectura Modelo/Vista/ControladorLa arquitectura MVC (Model/View/Controller) fue introducida como parte de la versiónSmalltalk-80 del lenguaje de programación Smalltalk. Fue diseñada para reducir el esfuerzode programación necesario en la implementación de sistemas múltiples y sincronizados delos mismos datos. Sus características principales son que el Modelo, las Vistas y losControladores se tratan como entidades separadas; esto hace que cualquier cambio produ-cido en el Modelo se refleje automáticamente en cada una de las Vistas.

Además del programa ejemplo que hemos presentado al principio y que posteriormenteimplementaremos, este modelo de arquitectura se puede emplear en sistemas de represen-tación gráfica de datos, como se ha citado, o en sistemas CAD, en donde se presentanpartes del diseño con diferente escala de aumento, en ventanas separadas.

274 Tutorial de Java

En la figura siguiente, vemos la arquitectura MVC en su forma más general. Hay un Modelo,múltiples Controladores que manipulan ese Modelo, y hay varias Vistas de los datos delModelo, que cambian cuando cambia el estado de ese Modelo.

Este modelo de arquitectura presenta varias ventajas:

· Hay una clara separación entre los componentes de un programa; lo cual nos permiteimplementarlos por separado

· Hay un API muy bien definido; cualquiera que use el API, podrá reemplazar el Mode-lo, la Vista o el Controlador, sin aparente dificultad.

· La conexión entre el Modelo y sus Vistas es dinámica; se produce en tiempo deejecución, no en tiempo de compilación.

Al incorporar el modelo de arquitectura MVC a un diseño, las piezas de un programa sepueden construir por separado y luego unirlas en tiempo de ejecución. Si uno de los Compo-nentes, posteriormente, se observa que funciona mal, puede reemplazarse sin que las otraspiezas se vean afectadas. Este escenario contrasta con la aproximación monolítica típica demuchos programas Java. Todos tienen un Frame que contiene todos los elementos, uncontrolador de eventos, un montón de cálculos y la presentación del resultado. Ante estaperspectiva, hacer un cambio aquí no es nada trivial.

Definición de las partes

El Modelo es el objeto que representa los datos del programa. Maneja los datos y controlatodas sus transformaciones. El Modelo no tiene conocimiento específico de los Controladoreso de las Vistas, ni siquiera contiene referencias a ellos. Es el propio sistema el que tieneencomendada la responsabilidad de mantener enlaces entre el Modelo y sus Vistas, y noti-ficar a las Vistas cuando cambia el Modelo.

La Vista es el objeto que maneja la presentación visual de los datos representados por elModelo. Genera una representación visual del Modelo y muestra los datos al usuario.Interactúa con el Modelo a través de una referencia al propio Modelo.

El Controlador es el objeto que proporciona significado a las ordenes del usuario, actuandosobre los datos representados por el Modelo. Cuando se realiza algún cambio, entra enacción, bien sea por cambios en la información del Modelo o por alteraciones de la Vista.Interactúa con el Modelo a través de una referencia al propio Modelo.

Tutorial de Java 275

Vamos a mostrar un ejemplo concreto. Consideremos como tal el sistema descrito en laintroducción a este capítulo, una pieza geométrica en tres dimensiones, que representamosen la figura siguiente:

En este caso, la pieza central de la escena en tres dimensiones es el Modelo. El Modelo esuna descripción matemática de los vértices y las caras que componen la escena. Los datosque describen cada vértice o cara pueden modificarse (quizás como resultado de una ac-ción del usuario, o una distorsión de la escena, o un algoritmo de sombreado). Sin embargo,no tiene noción del punto de vista, método de presentación, perspectiva o fuente de luz. ElModelo es una representación pura de los elementos que componen la escena.

La porción del programa que transforma los datos dentro del Modelo en una presentacióngráfica es la Vista. La Vista incorpora la visión del Modelo a la escena; es la representacióngráfica de la escena desde un punto de vista determinado, bajo condiciones de iluminacióndeterminadas.

El Controlador sabe que puede hacer el Modelo e implementa el interface de usuario quepermite iniciar la acción. En este ejemplo, un panel de datos de entrada es lo único que senecesita, para permitir añadir, modificar o borrar vértices o caras de la figura.

Observador y observableEl lenguaje de programación Java proporciona soporte para la arquitectura MVC mediantedos clases:

· Observer : Es cualquier objeto que desee ser notificado cuando el estado de otroobjeto sea alterado

· Observable : Es cualquier objeto cuyo estado puede representar interés y sobre elcual otro objeto ha demostrado ese interés

Estas dos clases se pueden utilizar para muchas más cosas que la implementación de laarquitectura MVC. Serán útiles en cualquier sistema en que se necesite que algunos objetossean notificados cuando ocurran cambios en otros objetos.

El Modelo es un subtipo de Observable y la Vista es un subtipo de Observer . Estas dosclases manejan adecuadamente la función de notificación de cambios que necesita la arqui-tectura MVC. Proporcionan el mecanismo por el cual las Vistas pueden ser notificadasautomáticamente de los cambios producidos en el Modelo. Referencias al objeto Modelotanto en el Controlador como en la Vista permiten acceder a los datos de ese objeto Modelo.

276 Tutorial de Java

Funciones Observer y Observable

Vamos a enumerar las funciones que intervienen en el control de Observador y Observable:

Observerpublic void update( Observableobs,Object obj )

Llamada cuando se produce un cambio en el estado del objeto Observable

Observablepublic void addObserver( Observer obs )

Añade un observador a la lista interna de observadores

public void deleteObserver( Observer obs )

Borra un observador de la lista interna de observadores

public void deleteObservers()

Borra todos los observadores de la lista interna

public int countObserver()

Devuelve el número de observadores en la lista interna

protected void setChanged()

Levanta el flag interno que indica que el Observable ha cambiado de estado

protected void clearChanged()

Baja el flag interno que indica que el Observable ha cambiado de estado

protected boolean hasChanged()

Devuelve un valor booleano indicando si el Observable ha cambiado de estado

public void notifyObservers()

Comprueba el flag interno para ver si el Observable ha cambiado de estado y lonotifica a todos los observadores

public void notifyObservers( Object obj )

Comprueba el flag interno para ver si el Observable ha cambiado de estado y lonotifica a todos los observadores. Les pasa el objeto especificado en la llamada paraque lo usen los observadores en su método notify().

Utilizar Observador y ObservableVamos a describir en los siguientes apartados, como crear una nueva clase Observable yuna nueva clase Observer y como utilizar las dos conjuntamente.

Tutorial de Java 277

Extender un Observable

Una nueva clase de objetos observables se crea extendiendo la clase Observable . Como laclase Observable ya implementa todos los métodos necesarios para proporcionar el funcio-namiento de tipo Observador/Observable, la clase derivada solamente necesita proporcio-nar algún tipo de mecanismo que lo ajuste a su funcionamiento particular y proporcionaracceso al estado interno del objeto Observable.

En la clase ValorObservable que mostramos a continuación, el estado interno del Modeloes capturado en el entero n. A este valor se accede (y más importante todavía, se modifica)solamente a través de sus métodos públicos. Si el valor cambia, el objeto invoca a su propiométodo setChanged() para indicar que el estado del Modelo ha cambiado. Luego, invoca asu propio método notifyObservers() para actualizar a todos los observadores registrados.

import java.util.Observable;

public class ValorObservable extends Observable { private int nValor = 0;

// Constructor al que indicamos el valor en que comenzamos y los // limites inferior y superior que no deben sobrepasarse public ValorObservable( int nValor,int nInferior,int nSuperior ) { this.nValor = nValor; }

// Fija el valor que le pasamos y notifica a los observadores que // estan pendientes del cambio de estado de los objetos de esta // clase, que su etado se ha visto alterado public void setValor(int nValor) { this.nValor = nValor;

setChanged(); notifyObservers(); }

// Devuelve el valor actual que tiene el objeto public int getValor() { return( nValor ); } }

Implementar un Observador

Una nueva clase de objetos que observe los cambios en el estado de otro objeto se puedecrear implementando la interface Observer . Esta interface necesita un método update() quese debe proporcionar en la nueva clase. Este método será llamado siempre que el Observa-ble cambie de estado, que anuncia este cambio llamando a su método notifyObservers(). Elobservador entonces, debería interrogar al objeto Observable para determinar su nuevoestado; y, en el caso de la arquitectura MVC, ajustar su Vista adecuadamente.

En la clase ObservadorDeTexto , que muestra el código siguiente, el método notify() prime-

278 Tutorial de Java

ro realiza una comprobación para asegurarse de que el Observable que ha anunciado uncambio es el Observable que él esta observando. Si lo es, entonces lee su estado e imprimeel nuevo valor.

import java.util.Observer;import java.util.Observable;

public class TextoObservador extends Frame implements Observer { private ValorObservable vo = null;

public TextoObservador( ValorObservable vo ) { this.vo = vo; }

public void update( Observable obs,Object obj ) { if( obs == vo ) tf.setText( String.valueOf( vo.getValor() ) ); } }

Usando Observador y Observable

Un programa indica a un objeto Observable que hay un objeto observador que debe sernotificado cuando se produzca un cambio en su estado, llamando al método addObserver()del objeto Observable. Este método añade el Observador a la lista de observadores que elobjeto Observable ha de notificar cuando su estado se altere.

En el ejemplo siguiente, en donde mostramos la clase ControlValor , ControlValor.java, ve-mos como se usa el método addObserver() para añadir una instancia de la claseTextoObservador a la lista que mantiene la clase ValorObservable .

public class ControlValor {

// Constructor de la clase que nos permite crear los objetos de // observador y observable public ControlValor() { ValorObservable vo = new ValorObservable( 100,0,500 ); TextoObservador to = new TextoObservador( vo );

vo.addObserver( to ); }

public static void main( String args[] ) { ControlValor m = new ControlValor(); } }

En la siguiente secuencia, vamos a describir como se realiza la interacción entre un Obser-vador y un objeto Observable, durante la ejecución de un programa:

En primer lugar el usuario manipula un elemento del interface de usuario representadopor el Controlador. Este Controlador realiza un cambio en el Modelo a través de unode sus métodos públicos de acceso; en nuestro caso, llama a setValue().

Tutorial de Java 279

El método público de acceso modifica el dato privado, ajusta el estado interno del Mode-lo y llama al método setChanged() para indicar que su estado ha cambiado. Luegollama al método notifyObservers() para notificar a los observadores que su estado noes el mismo. La llamada a este método puede realizarse en cualquier lugar, inclusodesde un bucle de actualización que se esté ejecutando en otro thread.

Se llama a los métodos update() de cada Observador, indicando que hay un cambio en elestado del objeto que estaban observando. El Observador accede entonces a losdatos del Modelo a través del método público del Observable y actualiza las Vistas.

Ejemplo de aplicación MVCEn el ejemplo siguiente, vemos como colaboran juntos Observador y Observable en la ar-quitectura MVC:

El Modelo de este ejemplo es muy simple. Su estado interno consta de un valor entero. Estevalor, o estado, es manipulado exclusivamente a través de métodos públicos de acceso. Elcódigo del modelo se encuentra implementado en ValorObservable.java.

Inicialmente, hemos escrito una clase simple de Vista/Controlador. La clase combina lascaracterísticas de una Vista (presenta el valor que corresponde al estado actual del Modelo)y un Controlador (permite al usuario introducir un nuevo valor para alterar el estado delModelo). El código se encuentra en el fichero TextoObservador.java. Podemos crear instan-cias de esta vista pulsando el botón superior que aparece en el applet.

A través de este diseño utilizando la arquitectura MVC (en lugar de colocar el código paraque el Modelo, la Vista y el Controlador de texto en una clase monolítica), el sistema puedeser fácilmente rediseñado para manejar otra Vista y otro Controlador. En este caso, hemosvisto una clase Vista/Controlador con una barra de desplazamiento. La posición del marca-dor en la barra representa el valor actual que corresponde con el estado del Modelo y puedeser alterado a través de movimientos del marcador sobre la barra por acción del usuario. Elcódigo de esta clase se encuentra en BarraObservador.java. Se pueden crear instancias deesta clase pulsando el botón inferior del applet de esta página.

280 Tutorial de Java

Tutorial de Java 281

Capítulo 16Aplicaciones Java

Los applets anteriores son simplemente ejemplos de lo que se puede conseguir con Java.Son un poco más complicados que los desarrollados en el Tutorial, pero tampoco tan com-plicados como para que no se pueda seguir adecuandamente el código fuente.

Se han desarrollado sobre Windows’95, por lo que no estoy seguro de que en otros siste-mas se visualice todo correctamente, sobre todo teniendo en cuenta que no todas las fuen-tes de caracteres están disponibles siempre. Si se desea hacer un uso comercial de estosapplets, habría que añdirles más detalles de configuración y, sobre todo, capturar todas lasexcepciones posibles, porque lo que en un sitio va bien, en otro puede generar una excep-ción.

EtiquetaVamos a desarrolllar un nuevo Componente para el AWT, para mejorar el Label que seproporciona con el AWT, añadiéndole al original cosas como bordes, efectos de sombreado,etc., o posibilidad de fijarle características como el color o el font de caracteres con que sepresentará en pantalla.

En el código fuente de la clase, Etiqueta.java, hemos introducido gran cantidad de comenta-rios, por lo que explicaciones aquí serían redundantes, en la mayoría de las sentencias paraque se vaya siguiendo paso a paso la construcción y entendiendo qué hace cada uno de losmétodos y, para que resulte sencillo al lector el añadir nuevos tipos de efectos sobre el textode la Etiqueta.

El applet EjEtiqueta.java presenta las tres posibilidades de presentar texto en pantalla quehemos implementado: Texto normal, resaltado y hundido. A continuación se muestra el re-sultado de la ejecución del applet.

282 Tutorial de Java

//

// Etiqueta.java

// Copyright (c) 1996, Agustin Froufe

// Todos los derechos reservados.

//

// No se asume ninguna responsabilidad por el uso o alteracion de este

// software. Este software se proporciona COMO ES, sin garantia de ningun

// tipo de su funcionamiento y en ningun caso sera el autor responsable de

// daños o perjuicios que se deriven del mal uso del software, aun cuando

// este haya sido notificado de la posibilidad de dicho daño.

//

// Compilador: javac 1.0

// Autor: Agustin Froufe

// Creacion: 02-Jun-1996 05:18:10

//

//--------------------------------------------------------------------------

// Esta informacion no es necesariamente definitiva y esta sujeta a cambios

// que pueden ser incorporados en cualquier momento, sin avisar.

//--------------------------------------------------------------------------

import java.awt.*;

public class Etiqueta extends Panel {

private String texto = "";

private Font font = null;

private FontMetrics metrica = null;

private Color color = Color.black;

private int ancho = 0;

private int alto = 0;

private int sombra = 0;

private boolean borde = false;

// Declaramos las variables que indican como aparecera el texto en

// pantalla, para ello las hacemos "public" para que sean visibles

// a otras clases, "static" para que la compartan todos los objetos

// de la clase y "final" porque son valores constantes, que no van

Tutorial de Java 283

// a variar de ninguna de las maneras

public static final int TextoNormal = 0;

public static final int TextoResaltado = 1;

public static final int TextoHundido = 2;

// Constructor basico, con una cadena vacia

public Etiqueta() {

this( "" );

}

// Constructor de conveniencia al que le pasamos una cadena de texto

// que se presentara con los valores de defecto

public Etiqueta( String Texto ) {

setText( Texto );

}

// Constructor de conveniencia al que le pasamos todos los datos

// para que pinte el texto como nostros queremos

// Tiene la particularidad de que lo podemos utilizar como plantilla

// para crear otros objetos que sean iguales a uno que ya tengamos,

// porque le pasamos este nuestro objeto y nos devuelve otro con

// las mismas caracteristicas

// Solo fijamos las caracteristicas, no indicamos el texto a

// presentar

public Etiqueta( Etiqueta plantilla ) {

copiaPlantilla( plantilla );

}

// Constructor de conveniencia completo, en el que le decimos

// el texto que debe aparecer y las caracteristicas con que

// debe presentarlo en pantalla

public Etiqueta( String Texto,Etiqueta plantilla ) {

copiaPlantilla( plantilla );

284 Tutorial de Java

setText( Texto );

}

// Metodo que pasa los valores de las caracteristicas con que

// queremos pintar, a los atributos del objeto Texto que

// vamos a hacer aparecer en pantalla, crea un objeto a partir

// de otro

public void copiaPlantilla( Etiqueta plantilla ) {

texto = plantilla.texto;

font = plantilla.font;

metrica = plantilla.metrica;

color = plantilla.color;

ancho = plantilla.ancho;

sombra = plantilla.sombra;

borde = plantilla.borde;

}

// Nos aseguramos de que se seleccione una fuente para pintar los

// caracteres. Si no se indica una, la creamos nosotros de la

// forma mas sencilla posible

public void checkFont() {

if( font == null )

font = new Font( "Helvetica",Font.PLAIN,12 );

}

// Fija la cadena de texto que se va a presentar, asegurandose

// de que se indica con que font de caracteres queremos hacerlo,

// luego copia el texto en un miembro local y hace que el panel

// se repinte

public void setText( String Texto ) {

checkFont();

texto = Texto;

repaint();

Tutorial de Java 285

}

// Fijamos una nueva fuente de caracteres para pintar el texto

public void setFont( String nombre,int estilo,int tam ) {

font = new Font( nombre,estilo,tam );

}

// Fijamos el ancho del rectangulo en donde vamos a inscribir el

// texto

public void setAncho( int Ancho ) {

ancho = Ancho;

}

// Fijamos la altura del rectangulo en donde vamos a inscribir el

// texto

public void setAlto( int Alto ) {

alto = Alto;

}

// Fijamos el color con que queremos presentar el texto en pantalla

public void setColor( Color cColor ) {

color = cColor;

}

// Fijamos los pixels de desplazamiento que habra respecto de los

// dos texto que se escriben para dar el efecto de sombra.

// Para conseguir este efecto, simplemente pintamos el texto con el

// color seleccionado para la sombra y luego lo volvemos a pintar

// con el color del texto y desplazado tantos pixels como indique

// este paramentro

public void setSombra( int Sombra ) {

286 Tutorial de Java

sombra = Sombra;

}

// Fija el tamano del borde del rectangulo que circunscribe al

// texto que estamos presentando en pantalla

public void setBorde( boolean Borde ) {

borde = Borde;

}

// Funciones de recuperacion, equivalentes a las del grupo 'set',

// utilizadas para recuperar los atributos actueles fijados para

// el texto que actualmente se presenta en pantalla

public String getText() {

return( texto );

}

public Font getFont() {

return( font );

}

public int getAncho() {

return( ancho );

}

public Color getColor() {

return( color );

}

public int getSombra() {

return( sombra );

}

public boolean getBorde() {

return( borde );

Tutorial de Java 287

}

// Este metodo se llama automaticamente cuando el objeto aparece

// en pantalla por primera vez o cuando se ve expuesto de nuevo,

// tras haber estado tapado por otra ventana

public void paint( Graphics g ) {

update( g );

}

// Este metodo es llamado por el propio objeto para mostrar

// en pantalla algo que haya cambiado o porque alguien lo llame

// directamente, como en el caso de update

public void update( Graphics g ) {

// Borramos todo el area que va a ocupar el texto

Color color = g.getColor();

g.clearRect( 0,0,bounds().width,bounds().height );

// Si va a llevar borde el rectangulo que delimita el espacio en

// donde se va a pintar el texto, se lo ponemos

if( borde )

{

g.setColor( Color.lightGray );

g.draw3DRect( 0,0,bounds().width-1,bounds().height-1,false );

}

drawText( g );

g.setColor( color );

}

// Este es el metodo encargado de pintar realmente el texto en la

// pantalla

private void drawText( Graphics g ) {

// Si no hay texto que pintar, nos vamos

if( texto == null )

return;

288 Tutorial de Java

// Convertimos el texto en un array de caracteres

char caracteres[] = texto.toCharArray();

int Longitud = texto.length();

Color colorant = g.getColor();

// Fijamos la fuente con que queremos que aparezca el texto y

// recogemos la informacion de esa fuente, porque necesitamos

// conocer el ancho de cada uno de los caracteres

g.setFont( font );

FontMetrics metrica = getFontMetrics( font );

int stringWidth = metrica.charsWidth( caracteres,0,Longitud );

// Controlamos el efecto que queremos darle al texto, si

// resaltado o hundido (hacia abajo), para conseguirlo

// pintamos dos cadenas en diferente color

if( sombra == TextoResaltado )

{

g.setColor( Color.white );

g.drawChars( caracteres,0,Longitud,1,metrica.getHeight()-3 );

}

else if( sombra == TextoHundido )

{

g.setColor( Color.white );

g.drawChars( caracteres,0,Longitud,3,metrica.getHeight()-1 );

}

// Aplicamos el color mas oscuro con un ligero desplazamiento

// y recuperamos el color antiguo con que estabamos trabajando

g.setColor( color );

g.drawChars( caracteres,0,Longitud,2,metrica.getHeight()-2 );

g.setColor( colorant );

}

// Este metodo devuelve el tamno minimo deseable para el rectangulo

// donde vamos a pintar el texto. Es importante porque nuestro

Tutorial de Java 289

// objeto no deja de ser mas que un Componente en el Layout, y el

// gestor del layout a la hora de reconstruirlo, lo llama para

// saber cual es el minimo tamano posible

public Dimension minimumSize() {

if( alto == 0 )

{

if( font != null )

alto = font.getSize();

else

alto = 12;

}

if( ancho == 0 )

ancho = 100;

return( new Dimension( ancho+3,alto+3 ) );

}

// Con este metodo pasa lo mismo, el manejador del Layout lo llama

// para saber cual es el tamano que ha fijado el creador del

// componente e intentar respetarlo (siempre que pueda)

public Dimension preferredSize() {

return( minimumSize() );

}

// Creamos un bordecito entre el texto y el borde del rectangulo

// que lo circunscribe

public Insets insets() {

return( new Insets( 3,3,3,3 ) );

}

}

//------------------------------------------ Final del fichero Etiqueta.java

290 Tutorial de Java

RELOJ DIGITALSeguramente, siempre que hagamos una aplicación con una envergadura mínima, necesi-taremos un temporizador. Así que vamos a implementar uno que llegue a resolución desegundos; porque, también es casi seguro, que en una aplicación normal, no necesitaremosmás precisión.

Vamos a implementar nuestro temporizador, Timer.java, de la forma más general, haciendoque tenga un time-out, que se pueda resetear automática o manualmente y lo vamos aconstruir como un thread individual, con lo cual, podremos tener tantos timers como quera-mos.

La interface Temporizador , Temporizador.java, nos proporciona los métodos que necesita-mos para implementar el funcionamiento del Timer, es decir, que consideraremos al Timercomo un elemento observable, de forma que notifique a los observadores cuando algúnevento ocurra, como puede ser el arranque del timer, la pérdida o su muerte. Así, si unaclase determinada quiere ser avisada cuando le ocurra algo al timer, debe implementar lainterface Temporizador, dejando vacíos los métodos que corresponden a los eventos que nole interese recoger.

La interface Temporizador consiste en cuatro métodos, de los cuales los más interesanteson timerMuerto(), que se llama cuando la duración del timer se ha cumplido y, timerIntervalo(),que ocurre a intervalos regulares, según el tiempo que hayamos fijado.

Si una clase necesita más de un timer, puede implementar esta interface, pero entoncesnecesitaríamos incorporar a la clase que la use métodos para que fije e indique su nombre;de este modo, los timers estarán identificados dentro de la interface Temporizador y nohabrá problemas al estar varios timers corriendo a la vez.

El applet que presentamos, RelojDigital.java, simplemente recoge la hora del sistema y la vaactualizando cada segundo utilizando un timer. Hacemos uso también del componente Eti-queta que hemos desarrollado en otro ejemplo, para presentar el texto con la hora en lapantalla.

//

// Timer.java

// Copyright (c) 1996, Agustin Froufe

// Todos los derechos reservados.

//

// No se asume ninguna responsabilidad por el uso o alteracion de este

// software. Este software se proporciona COMO ES, sin garantia de ningun

Tutorial de Java 291

// tipo de su funcionamiento y en ningun caso sera el autor responsable de

// daños o perjuicios que se deriven del mal uso del software, aun cuando

// este haya sido notificado de la posibilidad de dicho daño.

//

// Compilador: javac 1.0

// Autor: Agustin Froufe

// Creacion: 23-Oct-1996 014:53:09

//

//--------------------------------------------------------------------------

// Esta informacion no es necesariamente definitiva y esta sujeta a cambios

// que pueden ser incorporados en cualquier momento, sin avisar.

//--------------------------------------------------------------------------

import java.lang.*;

import java.util.*;

public class Timer implements Runnable {

private Date inicio,parada;

private Thread thread = null;

private int duracion = 0;

private int duracionAnt = 0;

private int intervalo = 1000;

private boolean repeticion = false;

private boolean enEjecucion = false;

private Temporizador handler = null;

// Contructor basico, usado por defecto

Timer() { }

// Los siguientes constructores son de conveniencia, porque todas

// las caracteristicas del timer se pueden ver y modificar a

// traves de los metodos get y set

// Constructor de conveniencia que acepta un tiempo de duracion

Timer( int tiempo ) {

setDuracion( tiempo );

292 Tutorial de Java

}

// Constructor de conveniencia que acepta un Temporizador

Timer( Temporizador Handler ) {

setHandler( Handler );

}

// Constructor de conveniencia que acepta un temporizador y una

// duracion

Timer( int tiempo,Temporizador Handler ) {

setDuracion( tiempo );

setHandler( Handler );

}

// Fija el numero se segundos que correra el timer

public void setDuracion( int tiempo ) {

duracion = tiempo;

}

// Fija el objeto que ha de ser notificado de los eventos que

// sucedan al timer

public void setHandler( Temporizador Handler ) {

handler = Handler;

}

// Fija el numero de milisegundos entre pulsos del timer

public void setIntervalo( int Intervalo ) {

intervalo = Intervalo;

}

Tutorial de Java 293

// Funciones "get" para recoger los datos de las caracteristicas

// que se han fijado antes

public int getDuration() {

return( duracion );

}

public Temporizador getHandler() {

return( handler );

}

public int getIntervalo() {

return( intervalo );

}

// Devuelve el numero de segundos que han transcurrido desde que

// se arranco el timer

public int getElapsed() {

return( calculaLapso( new Date() ) );

}

// Este metodo permite resetear el timer antes de relanzarlo. Se

// podria usar el metodo setDuracion, pero este es mas corto

// y elegante

public void resetDuracion() {

duracion = duracionAnt;

}

// Aqui creamos un nuevo thread para correr el Timer. Lo incializamos

// con "this" de forma que el metodo run() se llame inmediatamente

// como comience la ejecucion del thread

public void start() {

thread = new Thread( this );

thread.start();

294 Tutorial de Java

}

// Aqui almacenamos el momento en que se llama a este metodo.

// Tambien comprobamos si hay algun Temporizador asociado al Timer

// que estamos parando, en cuyo caso, notificamos a los observadores

// de este Timer que lo hemos detenido (para eso esta la interface

// Temporizador, que debera estar implementada en las clases que

// miren a este Timer)

public void stop() {

enEjecucion = false;

parada = new Date();

if ( handler != null )

handler.timerParado( this );

}

public void run() {

enEjecucion = true;

duracionAnt = duracion;

// Arrancamos el Timer y lo notificamos a las clases que esten

// poendientes

inicio = new Date();

if( handler != null )

handler.timerArrancado( this );

while( enEjecucion )

{

// Esperamos el tiempo que nos hayan dicho en la configuracion

// del intervalo

try {

esperar( intervalo );

} catch( InterruptedException e ) {

return;

}

Tutorial de Java 295

// Cuando se cumple el intervalo, avisamos a las clases que

// esten pendientes. Si esas clases no quieren hacer nada

// con este evento periodico, es suficiente con que no lo

// sobrecarguen, que se quede vacio

if( handler != null )

handler.timerIntervalo( this );

// Si no indicamos una duracion para el Timer, estara

// corriendo indefinidamente

if( duracion > 0 )

{

// Comprobamos si el Timer esta muerto ya, para no

// tener que matarlo

if( estaMuerto() )

{

// Si esta muerto, lo propagamos

if( handler != null )

handler.timerMuerto( this );

// Aqui comprobamos si se quiere una repeticion

// automatica, en cuyo caso, volvemos a arrancar

// el Timer

if( repeticion )

{

enEjecucion = true;

inicio = new Date();

if( handler != null )

handler.timerArrancado( this );

}

else

{

enEjecucion = false;

}

}

}

296 Tutorial de Java

}

}

// Metodos que nos informan del estado del Timer

public boolean estaCorriendo() {

return( enEjecucion );

}

public boolean estaParado() {

return( !enEjecucion );

}

public boolean estaMuerto() {

int segundos = 0;

// Calculamos el intervalo de tiempo que ha transcurrido desde

// que se ha arrancado el Timer

segundos = calculaLapso( new Date() );

if( segundos >= duracion )

return( true );

else

return( false );

}

private int calculaLapso( Date actual ) {

Date dfinal;

int segundos = 0;

if( enEjecucion )

dfinal = actual;

else

dfinal = parada;

Tutorial de Java 297

// Si se quiere mas precision, en vez de Date(), se puede

// utilizar System.currentTimeMillis(), que proporciona

// muchisima mas resolucion

segundos += ( dfinal.getHours() - inicio.getHours() ) * 3600;

segundos += ( dfinal.getMinutes() - inicio.getMinutes() ) * 60;

segundos += ( dfinal.getSeconds() - inicio.getSeconds() );

return( segundos );

}

// Aqui implementamos la espera. El lapso en milisegundos se lo

// pasamos al metodo wait() del thread

// Este metodo es sincronizado porque sino salta una excepcion

// interna en el interprete de Java. No esta muy bien documentado

// el porque, pero parece ser que no se puede llamar al metodo

// wait() de un thread desde otro que no sea sincronizado

private synchronized void esperar( int lapso )

throws InterruptedException {

this.wait( lapso );

}

}

//--------------------------------------------- Final del fichero Timer.java

//

// Temporizador.java

// Copyright (c) 1996, Agustin Froufe

// Todos los derechos reservados.

//

// No se asume ninguna responsabilidad por el uso o alteracion de este

// software. Este software se proporciona COMO ES, sin garantia de ningun

// tipo de su funcionamiento y en ningun caso sera el autor responsable de

// daños o perjuicios que se deriven del mal uso del software, aun cuando

// este haya sido notificado de la posibilidad de dicho daño.

//

// Compilador: javac 1.0

298 Tutorial de Java

// Autor: Agustin Froufe

// Creacion: 23-Oct-1996 13:56:30

//

//--------------------------------------------------------------------------

// Esta informacion no es necesariamente definitiva y esta sujeta a cambios

// que pueden ser incorporados en cualquier momento, sin avisar.

//--------------------------------------------------------------------------

//

// Interface que vamos a utilizar en la implementacion del timer, con el

// que luego generaremos el reloj digital de demostracion

// Esta interface debe ser implementada por cualquier clase que desee

// ser informada de los eventos que genere el Timer, en nuetro caso el

// reloj

//

public interface Temporizador {

public void timerArrancado( Timer timer );

public void timerParado( Timer timer );

public void timerMuerto( Timer timer );

public void timerIntervalo( Timer timer );

}

//------------------------------------- Final del fichero Temporizador.java

//

// RelojDigital.java

// Copyright (c) 1996, Agustin Froufe

// Todos los derechos reservados.

//

// No se asume ninguna responsabilidad por el uso o alteracion de este

// software. Este software se proporciona COMO ES, sin garantia de ningun

// tipo de su funcionamiento y en ningun caso sera el autor responsable de

// daños o perjuicios que se deriven del mal uso del software, aun cuando

// este haya sido notificado de la posibilidad de dicho daño.

//

Tutorial de Java 299

// Compilador: javac 1.0

// Autor: Agustin Froufe

// Creacion: 02-Jun-1996 05:18:10

//

//--------------------------------------------------------------------------

// Esta informacion no es necesariamente definitiva y esta sujeta a cambios

// que pueden ser incorporados en cualquier momento, sin avisar.

//--------------------------------------------------------------------------

import java.awt.*;

import java.lang.*;

import java.util.*;

import java.applet.*;

public class RelojDigital extends Applet implements Temporizador {

private Timer timer;

private Etiqueta horaActual;

private Color fondo;

// Este es el metodo main() para que podamos ejecutar la aplicacion

// desde la linea de comandos

public static void main( String args[] ) {

RelojDigital reloj = new RelojDigital();

reloj.init();

reloj.start();

// Craemos un frame para que se pueda visualizar el reloj

Frame frame = new Frame( "Reloj Digital" );

frame.add( "Center",reloj );

frame.resize( 50,20 );

frame.show();

}

public void init() {

300 Tutorial de Java

// Cargamos el parametro que nos indica el color de fondo que

// queremos para el reloj, si no se especifica o no se indica

// correctamente, fijamos uno por defecto

// Lo adecuado, en caso de presentar el applet sobre una pagina

// de un navegador es que este parametro coincida con el que

// se especifique para el color de Background de esa pagina

String Fondo = getParameter( "Fondo" );

if( Fondo == null )

fondo = Color.lightGray;

else

{

try {

fondo = new Color( Integer.parseInt( Fondo,16 ) );

} catch( NumberFormatException e ) {

fondo = Color.lightGray;

}

}

setBackground( fondo );

// Nos creamos un timer, para actualizar el reloj cada segundo

Timer timer = new Timer( this );

// Creamos un objeto Etiqueta, para que nuestro reloj aparezca

// con un texto decente y bonito

horaActual = new Etiqueta();

horaActual.setFont( "Helvetica",Font.PLAIN,18 );

horaActual.setColor( new Color( 128,0,0 ) );

horaActual.setAlto( 25 );

horaActual.setAncho( 100 );

horaActual.setSombra( Etiqueta.TextoHundido );

horaActual.setBorde( true );

add( horaActual );

// Arrancamos el timer para que el reloj empiece a marchar

timer.start();

}

Tutorial de Java 301

// En cada aviso de que nuestro Timer ha llegado al intervalo de

// tiempo que nosotros hemos fijado, cogemos la hora del sistema

// y la presentamos

public void timerIntervalo( Timer t ) {

Date actual = new Date();

int horas = actual.getHours();

int minutos = actual.getMinutes();

int segundos = actual.getSeconds();

int hora;

String tiempo = "";

// Los "ifs" que siguen son para formatear la hora

// correctamente y que siempre ocupe lo mismo en la

// Etiqueta, para que no salten las horas de posicion al

// presentarse en pantalla

if( horas > 12 )

hora = horas - 12;

else

hora = horas;

if( hora < 10 )

tiempo += "0";

tiempo += hora;

tiempo += ":";

if( minutos < 10 )

tiempo += "0";

tiempo += minutos + ":";

if( segundos < 10 )

tiempo += "0";

tiempo += segundos;

if( horas > 12 )

tiempo += " pm";

302 Tutorial de Java

else

tiempo += " am";

horaActual.setText( tiempo );

}

// Sobrecargamos los metodos que no nos interesan de la interface

// Temporizador, porque solamente vamos a utilizar la que genera

// un evento cada cierto intervalo de tiempo, las demas no tienen

// interes en este ejemplo

public void timerArrancado( Timer t ) { }

public void timerParado( Timer t ) { }

public void timerMuerto( Timer t ) { }

public void paint( Graphics g ) {

g.setColor( fondo );

g.fillRect( 0,0,size().width,size().height );

}

}

//-------------------------------------- Final del fichero RelojDigital.java

Tutorial de Java 303

PERSIANAEste applet presenta información desplazándose a través de la zona de pantalla que cons-tituye el applet. Podemos incorporar cualquier tipo de información para que sea presentadaen pantalla, tal como se muestra en el ejemplo de esta misma página. Se pueden conseguirmúltiples efectos, a través de la selección de fuentes y utilización de colores.

El texto que se presenta en la pantalla va incluido en el fichero de código fuente, Persiana.java,y no en la llamada al applet. Además, se puede incorporar un dibujo de fondo, que serápresentado a la vez que se ejecute el applet. El código de este applet es muy sencillo y fácilde comprender, a pesar de que el efecto que se visualiza en pantalla sugiera una programa-ción complicada.

//

// Persiana.java

// Copyright (c) 1996, Agustin Froufe

// Todos los derechos reservados.

//

// No se asume ninguna responsabilidad por el uso o alteracion de este

// software. Este software se proporciona COMO ES, sin garantia de ningun

// tipo de su funcionamiento y en ningun caso sera el autor responsable de

// daños o perjuicios que se deriven del mal uso del software, aun cuando

// este haya sido notificado de la posibilidad de dicho daño.

//

// Compilador: javac 1.0

// Autor: Agustin Froufe

// Creacion: 17-Oct-1996 06:22:56

//

//--------------------------------------------------------------------------

// Esta informacion no es necesariamente definitiva y esta sujeta a cambios

// que pueden ser incorporados en cualquier momento, sin avisar.

//--------------------------------------------------------------------------

import java.awt.*;

304 Tutorial de Java

import java.applet.Applet;

public class Persiana extends Applet implements Runnable {

Image imagenFondo;

Image offI;

Graphics offG;

int posicion;

String texto[];

Font font;

Font font1;

Font font2;

Font font3;

boolean puedePintar;

Thread thrd;

MediaTracker mdt;

public void init() {

mdt = new MediaTracker( this );

// Instanciación de los objetos fuente que vamos a utilizar durante

// la presentación del texto en su desplazamiento por la persiana

font = new Font( "Arial",0,15 );

font1 = new Font( "Helv",1,23 );

font2 = new Font( "TimesNewRoman",1,30 );

font3 = new Font( "Arial",1,16 );

// Cadenas de texto correspondientes al primer grupo de información

// que se va a presentar en la ventana

texto[0] = "Este Tutorial de Java proporciona suficientes conocimien-tos";

texto[1] = "para que, tanto los programadores noveles como aquellos queya";

texto[2] = "conocen las bondades del lenguaje Java, puedan acercarse enun";

texto[3] = "caso y profundizar en el otro, en los más diversos aspectosdel";

texto[4] = "lenguaje. Se recorren casi todas las características deJava,";

Tutorial de Java 305

texto[5] = "desde las más sencillas como puede ser la descripción delos";

texto[6] = "tipos soportados por el lenguaje, hasta las más complica-das";

texto[7] = "como la implementación de un entorno cliente-servidor o el";

texto[8] = "desarrollo de un modelo de arquitectura Modelo-Vista comple-to.";

// Información que se presentará a continuación del título

// del segundo bloque de información de la persiana

texto[9] = "Se hace un profundo recorrido por el Abstract WindowToolkit,";

texto[10] = "presentado todos los elementos que lo componen en su ac-tual";

texto[11] = "versión, con múltiples líneas de código fuente para queel";

texto[12] = "navegante pueda ir comprobando cómo se usan y cómo Java";

texto[13] = "utiliza los recursos que pone en manos del programador.";

texto[14] = "También hay múltiples applets Java en funcionamiento a lo";

texto[15] = "largo del Tutorial, de forma que se puede ir comprobando";

texto[16] = "la apariencia que presentan los applets sobre el browserque";

texto[17] = "estemos utilizando.";

// Bloque final de información

texto[18] = "Como compendio a todo lo desarrollado en este Tutorial de";

texto[19] = "Java, se presentan varias aplicaciones autónomas y algu-nos";

texto[20] = "applets listos para utilizar, con toda la información";

texto[21] = "necesaria para su implementación y uso.";

// Recuperamos la imagen que se va a utilizar como fondo de la

// cadena que represente el título principal de la persiana

imagenFondo = getImage( getCodeBase(),"fondo.gif" );

try {

offI = createImage( 700,55 );

offG = offI.getGraphics();

} catch( Exception e ) {

offG = null;

}

306 Tutorial de Java

return;

}

public void update( Graphics g ) {

if( puedePintar )

paint( g );

}

public void paint( Graphics g ) {

if( offG != null )

{

paintApplet( offG );

g.drawImage( offI,0,0,this );

return;

}

paintApplet( g );

}

public void paintApplet( Graphics g ) {

// Presentamos la imagen de fondo y el título principal que

// queremos asignar a la infromación que va a ir presentando

// la persiana.

// Para hacer un poco más atractiva la visualización del título

// hacemos que aparezca una especie de sombra de la cadena de

// texto. Para ello, simplemente la volvemos a escribir pero

// desplazada

g.setColor( Color.black );

g.fillRect( 0,0,600,700 );

g.setFont( font2 );

g.drawImage( imagenFondo,0,-posicion,this );

Tutorial de Java 307

g.setColor( Color.white );

g.drawString( "Tutorial de Java",200,50-posicion );

g.setColor( Color.blue );

g.drawString( "Turorial de Java",198,48-posicion );

g.setColor( Color.orange );

g.setFont( font1 );

g.fillRect( 130,60-posicion,190,24 );

g.setColor( Color.black );

g.drawString( "Presentación",150,80-posicion );

g.setColor( Color.white );

g.setFont( font );

for( int i=0; i < 9; i++ )

g.drawString( texto[i],5,105+i*19-posicion );

// Creamos otro rectángulo de color para colocar el título que

// encabeza al grupo de cadenas que van a seguir desplazándose

// por la persiana

g.setColor( Color.white );

g.fillRect( 120,265-posicion,180,26 );

g.setColor( Color.red );

g.setFont( font1 );

g.drawString( "AWT",180,287-posicion );

g.setColor( Color.white );

g.setFont( font );

for( int j=9; j < 18; j++ )

g.drawString( texto[j],5,140+j*19-posicion );

// Presentamos el siguiente grupo de información, creando una

// cabecera para indicar de qué se trata la información que va a

// aparecer en la persiana

g.setColor( Color.red );

g.fillRect( 120,471-posicion,200,30 );

g.setColor( Color.white );

g.setFont( font1 );

g.drawString( "Ejemplos",165,493-posicion );

g.setColor( Color.white );

308 Tutorial de Java

g.setFont( font );

for( int j=18; j < 22; j++ )

g.drawString( texto[j],5,177+j*19-posicion );

// Esta es la misma información que la del principio

g.setFont( font2 );

g.drawImage( imagenFondo,0,600-posicion,this );

g.setColor( Color.white );

g.drawString( "Tutorial de Java",200,650-posicion );

g.setColor( Color.blue );

g.drawString( "Tutorial de Java",198,648-posicion );

}

public void start() {

// Creamos un nuevo thread y lo arrancamos

thrd = new Thread( this );

thrd.start();

}

public void stop() {

thrd.stop();

}

public void run() {

for( puedePintar=false; true; puedePintar=false )

{

try {

puedePintar = true;

repaint();

// Si estamos al principio, alargamos un poco la espera

if( posicion == 0 )

{

Thread.currentThread();

Tutorial de Java 309

Thread.sleep( 5000 );

}

posicion++;

// Cuando lleguemos al final del texto, volvemos al

// principio, para repetir todos los mensajes

if( posicion == 600 )

posicion = 0;

Thread.currentThread();

// Aqui podemos hacer más rápido o más lento el

// desplazamiento del texto por la persiana

Thread.sleep( 100 );

} catch( InterruptedException e ) {

;

}

}

}

// El constructor del applet se limita a crear el array de texto que

// contendrá todas las cadenas o líneas de texto que se irán pasando

// por la ventana

public Persiana() {

texto = new String[22];

}

}

//------------------------------------------ Final del fichero Persiana.java

310 Tutorial de Java

SOLAPASEste applet nos muestra cómo se pueden incorporar nuevos Componentes al AWT. Hemosdiseñado un sistema de solapas o Tabs, como se suelen reconocer, para incorporar a nues-tras aplicaciones. En el fichero EjFicha.java, se encuentra el código que muestra cómo seusa y que corresponde a la ejecución del applet que aparece en esta misma página.

Los ficheros de código fuente que se emplean para la implementación del sistema de sola-pas propuesto son:

CuerpoFicha.java

Se encarga de presentar el cuerpo de la ficha que corresponde a una solapa. Nospermite incorporar cualquier otro elemento que deseemos a esa ficha, tal como muestrael ejemplo, en el cual hemos añadido diferentes componentes a cada una de lasfichas que se abren al seleccionar las solapas.

Solapa.java

Se encarga de gestionar cada una de las solapas. Nos indica cuando se ha pulsadoen una solapa y tiene implementados los métodos necesarios para indicarnos cuan-do una solapa está selecciona o no, cuando se pica con el ratón dentro de una sola-pa, o para resaltar la solapa cuando se selecciona, entre otros.

Ficha.java

Este fichero contiene una clase compuesta de dos Objetos, uno de clase Solapa yotro de clase CuerpoFicha. Se encarga de gestionar la presentación de la Ficha alcompleto, de forma que cuando se selecciona una solapa, se presenta el contenidodel objeto CuerpoFicha que tiene asociado ese objeto Solapa que se ha elegido.

ListaFichas.java

Contiene un objeto Vector con todas las fichas que hayamos incorporado al applet.Aquí es donde podemos indicar que el applet contendrá más o menos Fichas, queestarán representadas por las Solapas que el usuario podrá elegir con el ratón.

Tutorial de Java 311

//

// CuerpoFicha.java

// Copyright (c) 1996, Agustin Froufe

// Todos los derechos reservados.

//

// No se asume ninguna responsabilidad por el uso o alteracion deeste

// software. Este software se proporciona COMO ES, sin garantia deningun

// tipo de su funcionamiento y en ningun caso sera el autor responsablede

// daños o perjuicios que se deriven del mal uso del software, aun cuan-do

// este haya sido notificado de la posibilidad de dicho daño.

//

// Compilador: javac 1.0

// Autor: Agustin Froufe

// Creacion: 11-Sep-1996 11:35:43

//

//--------------------------------------------------------------------------

// Esta informacion no es necesariamente definitiva y est sujeta a cam-bios

// que pueden ser incorporados en cualquier momento, sin avisar.

//--------------------------------------------------------------------------

import java.awt.*;

// Presenta el cuerpo de la ficha en blanco, para que se puedan insertarlos

// componentes que correspondan

// Es el contenedor del control tabular que estamos implementando

public class CuerpoFicha extends Panel {

// Lo unico que se pasa al constructor de esta clase es el color de

// fondo con que queremos que aparezca

public CuerpoFicha( Color colorCuerpo ) {

setBackground( colorCuerpo );

}

312 Tutorial de Java

public void paint( Graphics g ) {

Rectangle area;

area = bounds();

// Pintamos el fondo del cuerpo de la ficha con el color

// que se haya indicado en el constructor de la clase

g.setColor( getBackground() );

g.fillRect( area.x,area.y + 1,area.width,area.height-1 );

// Pintamos una linea brillante el los lados superior e

// izquierdo

g.setColor( Color.white );

g.drawLine( area.x,area.y,area.x+area.width,area.y );

g.drawLine( area.x,area.y,area.x,area.y+area.height );

// Pintamos una linea gris en los lados inferior y derecho

// para dar sensacion de sombra

g.setColor( Color.gray );

g.drawLine( area.x,area.y+area.height-1,

area.x+area.width-1,area.y+area.height-1 );

g.drawLine( area.x+area.width-1,area.y+area.height-1,

area.x+area.width-1,area.y+1 );

// Pintamos la sombra de la sobra de los lados inferior y

// derecho, para aumentar la sensacion de tres dimensiones y

// profundidad

g.setColor( Color.black );

g.drawLine( area.x,area.y+area.height,

area.x+area.width,area.y+area.height );

g.drawLine( area.x+area.width,area.y+area.height,

area.x+area.width,area.y+1 );

}

}

Tutorial de Java 313

//--------------------------------------- Final del ficheroCuerpoFicha.java

//

// Solapa.java

// Copyright (c) 1996, Agustin Froufe

// Todos los derechos reservados.

//

// No se asume ninguna responsabilidad por el uso o alteracion deeste

// software. Este software se proporciona COMO ES, sin garantia deningun

// tipo de su funcionamiento y en ningun caso sera el autor responsablede

// daños o perjuicios que se deriven del mal uso del software, aun cuan-do

// este haya sido notificado de la posibilidad de dicho daño.

//

// Compilador: javac 1.0

// Autor: Agustin Froufe

// Creacion: 12-Sep-1996 11:57:07

//

//--------------------------------------------------------------------------

// Esta informacion no es necesariamente definitiva y est sujeta a cam-bios

// que pueden ser incorporados en cualquier momento, sin avisar.

//--------------------------------------------------------------------------

import java.awt.*;

// Este objeto se utiliza para seleccionar cada una de las fichas

// Pulsando con el raton sobre una de ellas, se visualizara el

// contenido de la ficha que tiene adosada

public class Solapa extends Canvas {

protected String rotulo; // Rotulo indicador de la solapa

protected Font seleFont; // Font cuando la solapa esta seleccio-nada

protected Font normFont; // Font del estado normal de la solapa

314 Tutorial de Java

protected boolean seleccionada; // Indicador de seleccion de la solapa

public Solapa( String rotulo,Font normFont,

Font seleFont,Color captionColor,Color bgColor ) {

super();

this.rotulo = rotulo;

this.normFont = normFont;

this.seleFont = seleFont;

setForeground( captionColor );

setBackground( bgColor );

seleccionada = false;

}

public void pintaRotulo( Graphics g ) {

int rotuloX;

int rotuloY;

FontMetrics fm;

// Comprobamos la fuente de caracteres que vamos a utilizar en la

// presentacion del rotulo, en funcion del estado de la ficha

if( seleccionada )

{

g.setFont( seleFont );

fm = g.getFontMetrics( seleFont );

}

else

{

g.setFont( normFont );

fm = g.getFontMetrics( normFont );

}

// Calculamos las coordenadas en que vamos a presentar el rotulo

// Teniendo en cuenta la longitud del texto del rotulo y

// la fuente de caracteres

rotuloX =

Tutorial de Java 315

bounds().x +( (bounds().width - fm.stringWidth(rotulo) ) / 2);

rotuloY = bounds().y + ( bounds().height / 2 ) -

fm.getDescent() + ( fm.getHeight() / 2 );

// Fijamos el color y pintamos el rotulo

g.setColor( getForeground() );

g.drawString( rotulo,rotuloX,rotuloY );

}

// Pinta la solapa en base a pares de lineas para proporcionar

// sensacion de tres dimensiones, en caso de que este seleccionada

// se pintara dos pixels mas grande para que de la sensacion

// de que esta delante del resto de fichas

public void pintaSolapa( Graphics g ) {

int delante;

Rectangle area;

delante = ( seleccionada ) ? 2 : 0;

area = bounds();

g.setColor( getBackground() );

// Pintamos el rectangulo que va a ocupar la solapa

if ( !seleccionada )

{

g.fillRect( area.x,area.y+2, area.width,area.height-2 );

g.drawLine( area.x+1,area.y+1,area.width+area.x-2,area.y+1 );

}

else

{

g.fillRect( area.x-1,area.y,area.width+2,area.height );

g.drawLine( area.x,area.y-1,area.x+area.width,area.y-1 );

}

// left side, rounded corner and top side lit

g.setColor( Color.white );

316 Tutorial de Java

g.drawLine( area.x-delante,area.y+area.height-2,

area.x-delante,area.y+2-delante );

g.drawLine( area.x-delante,area.y+2-delante,

area.x+2-delante,area.y-delante );

g.drawLine( area.x+2-delante,area.y-delante,

area.x+area.width-2+delante,area.y-delante );

if( !seleccionada )

{

// Pintamos la sombra del lado derecho

g.setColor( Color.gray );

g.drawLine( area.x+area.width-1,area.y,

area.x+area.width-1,area.y+area.height-2 );

// Pintamos la sombra de la sombra del lado derecho

// para dar mas sensacion de profundidad

g.setColor( Color.black );

g.drawLine( area.x+area.width,area.y+2,

area.x+area.width,area.y+area.height-2 );

}

else

{

// Pintamos la sombra del lado derecho

g.setColor( Color.gray );

g.drawLine( area.x+area.width-1+delante,area.y-delante,

area.x+area.width-1+delante,area.y+area.height-2 );

// Pintamos la sombra de la sombra del lado derecho

g.setColor( Color.black );

g.drawLine( area.x+area.width+delante,area.y+2-delante,

area.x + area.width+delante,area.y + area.height-2 );

// Como la solapa esta seleccionada, pintamos en color del

// fondo la linea que une la solapa anterior con la

// siguiente, dando sensacion de que la solapa esta

// efectivamente pegada a la ficha

Tutorial de Java 317

g.setColor( getBackground() );

g.drawLine( area.x-delante+1,area.y+area.height,

area.x+area.width,area.y+area.height );

}

}

// Devuelve el texto del rotulo de la solapa

public String getRotulo() {

return( rotulo );

}

// Devuelve el punto correspondiente a la posicion final de la

// solapa

public Point getFinal() {

return( new Point(bounds().x+bounds().width+2,bounds().y ) );

}

// Devuelve la altura de la solapa

public int getAlto() {

return( size().height );

}

// Devuelve la fuente de caracteres con que se pinta el rotulo de

// la solapa cuando esta seleccionada

public Font getFuenteSel() {

return( seleFont );

}

// Indica si la posicion que se pasa como parametro esta dentro

// de los limites de la solapa o no

public boolean estaDentro( int x,int y ) {

318 Tutorial de Java

return( bounds().inside( x,y ) );

}

// Indica si la solapa esta seleccionada o no

public boolean estaSeleccionada() {

return( seleccionada );

}

// Cuando pulsamos el raton, nos devuelve true si lo hemos hecho

// sobre la solapa o false si lo hemos hecho fuera del rectangulo

// que ocupa la solapa

public boolean mouseDown( Event evt,int x,int y ) {

if( ( estaDentro( x,y ) ) && ( !estaSeleccionada() ) )

return( true );

else

return( false );

}

public void paint( Graphics g ) {

pintaSolapa( g );

pintaRotulo( g );

}

// Indica que la solapa esta seleccionada

public void seleccionar() {

seleccionada = true;

}

// Indica que la solapa deja de estar seleccionada

public void deSeleccionar() {

seleccionada = false;

Tutorial de Java 319

}

}

//-------------------------------------------- Final del ficheroSolapa.java

//

// Ficha.java

// Copyright (c) 1996, Agustin Froufe

// Todos los derechos reservados.

//

// No se asume ninguna responsabilidad por el uso o alteracion deeste

// software. Este software se proporciona COMO ES, sin garantia deningun

// tipo de su funcionamiento y en ningun caso sera el autor responsablede

// daños o perjuicios que se deriven del mal uso del software, aun cuan-do

// este haya sido notificado de la posibilidad de dicho daño.

//

// Compilador: javac 1.0

// Autor: Agustin Froufe

// Creacion: 12-Sep-1996 11:43:24

//

//--------------------------------------------------------------------------

// Esta informacion no es necesariamente definitiva y est sujeta a cam-bios

// que pueden ser incorporados en cualquier momento, sin avisar.

//--------------------------------------------------------------------------

import java.awt.*;

import java.lang.String;

import CuerpoFicha;

import Solapa;

// Crea un objeto que sera seleccionado cuando se pulse con el raton

// en su solapa. Contiene a dos objetos, uno de tipo Solapa y otro de

320 Tutorial de Java

// tipo cuerpoFicha, de forma que al seleccionar en el primero

// aparecera la solapa marcada y el contenido del objeto cuerpoFichero

// asociado a esa solapa

public class Ficha extends Panel {

public static final int SOLAPA_SELECCIONADA = 2000;

protected Solapa solapa;

protected CuerpoFicha cuerpoFicha;

public Ficha( String rotulo,Font normFont,Font seleFont,

Color colorRotulo,Color colorFicha ) {

super();

solapa = new Solapa( rotulo,normFont,seleFont,

colorRotulo,colorFicha );

cuerpoFicha = new CuerpoFicha( colorFicha );

}

// Devuelve el rotulo identificador de la solapa

public String getRotulo() {

return( solapa.getRotulo() );

}

// Devuelve los limites del cuerpo de la Ficha

public Rectangle getBordesFicha() {

return( cuerpoFicha.bounds() );

}

// Devuelve los limites que ocupa la solapa

public Rectangle getBordesSolapa() {

return( solapa.bounds() );

}

// Devuelve el punto final, ams alejado de la solapa

Tutorial de Java 321

public Point getFinalSolapa() {

return( solapa.getFinal() );

}

public boolean handleEvent( Event evt ) {

// La solapa devolvera true solamente si ha sido seleccionada

// en ese momento por el evento, aunque lo que siempre haremos

// sera notificarlo a ListaFichas, por lo que devolvemos false

// y el evento SOLAPA_SELECCIONADA se propagara hacia arriba

if( solapa.handleEvent( evt ) )

{

evt.id = SOLAPA_SELECCIONADA;

evt.arg = this;

}

return( false );

}

// Indica si la ficha esta seleccionada o no

public boolean estaSeleccionada() {

return( solapa.estaSeleccionada() );

}

public void medidas( Point solapaXY,Graphics g,

Dimension appTam,Point appPos ) {

FontMetrics fm;

int anchoRotulo;

int altoRotulo;

fm = g.getFontMetrics( solapa.getFuenteSel() );

anchoRotulo = fm.stringWidth( solapa.getRotulo() );

altoRotulo = fm.getHeight();

solapa.reshape( solapaXY.x,solapaXY.y,anchoRotulo+altoRotulo,

altoRotulo+(altoRotulo / 3) );

322 Tutorial de Java

cuerpoFicha.reshape( appPos.x,appPos.y+solapa.getAlto(),

appTam.width-1,appTam.height-solapa.getAlto()-1 );

}

public void paint( Graphics g ) {

if( estaSeleccionada() )

cuerpoFicha.paint( g );

solapa.paint( g );

}

public void seleccionar() {

solapa.seleccionar();

}

public void deSeleccionar() {

solapa.deSeleccionar();

}

}

//--------------------------------------------- Final del ficheroFicha.java

//

// ListaFichas.java

// Copyright (c) 1996, Agustin Froufe

// Todos los derechos reservados.

//

// No se asume ninguna responsabilidad por el uso o alteracion deeste

// software. Este software se proporciona COMO ES, sin garantia deningun

// tipo de su funcionamiento y en ningun caso sera el autor responsablede

// daños o perjuicios que se deriven del mal uso del software, aun cuan-do

Tutorial de Java 323

// este haya sido notificado de la posibilidad de dicho daño.

//

// Compilador: javac 1.0

// Autor: Agustin Froufe

// Creacion: 12-Sep-1996 12:46:13

//

//--------------------------------------------------------------------------

// Esta informacion no es necesariamente definitiva y est sujeta a cam-bios

// que pueden ser incorporados en cualquier momento, sin avisar.

//--------------------------------------------------------------------------

import java.awt.*;

import java.util.Vector;

import Ficha;

public class ListaFichas extends Panel {

protected Vector fichas;

protected Point posicion;

protected Dimension tama;

protected Font normFont;

protected Font seleFont;

protected Color fgColor;

protected Color bgColor;

protected Panel contenido;

public ListaFichas( Point posicion,Dimension tama,Font normFont,

Font seleFont,Color fgColor,Color bgColor,

String primerRotulo,Panel primerPanel ) {

super();

// Aqui fijamos el layout a null, porque el metodo medidas()

// sera el que controle ese layout

setLayout( null );

324 Tutorial de Java

// Nos creamos un objeto Vector, que nos va a permitir la

// manipulacion de la lista de las fichas

fichas = new Vector();

this.posicion = posicion;

this.tama = tama;

this.normFont = normFont;

this.seleFont = seleFont;

this.fgColor = fgColor;

this.bgColor = bgColor;

contenido = new Panel();

add( contenido );

contenido.setLayout( new CardLayout() );

// Incorporamos la primera ficha y la dejamos seleccionada

addFicha( primerRotulo,primerPanel );

primeraFicha().seleccionar();

}

// En este metodo incorporamos una ficha a la lista, con todos

// los valores por defecto

public synchronized void addFicha( String rotulo,Panel panel ) {

fichas.addElement( new Ficha( rotulo,normFont,seleFont,

fgColor,bgColor) );

contenido.add( rotulo,panel );

}

// En este metodo, nos permitimos especificar los colores de

// primer plano y de fondo con que se va a presentar la ficha

// en pantalla, a demas de incorporarla a la lista de fichas

public synchronized void addFicha( Color fgColor,Color bgColor,

String rotulo,Panel panel ) {

fichas.addElement( new Ficha( rotulo,normFont,seleFont,

fgColor,bgColor ) );

Tutorial de Java 325

contenido.add( rotulo,panel );

}

// Presenta el contenido de la ficha que se ha seleccionado

public void presentaFichaSeleccionada() {

( (CardLayout)contenido.getLayout() ).show( contenido,

( getSeleccionada().getRotulo() ) );

}

// Devuelve la primera ficha de la lista

public synchronized Ficha primeraFicha() {

return( (Ficha)fichas.firstElement() );

}

// Devuelve el contenido del control Tabular completo

public Panel getContenido() {

return contenido;

}

// Devuelve la siguiente ficha a la que se le pasa como parametro

public synchronized Ficha getSigFicha( Ficha f ) {

Ficha nextF;

// La numeracion de los elementos empieza en 0

if( fichas.indexOf( f ) < (fichas.size() - 1) )

nextF = fichaAt( fichas.indexOf(f)+1 );

else

nextF = null;

return( nextF );

}

326 Tutorial de Java

// Devuelve la ficha que actualmente esta seleccionada

public synchronized Ficha getSeleccionada() {

Ficha f;

// Nos colocamos en la primera y vamos comprobando

// ficha por ficha cual es la que esta seleccionada

f = primeraFicha();

while( (!f.estaSeleccionada()) && (f != null) )

f = getSigFicha( f );

return( f );

}

// Devuelve informacisn de si el evento se ha controlado o no

// en este manejador

public boolean handleEvent( Event evt ) {

boolean manejado;

Ficha f;

// Localizamos la ficha que ha generado el evento

f = primeraFicha();

manejado = f.handleEvent( evt );

if( !manejado )

f = getSigFicha( f );

while( (f != null) && (!manejado) )

{

manejado = f.handleEvent( evt );

f = getSigFicha( f );

}

// Si han seleccionado otra ficha, liberamos la actualmente

// seleccionada y seleccionamos la que nos llega en el

// evento, que correponde a la solapa que se ha pulsado

if( evt.id == Ficha.SOLAPA_SELECCIONADA )

Tutorial de Java 327

{

getSeleccionada().deSeleccionar();

( (Ficha)evt.arg ).seleccionar();

presentaFichaSeleccionada();

repaint();

manejado = true;

}

// Si aqui no se ha controlado el evento, se intenta que lo

// controle directamente alguno de los componentes que se

// hayan colocado en la ficha

if( !manejado )

manejado = contenido.handleEvent( evt );

return( manejado );

}

// Devuelve la ultima ficha del control Tabular

public synchronized Ficha ultimaFicha() {

return( (Ficha)fichas.lastElement() );

}

// Calcula el area que ocupa todo el Control Tabular

public void medidas( Graphics g ) {

Ficha f;

Point solapaXY;

// Calcula el area ocupada por las solapas

tama = size();

f = primeraFicha();

solapaXY = new Point( posicion.x+2,posicion.y+2 );

f.medidas( solapaXY,g,tama,posicion );

if( f != ultimaFicha() )

{

328 Tutorial de Java

do {

solapaXY = f.getFinalSolapa();

f = getSigFicha( f );

f.medidas( solapaXY,g,tama,posicion );

} while( f != ultimaFicha() );

}

// Calcula el area ocupada por los paneles que forman el

// cuerpo de las fichas

Rectangle r = primeraFicha().getBordesSolapa();

contenido.move( r.x+3,r.y+r.height+3 );

r = primeraFicha().getBordesFicha();

contenido.resize( r.width-10,r.height-10 );

}

public void paint( Graphics g ) {

Ficha f;

medidas( g );

f = primeraFicha();

while( f != null )

{

if( !f.estaSeleccionada() )

f.paint( g );

f = getSigFicha( f );

}

getSeleccionada().paint( g );

contenido.paintAll( g );

}

// Devuelve la ficha que se encuentre en la posicion que se indique

// en el indice que se pasa como paramentro

public synchronized Ficha fichaAt( int indice ) {

Ficha f;

Tutorial de Java 329

// Capturamos la excepcion de que se nos vaya fuera de

// limites el indice que nos pasan

try {

f = (Ficha)fichas.elementAt( indice );

} catch( ArrayIndexOutOfBoundsException e ) {

System.out.println("Error : No hay ficha #" + indice );

return( null );

}

return( f );

}

}

//--------------------------------------- Final del ficheroListaFichas.java

TRANSPARENCIAVamos a mostrar en esta aplicación un ejemplo de las nuevas técnicas que se están desa-rrollando actualmente. Utilizaremos para demostrar este método de Transparencia el dibujode presentación de la Home Page del Workshop de Sun.

No sé si el applet está hecho tal como yo lo voy a reproducir, pero el resultado que obtendre-mos será exactamente el mismo.

La técnica es muy sencilla, consiste en tener un dibujo grande con varias imágenes de lazona que queremos reproducir en pantalla, y utilizando los eventos del ratón, presentamosuna imagen u otra. Primero desarrollamos una clase para que controle el repintado de lasdistintas imágenes, AppletFijo.java, que se van a ir presentando y que no se repinte la zonatotal del applet, lo que produciría un desagradable efecto de parpadeo. Luego extendere-mos esta clase para la imagen completa, creando instancias de objetos BotonImg(BotonImg.java), que son los que van a controlar cada una de las opciones. Funcionan comobotones normales, sólo que cotnrolan de forma exhaustiva los eventos que llegan del ratón,BotonImg.java. Finalmente, en la clase MenuDeBotones (MenuDeBotones.java) es dondecreamos la clase principal, cargando la imagen de fondo y recuperando todos los parámetrosque se pasen en la llamada al applet.

Como podrá observarse, el código es muy sencillo y fácil de entender, y el resultado esaltamente profesional (tanto, que hasta Sun lo utiliza). Espero no haber violado ningún se-creto comercial al reproducir el funcionamiento del applet, aunque seguramente el métodoque he utilizado yo no es ni por asomo el que utiliza Sun. Yo no dejo de ser un mero aficio-nado.

330 Tutorial de Java

Esta técnica se puede utilizar con otras imágenes, otras opciones, y otros lanzamientos adirecciones. Espero que a alguien pueda serle útil.

//

// AppletFijo.java

// Copyright (c) 1996, Agustin Froufe

// Todos los derechos reservados.

//

Tutorial de Java 331

// No se asume ninguna responsabilidad por el uso o alteracion de este

// software. Este software se proporciona COMO ES, sin garantia de ningun

// tipo de su funcionamiento y en ningun caso sera el autor responsable de

// daños o perjuicios que se deriven del mal uso del software, aun cuando

// este haya sido notificado de la posibilidad de dicho daño.

//

// Compilador: javac 1.0

// Autor: Agustin Froufe

// Creacion: 29-Oct-1996 11:04:12

//

//--------------------------------------------------------------------------

// Esta informacion no es necesariamente definitiva y est sujeta a cambios

// que pueden ser incorporados en cualquier momento, sin avisar.

//--------------------------------------------------------------------------

import java.awt.*;

import java.applet.Applet;

// En este applet lo que intentamos es que cuando llegue el mensaje de que

// actualicemos la pantalla, el método update() se encargue solamente de la

// zona que nosotros le indiquemos, y que no fluctúe todo el dibujo

public class AppletFijo extends Applet {

private Image imagen;

private Graphics imgGr;

private Dimension imgTam;

public final synchronized void update( Graphics g ) {

Dimension dim = size();

if( imagen == null || dim.width != imgTam.width ||

dim.height != imgTam.height )

{

imagen = createImage( dim.width,dim.height );

imgTam = dim;

imgGr = imagen.getGraphics();

imgGr.setFont( getFont() );

332 Tutorial de Java

}

imgGr.fillRect( 0,0,dim.width,dim.height );

paint( imgGr );

g.drawImage( imagen,0,0,null );

}

public AppletFijo() {}

}

//---------------------------------------- Final del fichero AppletFijo.java

//

// BotonImg.java

// Copyright (c) 1996, Agustin Froufe

// Todos los derechos reservados.

//

// No se asume ninguna responsabilidad por el uso o alteracion de este

// software. Este software se proporciona COMO ES, sin garantia de ningun

// tipo de su funcionamiento y en ningun caso sera el autor responsable de

// daños o perjuicios que se deriven del mal uso del software, aun cuando

// este haya sido notificado de la posibilidad de dicho daño.

//

// Compilador: javac 1.0

// Autor: Agustin Froufe

// Creacion: 11-Sep-1996 11:35:43

//

//--------------------------------------------------------------------------

// Esta informacion no es necesariamente definitiva y est sujeta a cambios

// que pueden ser incorporados en cualquier momento, sin avisar.

//--------------------------------------------------------------------------

import java.awt.*;

import java.awt.image.*;

import java.applet.*;

import java.net.*;

Tutorial de Java 333

public class BotonImg {

private Applet appSuperior;

public Image imagen;

public Image imagenUp;

public Image imagenDown;

public Image imagenMove;

public ImageFilter filtro;

public String mensaje;

public int estado;

public int offX;

public int offY;

public int tamX;

public int tamY;

public URL url;

public String href;

// En este método vamos fijando las imágenes que se presentarán en función

// del estado en que se encuentre el ratón

// Utilizamos un método que Java proporciona en la clase java.awt.imagen

// que nos permite extraer una zona de una imagen y guardarla como imagen

// completa. De este modo extraemos del dibujo que contiene las tres imágenes

// posibles, cada imagen correspondiente a los eventos que le llegan al

// objeto enviados por el ratón

public void init() {

filtro = new CropImageFilter( 0,0,tamX,tamY );

imagenUp = appSuperior.createImage(

new FilteredImageSource( imagen.getSource(),filtro ) );

filtro = new CropImageFilter( 0, tamY,tamX,tamY );

imagenMove = appSuperior.createImage(

new FilteredImageSource( imagen.getSource(),filtro ) );

filtro = new CropImageFilter( 0,tamY*2,tamX,tamY );

imagenDown = appSuperior.createImage(

new FilteredImageSource( imagen.getSource(),filtro ) );

// Aquí componemos la dirección a la que saltar cuando se pique con el

// ratón la zona que corresponda al botón

try {

334 Tutorial de Java

url = new URL( appSuperior.getDocumentBase(),href );

} catch( MalformedURLException e ) {

url = null;

}

return;

}

// Constructor, al que le pasamos el applet que correponde al dibujo completo

public BotonImg( Applet applet ) {

appSuperior = applet;

}

// Sobrecargamos el método showDocument, para que no se abra con las

// características del applet del botón, sino con las del padre, que sí

// tendrá las características de la página

public void showDocument( URL uRL ) {

appSuperior.getAppletContext().showDocument( uRL );

}

// En este método, pintamos un dibujo u otro sobre el botón, en función del

// estado que nos devuelva el sistema para el cursor y los botones del

// ratón

public void paint( Graphics g ) {

if( estado == 0 )

g.drawImage( imagenUp,offX,offY,appSuperior );

else if(estado == 1)

g.drawImage( imagenDown,offX,offY,appSuperior );

else if( estado == 2 )

g.drawImage( imagenMove,offX,offY,appSuperior );

}

// En los tres métodos que siguen, controlamos los eventos que nos llegan del

// ratón. La técnica a seguir es la misma en cada uno de ellos, mirar si

// el cursor se encuentra dentro del campo de acción del botón que estamos

// trantando, si no está dentro del campo del botón, pero sí dentro del

// dibujo general del applet, devolvemos el control al applet general para

Tutorial de Java 335

// que siga comprobando si está el cursor sobre otro botón. Si el cursor se

// encuentra en nuestro campo de acción, actuamos en consecuencia

public boolean mouseDown( Event evt,int i,int j ) {

if( i >= offX && i < offX + tamX &&

j >= offY && j < offY + tamY )

{

// Fijamos el estado a 1, para que cuando se suelte el botón

// podamos saltar a la dirección que se haya especificado en la

// lista de parámetros del applet

estado = 1;

appSuperior.repaint();

}

else if( estado != 0 )

{

appSuperior.showStatus( "" );

estado = 0;

appSuperior.repaint();

}

return( false );

}

public boolean mouseUp( Event evt,int i,int j ) {

if( i >= offX && i < offX + tamX &&

j >= offY && j < offY + tamY )

{

// Si previamente habíamos pulsado el ratón sobre este botón,hacemos

// que se muestre la página correcpondiente a la dirección URLque se

// había cargado

if( estado == 1 )

showDocument( url );

estado = 2;

appSuperior.repaint();

}

else if( estado != 0 )

{

336 Tutorial de Java

appSuperior.showStatus( "" );

estado = 0;

appSuperior.repaint();

}

return( false );

}

public boolean mouseMove( Event evt,int i,int j ) {

if( i >= offX && i < offX + tamX &&

j >= offY && j < offY + tamY )

{

// Si sólo movemos el cursor sobre el botón, nos limitamos apresentar

// el mensaje que se ha definido en los parámetros de llamada al

// applet

estado = 2;

appSuperior.showStatus( mensaje );

appSuperior.repaint();

}

else if( estado != 0 )

{

appSuperior.showStatus( "" );

estado = 0;

appSuperior.repaint();

}

return( false );

}

}

//------------------------------------------ Final del fichero BotonImg.java

//

// MenuDeBotones.java

// Copyright (c) 1996, Agustin Froufe

// Todos los derechos reservados.

//

Tutorial de Java 337

// No se asume ninguna responsabilidad por el uso o alteracion de este

// software. Este software se proporciona COMO ES, sin garantia de ningun

// tipo de su funcionamiento y en ningun caso sera el autor responsable de

// daños o perjuicios que se deriven del mal uso del software, aun cuando

// este haya sido notificado de la posibilidad de dicho daño.

//

// Compilador: javac 1.0

// Autor: Agustin Froufe

// Creacion: 29-Oct-1996 11:44:46

//

//--------------------------------------------------------------------------

// Esta informacion no es necesariamente definitiva y est sujeta a cambios

// que pueden ser incorporados en cualquier momento, sin avisar.

//--------------------------------------------------------------------------

import java.awt.*;

public class MenuDeBotones extends AppletFijo {

private int colFondo;

BotonImg tit;

BotonImg dem;

BotonImg ovw;

BotonImg faq;

BotonImg res;

Image fondo;

// En este método leemos todos los datos que se pasan desde la llamada

// al applet en la página HTML. Por cada una de las opciones que se

// encuentren en la página, crearemos un objeto BotonImg que sera el

// que controle los movimientos del ratón sobre ese Boton

public void init() {

colFondo = Integer.parseInt( getParameter( "Color" ),16 );

// Carga la imagen de fondo

fondo = getImage( getCodeBase(),getParameter( "Fondo" ) );

// Obtenemos los datos del botón que corresponderá a la opción de

338 Tutorial de Java

// "Java WorkShop - Home Page"

tit = new BotonImg( this );

// Esta es la posición X,Y en la que se coloca la imagen del botón

// sobre la imagen principal

tit.offX = Integer.valueOf( getParameter( "tit.offX" ) ).intValue();

tit.offY = Integer.valueOf( getParameter( "tit.offY" ) ).intValue();

// Este es el tamaño de la imagen que corresponde al botón en ancho y

// alto. En la imagen asociada que contiene las distintas imágenes de

// estados en que se puede encotnrar el botón, nos encontraremos con

// tres imágenes diferentes que estarán despalazadas en Y justamente

// el tamaño en altura que ocupa cada botón

tit.tamX = Integer.valueOf( getParameter( "tit.tamX" ) ).intValue();

tit.tamY = Integer.valueOf( getParameter( "tit.tamY" ) ).intValue();

// Esta es el dibujo que contiene las tres imágenes en que va a poder

// presentarse este botón

tit.imagen = getImage( getCodeBase(),getParameter( "tit.imagen" ) );

// Este es el mensaje que aparecerá en la zona de estado del Navegador

// cuando el cursor del ratón pase por la zona ocupada por el botón

tit.mensaje = getParameter( "tit.mensaje" );

// Esta es la dirección URL a la que se saltará cuando se actúe sobre

// el botón

tit.href = getParameter( "tit.href" );

tit.init();

// Obtenemos los datos del botón que corresponderá a la opción de

// "Demo Download Area & Product Pricing"

// Los parámetros son los mismos que en el caso anterior

dem = new BotonImg( this );

dem.offX = Integer.valueOf(getParameter( "dem.offX" ) ).intValue();

dem.offY = Integer.valueOf(getParameter( "dem.offY" ) ).intValue();

dem.tamX = Integer.valueOf(getParameter( "dem.tamX" ) ).intValue();

dem.tamY = Integer.valueOf(getParameter( "dem.tamY" ) ).intValue();

dem.imagen = getImage( getCodeBase(),getParameter( "dem.imagen" ) );

dem.mensaje = getParameter( "dem.mensaje" );

dem.href = getParameter( "dem.href" );

dem.init();

Tutorial de Java 339

// Obtenemos los datos del botón que corresponderá a la opción de

// "Development Tools Overview & Tutorials"

// Los parámetros son los mismos que en el caso anterior

ovw = new BotonImg( this );

ovw.offX = Integer.valueOf(getParameter( "ovw.offX" ) ).intValue();

ovw.offY = Integer.valueOf(getParameter( "ovw.offY" ) ).intValue();

ovw.tamX = Integer.valueOf(getParameter( "ovw.tamX" ) ).intValue();

ovw.tamY = Integer.valueOf(getParameter( "ovw.tamY" ) ).intValue();

ovw.imagen = getImage( getCodeBase(),getParameter( "ovw.imagen" ) );

ovw.mensaje = getParameter( "ovw.mensaje" );

ovw.href = getParameter( "ovw.href" );

ovw.init();

// Obtenemos los datos del botón que corresponderá a la opción de

// "Java WorkShop FAQ"

faq = new BotonImg(this);

faq.offX = Integer.valueOf( getParameter( "faq.offX" ) ).intValue();

faq.offY = Integer.valueOf( getParameter( "faq.offY" ) ).intValue();

faq.tamX = Integer.valueOf( getParameter( "faq.tamX" ) ).intValue();

faq.tamY = Integer.valueOf( getParameter( "faq.tamY" ) ).intValue();

faq.imagen = getImage( getCodeBase(),getParameter("faq.imagen" ) );

faq.mensaje = getParameter( "faq.mensaje" );

faq.href = getParameter( "faq.href" );

faq.init();

// Obtenemos los datos del botón que corresponderá a la opción de

// "Resources"

res = new BotonImg( this );

res.offX = Integer.valueOf(getParameter( "res.offX" ) ).intValue();

res.offY = Integer.valueOf(getParameter( "res.offY" ) ).intValue();

res.tamX = Integer.valueOf(getParameter( "res.tamX" ) ).intValue();

res.tamY = Integer.valueOf(getParameter( "res.tamY" ) ).intValue();

res.imagen = getImage( getCodeBase(),getParameter( "res.imagen" ) );

res.mensaje = getParameter( "res.mensaje" );

res.href = getParameter( "res.href" );

340 Tutorial de Java

res.init();

}

// Aquí pintamos la imagen de fondo y le pasamos el control a cada uno de los

// botones para que controles la imagen que deben pintar en función de la

// acción que se realiza sobre ellos

public void paint( Graphics g ) {

g.setColor( new Color( colFondo ) );

g.fillRect( 0,0,size().width,size().height );

g.drawImage( fondo,0,0,this );

tit.paint( g );

dem.paint( g );

ovw.paint( g );

faq.paint( g );

res.paint( g );

}

// Ahora en los métodos que controlan los estados de los botones del ratón,

// pasamos el control a los botones, para que cambien la imagen que

// corresponde al botón sobre el que está actuando el ratón, ya sea solamente

// moviéndose sobre él o actuando con los botones sobre la zona que

// corresponde a ese botón

public boolean mouseUp( Event evt,int i,int j ) {

tit.mouseUp( evt,i,j );

dem.mouseUp( evt,i,j );

ovw.mouseUp( evt,i,j );

faq.mouseUp( evt,i,j );

res.mouseUp( evt,i,j );

return( false );

}

public boolean mouseDown( Event evt,int i,int j ) {

tit.mouseDown( evt,i,j );

dem.mouseDown( evt,i,j );

ovw.mouseDown( evt,i,j );

faq.mouseDown( evt,i,j );

Tutorial de Java 341

res.mouseDown( evt,i,j );

return( false );

}

public boolean mouseMove( Event evt,int i,int j ) {

tit.mouseMove( evt,i,j );

dem.mouseMove( evt,i,j );

ovw.mouseMove( evt,i,j );

faq.mouseMove( evt,i,j );

res.mouseMove( evt,i,j );

return( false );

}

public MenuDeBotones() {}

}

//------------------------------------- Final del fichero MenuDeBotones.java

CALCULADORAEl applet Calculadora.java presenta una calculadora básica, tomando como herramienta deconstrucción el AWT. Es una calculadora que realiza las operaciones más habituales, tipocalculadora solar, pero que ilustra perfectamente los mecanismos de presentación en panta-lla de Componentes del AWT sobre un layout propio, ya que todos los botones se colocanen posiciones determinadas.

Las operaciones matemáticas no son motivo de estudio, pero sí se podr•a completar lacalculadora con más funciones y hacerla semejante a las que se proporcionan con casitodos los entornos gráficos. La clase BotonCalculadora es la fundamental en este ejemplo,ya que nos permite colocar botones en cualquier sitio y con cualquier tamaño.

342 Tutorial de Java

//

// Calculadora.java

// Copyright (c) 1996, Agustin Froufe

// Todos los derechos reservados.

//

// No se asume ninguna responsabilidad por el uso o alteracion de este

// software. Este software se proporciona COMO ES, sin garantia de ningun

// tipo de su funcionamiento y en ningun caso sera el autor responsable de

// daños o perjuicios que se deriven del mal uso del software, aun cuando

// este haya sido notificado de la posibilidad de dicho daño.

//

// Compilador: javac 1.0

// Autor: Agustin Froufe

// Creacion: 12-Sep-1996 15:39:38

//

//--------------------------------------------------------------------------

// Esta informacion no es necesariamente definitiva y est sujeta a cambios

// que pueden ser incorporados en cualquier momento, sin avisar.

//--------------------------------------------------------------------------

import java.awt.*;

import java.applet.Applet;

public class Calculadora extends Applet {

TextField pantallita;

public final int OP_NADA = 0;

public final int OP_SUMA = 1;

public final int OP_RESTA = 2;

public final int OP_MULT = 3;

public final int OP_DIV = 4;

public final int OP_IGUAL = 5;

public final int OP_BORRAR_C = 6;

public final int OP_BORRAR_AC = 7;

public final int PUNTO_DECIMAL = -1;

String cadDecimal;

Tutorial de Java 343

int operador = OP_NADA;

boolean nuevoNumero = true;

boolean esDecimal = false;

double Total = 0.0;

boolean esConstante = false;

double numConstante = 0.0;

int opConstante = OP_NADA;

public void init() {

BotonCalculadora bot0,bot1,bot2,bot3,bot4,bot5,bot6,bot7,bot8,bot9;

BotonCalculadora botDecimal,botSuma,botResta;

BotonCalculadora botMult,botDiv,botIgual,botBorraC,botBorraAC;

// No utilizamos ningún la layout porque vamos a colocar

// los botones y el campo de texto para mostrar el resultado

// de las operaciones, en posiciones fijas

setLayout( null );

setFont( new Font( "Helvetica",Font.PLAIN,14 ) );

setBackground( new Color( 0x66,0xA2,0xD4 ) );

// Pintamos los botones de los numeros

bot0 = new BotonCalculadora( "0",OP_NADA,0 );

add( bot0 );

bot0.reshape( 8,144,96,24 );

bot1 = new BotonCalculadora( "1",OP_NADA,1 );

add( bot1 );

bot1.reshape( 8,112,40,24 );

bot2 = new BotonCalculadora( "2",OP_NADA,2 );

add( bot2 );

bot2.reshape( 64,112,40,24 );

bot3 = new BotonCalculadora( "3",OP_NADA,3 );

add( bot3 );

bot3.reshape( 120,112,40,24 );

bot4 = new BotonCalculadora( "4",OP_NADA,4 );

add( bot4 );

bot4.reshape( 8,80,40,24 );

344 Tutorial de Java

bot5 = new BotonCalculadora( "5",OP_NADA,5 );

add( bot5 );

bot5.reshape( 64,80,40,24 );

bot6 = new BotonCalculadora( "6",OP_NADA,6 );

add( bot6 );

bot6.reshape( 120,80,40,24 );

bot7 = new BotonCalculadora( "7",OP_NADA,7 );

add( bot7 );

bot7.reshape( 8,48,40,24 );

bot8 = new BotonCalculadora( "8",OP_NADA,8 );

add( bot8 );

bot8.reshape( 64,48,40,24 );

bot9 = new BotonCalculadora( "9",OP_NADA,9 );

add( bot9 );

bot9.reshape( 120,48,40,24 );

// Pintamos los botones que nos van a permitir realizar las

// operaciones soportadas poe esta mini-calculadora

botDecimal = new BotonCalculadora( "¸",OP_NADA,PUNTO_DECIMAL );

add( botDecimal );

botDecimal.reshape( 120,144,40,24 );

botSuma = new BotonCalculadora( "+",OP_SUMA,0 );

add( botSuma );

botSuma.reshape( 176,112,40,56 );

botResta = new BotonCalculadora( "-",OP_RESTA,0 );

add( botResta );

botResta.reshape( 232,112,40,24 );

botMult = new BotonCalculadora( "*",OP_MULT,0 );

add( botMult );

botMult.reshape( 176,80,40,24 );

botDiv = new BotonCalculadora( "/",OP_DIV, 0);

add( botDiv );

botDiv.reshape( 176,48,40,24 );

botIgual = new BotonCalculadora( "=",OP_IGUAL,0 );

add( botIgual );

botIgual.reshape( 232,144,40,24 );

Tutorial de Java 345

botBorraC = new BotonCalculadora( "C",OP_BORRAR_C,0 );

add( botBorraC );

botBorraC.reshape( 232,48,40,24 );

botBorraAC = new BotonCalculadora( "AC",OP_BORRAR_AC,0 );

add( botBorraAC );

botBorraAC.reshape( 232,80,40,24 );

// Pintamos el campo de texto en que vamos a reflejar lo que se

// se va introduciendo y el resultado de las operaciones

pantallita = new TextField( "0",80 );

pantallita.setEditable( false );

add( pantallita );

pantallita.reshape( 48,8,184,31 );

String unaDecima = ( new Double(0.1) ).toString();

cadDecimal =

unaDecima.substring( unaDecima.length()-2 ).substring( 0,1 );

}

public static void main( String args[] ) {

Frame f = new Frame( "Calculadora Java" );

Calculadora c = new Calculadora();

c.init();

f.add( "Center",c );

f.pack();

f.resize( 395,181 );

f.show();

}

public void anadir( int valor ) {

String digito;

// Controlamos el punto decimal, para que si es lo primero que se

// pulsa, desplazar un cero a la izquierda, y si se pulsa cuando

346 Tutorial de Java

// ya estamos en un numero decimal, no hacerle caso

if( valor == PUNTO_DECIMAL )

if( !esDecimal )

{

if( nuevoNumero )

{

pantallita.setText( "0" );

nuevoNumero = false;

}

esDecimal = true;

digito = cadDecimal;

}

else

return;

else

digito = ( new Integer(valor) ).toString();

// Si tenemos que presentar un numero nuevo, porque estemos ya en

// otra operacion, borramos antes la pantallaita

if( nuevoNumero )

{

pantallita.setText( digito );

if( valor != 0 )

nuevoNumero = false;

}

else

pantallita.setText( pantallita.getText() + digito );

repaint();

}

// Ejecuta la operacion que indica el operador del boton que se

// haya pulsado

public void haceOperacion( int nuevoOperador ) {

double numero;

Tutorial de Java 347

numero = ( new Double( pantallita.getText() ) ).doubleValue();

switch( nuevoOperador )

{

case OP_SUMA:

case OP_RESTA:

case OP_MULT:

case OP_DIV:

if( nuevoNumero )

{

if( nuevoOperador == operador && !esConstante )

{

esConstante = true;

numConstante = numero;

opConstante = nuevoOperador;

}

else

esConstante = false;

}

else

esConstante = false;

case OP_IGUAL:

if( !nuevoNumero || esOperadorIgual( nuevoOperador ) )

{

if( esConstante )

{

Total = numConstante;

operador = opConstante;

}

// Controla los operadores de operaciones

// matematicas

switch( operador )

{

case OP_SUMA:

Total = Total + numero;

348 Tutorial de Java

break;

case OP_RESTA:

Total = Total - numero;

break;

case OP_MULT:

Total = Total * numero;

break;

case OP_DIV:

Total = Total / numero;

break;

case OP_IGUAL:

case OP_NADA:

Total = numero;

break;

}

// Presenta el resultado del tecleo en la pantalla

pantallita.setText(( new Double(Total) ).toString());

}

operador = nuevoOperador;

nuevoNumero = true;

esDecimal = false;

break;

}

// Controla los operadores de borrado tanto el Clear como el

// AllClear

switch( nuevoOperador )

{

// Borra el numero y cancela la operacion en curso

// solamente

case OP_BORRAR_C:

numero = 0.0;

pantallita.setText( "0" );

if( esOperadorIgual( operador ) )

Total = numero;

Tutorial de Java 349

nuevoNumero = true;

esDecimal = false;

break;

// Borra todas las operaciones y numeros, lo deja

// todo a cero

case OP_BORRAR_AC:

pantallita.setText( "0" );

operador = OP_NADA;

nuevoNumero = true;

esDecimal = false;

Total = 0.0;

esConstante = false;

break;

}

}

// Comprueba si es el operador igual, para presentar resultado

private protected boolean esOperadorIgual( int oper ) {

return( oper == OP_IGUAL );

}

}

// Esta clase se utiliza para pintar cada uno de los botones que van

// a conformar la calculadora

class BotonCalculadora extends Button {

int operador;

int bvalor;

BotonCalculadora( String texto, int oper, int valor ) {

super( texto );

operador = oper;

bvalor = valor;

}

350 Tutorial de Java

public boolean action( Event evt, Object arg ) {

Calculadora par = ( Calculadora )getParent();

// Si no se tiene ninguna operacion matematica asociada, solamente

// se añade el numero a la pantallita, y si ya es una

// operacion, entonces se hace y se presenta el resultado

if( operador == par.OP_NADA )

par.anadir( bvalor );

else

par.haceOperacion( operador );

return( true );

}

}

//--------------------------------------- Final del fichero Calculadora.java

CUENTA-KILOMETROSEl applet Contador.java presenta una imagen conocida de cuenta-kilómetros de coche, perocuyo código nos sirve para demostrar varias cosas. Podemos comprobar cómo se manipulael offset de un dibujo porque la imagen de los números es un fichero gráfico que contiene los10 dígitos posibles, y el contador va seleccionando cada uno de ellos en función de la cifraque tiene que representar el cuanta-kilómetros. También podemos comprobar la utilizaciónde threads para la implementación del intervalo de tiempo que tarda el cuenta-kilómetros enpasar de una cifra a la siguiente.

En la llamada al applet, podemos indicar el número de dígitos que va a contener el cuenta-kilómetros, el intervalo de tiempo que transcurrirá entre los cambios de cifras y el valor incialen que arranca el contador. Además, como es un applet muy sencillo, lo hemos adornadocon algunas opciones, por ejemplo:

· Con la tecla «+» aceleramos el contador· Con la tecla «-» disminuímos la velocidad con que cambian los números en el cuenta-

kilómetros· Con la tecla «0» ponemos a cero el contador· Utilizando el ratón, si picamos una vez sobre el cuenta-kilómetros, se detiene la cuenta,

y si picamos una segunda vez, vuelve a seguir contando

Tutorial de Java 351

//// Contador.java// Copyright (c) 1996, Agustin Froufe// Todos los derechos reservados.//// No se asume ninguna responsabilidad por el uso o alteracion de este// software. Este software se proporciona COMO ES, sin garantia de ningun// tipo de su funcionamiento y en ningun caso sera el autor responsable de// daños o perjuicios que se deriven del mal uso del software, aun cuando// este haya sido notificado de la posibilidad de dicho daño.//// Compilador: javac 1.0// Autor: Agustin Froufe// Creacion: 14-Sep-1996 16:33:10////--------------------------------------------------------------------------// Esta informacion no es necesariamente definitiva y est sujeta a cambios// que pueden ser incorporados en cualquier momento, sin avisar.//--------------------------------------------------------------------------

import java.awt.*;import java.applet.Applet;

// Esta clase implementa un contador gráfico, tipo cuentakilómetros de// coche. Admite que se le pasen tres parámetros desde la llamada al// applet en la página html que lo incorpore, son:// DIGITOS Número de dígitos del contador// INTERVALO Tiempo a transcurrir entre cada incremento// VALOR_INICIAL Cifra inicial desde la que empieza a contar// Utiliza un fichero que contiene las imágenes de los dígitos y// de ahí va extrayendo cada una de las figuras para componer el// número que tenga que representar en pantalla//public class Contador extends Applet implements Runnable { Thread thread = null; MediaTracker tracker = null; Image numeros; int numero_ancho = 15; // Pixels de ancho de cada uno de los numeros int numero_alto = 20; // Pixels de alto de cada uno de los numeros int digitos; int intervalo; String valor; int estado = 0;

public void init() { String parametro;

// Creamos un objeto mediatracker para soportar el contador tracker = new MediaTracker( this );

try { // Cargamos la imagen que contiene los numeros que vamos a // presentar en el contador numeros = getImage( getCodeBase(),"numeros.gif" ); tracker.addImage( numeros,0 );

// Numero de digitos de que consta el contador parametro = getParameter( "DIGITOS" ); digitos = Integer.parseInt( parametro );

// Intervalo entre cada actualizacion del contador parametro = getParameter( "INTERVALO" ); intervalo = Integer.parseInt ( parametro );

// Valor del contador cuando lo arrancamos valor = getParameter( "VALOR_INICIAL" ); if( valor == null ) valor = String.valueOf( (long)( Math.random() * (long)( Math.pow( 10,digitos ) ) ) ); } catch( Exception e ) { return; }

// Ponemos el applet al tamaño más adecuado

352 Tutorial de Java

resize( ( digitos * numero_ancho ),numero_alto );

// Creamos y arrancamos el thread que va a controla al // contador thread = new Thread( this ); thread.start(); }

public void stop() { if( thread != null ) { thread.stop(); thread = null; } }

// Método principal del thread, donde se llama a la función de // pintado, que es donde se componen las imágenes de los números // para reflejar la cifra por donde anda el contador public void run() { // Una vez que se ha cargado la imagen de los número, seguimos tracker.checkAll( true );

// repitamos y nos dormimos el intervalo de tiempo que se haya // indicado for( ;; ) { repaint();

try { Thread.sleep( intervalo ); } catch( InterruptedException e ) { ; } } }

// Cuando se pulsa el ratón, se para el contador y cuando se // vuelva a pulsar, seguirá contando public boolean mouseDown( Event e,int x,int y ) { if( thread != null ) { thread.stop(); thread = null; } else { thread = new Thread( this ); thread.start(); } return true; }

// Controlamos algunas teclas, para poder manipular el contador public boolean keyDown( Event e,int key ) { switch( key ) { case '+': // Va más rápido intervalo -= 100; if( intervalo < 10 ) intervalo = 10; break;

case '-': // Va más lento intervalo += 100; break;

case '0': // Lo reseteamos valor = String.valueOf( 0 ); estado = 0; break;

Tutorial de Java 353

default: valor = String.valueOf( (long)( Math.random() * (long)( Math.pow( 10,digitos ) )

) ); estado = 0; break; }

return true; }

// Aquí no borramos la pantalla, sólo pintamos encima public void update( Graphics g ) { paint( g ); }

// Pintamos el número public void paint( Graphics g ) { int i; int digit; boolean scroll;

if( tracker.checkAll() == false ) { g.setColor( Color.black ); g.fillRect( 0,0,(digitos * numero_ancho),numero_alto ); return; }

int zeros = digitos - valor.length(); for( i=0; i < zeros; i++ ) { Graphics gc = g.create( i * numero_ancho,0, numero_ancho,numero_alto ); gc.drawImage( numeros,0,0,this ); gc.dispose(); }

scroll = true;

for( i=valor.length() - 1; i >= 0;i-- ) { digit = valor.charAt( i ) - '0';

Graphics gc = g.create( (zeros+i) * numero_ancho,0, numero_ancho,numero_alto );

// Tenemos en cuenta cuando se va a cambiar de un número // a otro para ir mostrando la mitad del anterior y la primera // mitad del siguiente para dar la sensación de movimiento del // display del contador if( scroll ) { switch( estado ) { case 0: if( digit == 0 ) gc.drawImage( numeros,0, -( (9 * numero_alto) + numero_alto / 2 ),this

); gc.drawImage( numeros,0, -( (digit * numero_alto) - numero_alto / 2 ),this

); break;

case 1: gc.drawImage( numeros,0, -(digit * numero_alto),this ); break;

case 2: gc.drawImage( numeros,0,

354 Tutorial de Java

-( (digit * numero_alto) + numero_alto / 2 ),this);

if( digit == 9 ) gc.drawImage( numeros,0, -( (0 * numero_alto) - numero_alto / 2 ),this

); break; } } else gc.drawImage( numeros,0,-(digit * numero_alto),this );

gc.dispose();

if( digit != 9 ) scroll = false; }

if( estado >= 2 ) { estado = 0; long l = Long.parseLong( valor ); l++; valor = String.valueOf( l ); } estado++; } }

//---------------------------------------- Final del fichero Contador.java

POTENCIOMETROEl applet EjPotenciometro.java presenta un ejemplo de uso de un Componente nuevo quehemos implementado para incorporar a las posibilidades que ofrece el AWT. Se trata de unaBarra de Desplazamiento, pero semejando el potenciómetro deslizante de un equipo elec-trónico.

Incorpora una serie de posibilidades nuestro Componente Potenciometro.java, que el ejem-plo permite utilizar y comporbar, como es el posicionamiento de la escala indicadora. En elapplet ejemplo, hemos utilizado el Componente Grupo , que ya habíamos implementado elcapítulos anteriores.

Tutorial de Java 355

//

// EjPotenciometro.java

// Copyright (c) 1996, Agustin Froufe

// Todos los derechos reservados.

//

// No se asume ninguna responsabilidad por el uso o alteracion de este

// software. Este software se proporciona COMO ES, sin garantia de ningun

// tipo de su funcionamiento y en ningun caso sera el autor responsable de

// daños o perjuicios que se deriven del mal uso del software, aun cuando

// este haya sido notificado de la posibilidad de dicho daño.

//

// Compilador: javac 1.0

// Autor: Agustin Froufe

// Creacion: 16-Dic-1996 17:13:51

//

//--------------------------------------------------------------------------

// Esta informacion no es necesariamente definitiva y esta sujeta a cambios

// que pueden ser incorporados en cualquier momento, sin avisar.

//--------------------------------------------------------------------------

import java.awt.*;

import java.applet.*;

public class EjPotenciometro extends Applet {

Button izda;

Button dcha;

Button todo;

TextField inds;

TextField inds1;

Potenciometro s;

Potenciometro s1;

public void init() {

setBackground( Color.lightGray );

setLayout( null );

356 Tutorial de Java

// Utilizamos el componente Grupo que ya tenemos desarrollado

// Para agrupar los componentes de esta demo. El primer

// Grupo agrupa a todos los elementos: barras, botones, textos

Grupo g = new Grupo( "Ejemplo de Potenciómetro" );

add( g );

g.setFont( new Font( "Dialog",0,12 ) );

g.reshape( insets().left+20,insets().top+20,520,250 );

g.setLayout( null );

// Creamos un potenciómetro vertical

s = new Potenciometro( 0,150,2 );

g.add( s );

s.reshape( 40,50,42,180 );

s.setValor( 0 );

inds = new TextField( Integer.toString( s.getValor()),4 );

g.add( inds );

inds.reshape( 36,20,45,28 );

// Creamos otro potenciómetro, horizontal

s1 = new Potenciometro( 0,240,1 );

g.add( s1 );

s1.reshape( 120,35,365,40 );

s1.setValor( 0 );

inds1 = new TextField( Integer.toString( s1.getValor()),4 );

g.add( inds1 );

inds1.reshape( 420,80,50,28 );

// Este otro grupo engloba a los botones que permiten posicionar

// las marcas por encima o debajo de la barra

Grupo g2 = new Grupo( "Posición Indicador" );

g.add( g2 );

g2.reshape( 105,150,400,70 );

izda = new Button( "Izquierda/Arriba" );

g2.add( izda );

izda.reshape( 15,25,120,30 );

dcha = new Button( "Derecha/Abajo" );

g2.add( dcha );

dcha.reshape( 145,25,110,30 );

Tutorial de Java 357

todo = new Button( "Ambas Posiciones" );

g2.add( todo );

todo.reshape( 265,25,125,30 );

}

public boolean handleEvent( Event evt ) {

System.out.println( "Target "+evt.target );

System.out.println( " ID "+evt.id );

// Controlamos los eventos de los botones para posicionar

// las marcas y del propio potenciómetro para controlar

// la posicion del indicador y el valor en el texto

if( evt.target == izda && evt.id == 1001 )

{

s.setPosicionMarca( 1 );

s.update();

s1.setPosicionMarca( 1 );

s1.update();

return( true );

}

if( evt.target == dcha && evt.id == 1001 )

{

s.setPosicionMarca( 2 );

s.update();

s1.setPosicionMarca( 2 );

s1.update();

return( true );

}

if( evt.target == todo && evt.id == 1001 )

{

s.setPosicionMarca( 3 );

s.update();

s1.setPosicionMarca( 3 );

s1.update();

return( true );

}

if( evt.target == s && evt.id == 1001 )

358 Tutorial de Java

{

inds.setText( Integer.toString( s.getValor() ) );

return( true );

}

if( evt.target == s1 && evt.id == 1001 )

{

inds1.setText( Integer.toString( s1.getValor() ) );

return( true );

}

switch( evt.id ) {

default:

return( false );

}

}

}

//----------------------------------- Final del fichero EjPotenciometro.java

//

// Potenciometro.java

// Copyright (c) 1996, Agustin Froufe

// Todos los derechos reservados.

//

// No se asume ninguna responsabilidad por el uso o alteracion de este

// software. Este software se proporciona COMO ES, sin garantia de ningun

// tipo de su funcionamiento y en ningun caso sera el autor responsable de

// daños o perjuicios que se deriven del mal uso del software, aun cuando

// este haya sido notificado de la posibilidad de dicho daño.

//

// Compilador: javac 1.0

// Autor: Agustin Froufe

// Creacion: 16-Dic-1996 16:25:46

//

//--------------------------------------------------------------------------

// Esta informacion no es necesariamente definitiva y esta sujeta a cambios

Tutorial de Java 359

// que pueden ser incorporados en cualquier momento, sin avisar.

//--------------------------------------------------------------------------

import java.awt.*;

public class Potenciometro extends Canvas {

public static final int HORIZONTAL = 1;

public static final int VERTICAL = 2;

public static final int IZQUIERDA = 1;

public static final int DERECHA = 2;

public static final int SUBIR = 3;

public static final int BAJAR = 4;

public static final int SUPERIOR = 1;

public static final int INFERIOR = 2;

public static final int SUP_INF = 3;

boolean boolDrag;

int valor;

int Max;

int Min;

int x_offset;

int y_offset;

int hat_high;

int espacioMarca;

int posicion;

int linIncremento;

int pagIncremento;

int hat_offset;

int orientacion;

int posicionMarca;

Image offscreen;

static Font defFuente;

public Potenciometro( int min,int max,int orient ) {

this( min,max,orient,3 );

}

360 Tutorial de Java

public Potenciometro( int min,int max,int orient,int posMarca ) {

boolDrag = false;

Max = 100;

hat_high = 12;

espacioMarca = 30;

linIncremento = 1;

pagIncremento = 30;

orientacion = 1;

posicionMarca = 3;

Max = max;

Min = min;

valor = Min;

posicionMarca = posMarca;

orientacion = orient;

setFont( defFuente );

}

// Método que utilizamos para fijar los marcadores en un valor

// determinado

// Aquí se encuentran todos los métodos de get() y set() que se

// utilizan para fijar y recuperar los valores de los parámetros

// que controlan al potenciómetro

public void setValor( int v ) {

Dimension d = size();

valor = v;

if( orientacion == 2 )

{

posicion = d.height-hat_high - (valor-Min) *

(d.height-hat_high) / (Max-Min);

return;

}

posicion = d.width-hat_high - (valor-Min) *

(d.width-hat_high) / (Max-Min);

}

public int getValor() {

Tutorial de Java 361

return( valor );

}

public void setMaximo( int v ) {

Max = v;

}

public int getMaximo() {

return( Max );

}

public void setMinimo( int v ) {

Min = v;

}

public int getMinimo() {

return( Min );

}

public void setEspacioMarca( int f ) {

espacioMarca = f;

}

public int getEspacioMarca() {

return( espacioMarca );

}

public void setPosicionMarca( int p ) {

posicionMarca = p;

}

public int getPosicionMarca() {

return( posicionMarca );

}

public void setPagIncremento( int i ) {

362 Tutorial de Java

pagIncremento = i;

}

public int getPagIncremento() {

return( pagIncremento );

}

public void setLinIncremento( int i ) {

linIncremento = i;

}

public int getLinIncremento() {

return( linIncremento );

}

public void setOrientacion( int o ) {

orientacion = o;

}

public int getOrientacion() {

return( orientacion );

}

// Repintamos el potenciómetro, sólo una de las posiciones

public void paint( Graphics g ) {

if( orientacion == 1 )

{

printHSlider( g );

return;

}

printVSlider( g );

}

// Pinta el potenciómetro vertical

public void printVSlider( Graphics g ) {

Tutorial de Java 363

Dimension d = size();

x_offset = 17;

y_offset = 4;

g.setColor( Color.lightGray );

g.fill3DRect( x_offset,y_offset,4,d.height-2*y_offset,false );

g.setColor( Color.black );

g.drawLine( x_offset+1,y_offset+1,x_offset+1,d.height-y_offset-2 );

g.setColor( Color.lightGray );

g.fill3DRect( 5,posicion,27,hat_high,true );

g.setColor( getForeground() );

int delta = espacioMarca*d.height/Max;

for( int y = hat_high/2; y < d.height-hat_high/2; y += delta )

{

if( posicionMarca != 2 )

g.drawLine( 0,y,3,y );

if( posicionMarca != 1 )

g.drawLine( 33,y,36,y );

}

if( posicionMarca != 2 )

g.drawLine( 0,d.height-hat_high/2,3,d.height-hat_high/2 );

if( posicionMarca != 1 )

g.drawLine( 33,d.height-hat_high/2,36,d.height-hat_high/2 );

if( posicionMarca != 2 )

drawTriangulo( g,9,posicion+3,3,1,true );

if( posicionMarca != 1 )

drawTriangulo( g,27,posicion+3,3,2,true );

}

// Pinta el potenciómetro horizontal

public void printHSlider( Graphics g ) {

Dimension d = size();

y_offset = 17;

x_offset = 4;

364 Tutorial de Java

g.setColor( Color.lightGray );

g.fill3DRect( x_offset,y_offset,d.width-2*x_offset,4,false );

g.setColor( Color.black );

g.drawLine( x_offset+1,y_offset+1,d.width-x_offset-2,y_offset+1 );

g.setColor( Color.lightGray );

g.fill3DRect( posicion,5,hat_high,27,true );

g.setColor( getForeground() );

int delta = espacioMarca*d.width/Max;

for( int x=hat_high/2; x < d.width-hat_high/2; x += delta )

{

if( posicionMarca != 2 )

g.drawLine( x,0,x,3 );

if( posicionMarca != 1 )

g.drawLine( x,33,x,36 );

}

if( posicionMarca != 2 )

g.drawLine( d.width-hat_high/2,0,d.width-hat_high/2,3 );

if( posicionMarca != 1 )

g.drawLine( d.width-hat_high/2,33,d.width-hat_high/2,36 );

if( posicionMarca != 2 )

drawTriangulo( g,posicion+3,9,3,3,true );

if( posicionMarca != 1 )

drawTriangulo( g,posicion+3,27,3,4,true );

}

public void update() {

Dimension d = size();

if( offscreen == null )

offscreen = createImage( d.width,d.height );

if( offscreen == null )

return;

Tutorial de Java 365

Graphics offg = offscreen.getGraphics();

Graphics g = getGraphics();

offg.clipRect( 0,0,d.width,d.height );

offg.setFont( getFont() );

offg.setColor( getBackground() );

offg.fillRect( 0,0,d.width,d.height );

offg.setColor( g.getColor() );

paint( offg );

g.clipRect( 0,0,d.width,d.height );

g.drawImage( offscreen,0,0,this );

}

// Ahora controlamos los eventos que nos llegan del ratón, que son

// el pique y el arrastre

public boolean onMouseDown( int x,int y ) {

Dimension d = size();

if( orientacion == 2 && y > posicion && y < posicion+hat_high )

{

hat_offset = y-posicion;

boolDrag = true;

return( true );

}

if( orientacion == 1 && x > posicion && x < posicion+hat_high )

{

hat_offset = x-posicion;

boolDrag = true;

return( true );

}

if( orientacion == 2 )

{

if( y < posicion )

posicion = (posicion > pagIncremento) ?

(posicion-pagIncremento) : 0;

else if( y > posicion )

366 Tutorial de Java

posicion = (posicion < d.height-hat_high-pagIncremento) ?

(posicion+pagIncremento) : (d.height-hat_high);

valor = Min + (Max-Min) * (d.height-hat_high-posicion) /

(d.height-hat_high);

}

if( orientacion == 1 )

{

if( x < posicion )

posicion = (posicion > pagIncremento) ?

(posicion-pagIncremento) : 0;

else if( x > posicion )

posicion = (posicion < d.width-hat_high-pagIncremento) ?

(posicion+pagIncremento) : (d.width-hat_high);

valor = Min + (Max-Min) * (d.width-hat_high-posicion) /

(d.width-hat_high);

}

update();

return( true );

}

public boolean onMouseDrag( int x,int y ) {

Dimension d = size();

if( boolDrag && orientacion == 2 )

{

posicion = y-hat_offset;

if( posicion < 0 )

posicion = 0;

if( posicion > d.height-hat_high )

posicion = d.height-hat_high;

update();

}

if( boolDrag && orientacion == 1 )

{

posicion = x-hat_offset;

Tutorial de Java 367

if( posicion < 0 )

posicion = 0;

if( posicion > d.width-hat_high )

posicion = d.width-hat_high;

update();

}

return( true );

}

public boolean handleEvent( Event evt ) {

Event evnt;

switch( evt.id ) {

case 501:

return( onMouseDown( evt.x,evt.y ) );

case 502:

evnt = new Event( this,1001,new Integer( valor ) );

break;

case 506:

return( onMouseDrag( evt.x,evt.y ) );

default:

return( false );

}

postEvent( evnt );

boolDrag = false;

return( true );

}

public void reshape( int x,int y,int w,int h ) {

super.reshape( x,y,w,h );

offscreen = createImage( w,h );

update();

}

public Dimension preferredSize() {

368 Tutorial de Java

return( minimumSize() );

}

public Dimension minimumSize() {

return( new Dimension( 40,40 ) );

}

// Pintamos un triangulito sobre el indicador, que apunta hacia

// donde se encuentra la escala que estamos usando

private void drawTriangulo( Graphics g,int x,int y,

int tam,int orient,boolean habil ) {

if( habil )

g.setColor( Color.black );

else

g.setColor( Color.gray );

for( int i=0; i < tam; i++ )

{

switch( orient ) {

case 3:

g.drawLine( x+i,y-i,x+2*tam-i-1,y-i );

break;

case 4:

g.drawLine( x+i,y+i,x+2*tam-i-1,y+i );

break;

case 1:

g.drawLine( x-i,y+i,x-i,y+2*tam-i-1 );

break;

case 2:

g.drawLine( x+i,y+i,x+i,y+2*tam-i-1 );

break;

}

}

}

static {

defFuente = new Font( "Dialog",0,16);

Tutorial de Java 369

}

}

//------------------------------------- Final del fichero Potenciometro.java

CARTELde Luis Angel OrtegaEste applet, Cartel.java, resenta un texto en una sola línea que se desplaza a derecha oizquierda y es una contribución de un lector del Tutorial. Lo siguiente es suyo.

———————————————sicApplet de libre distribución, puedes enviar tus sugerencias a Luis Angel Ortega paracompartir información y conocimientos sobre el lenguaje Java y Internet.

Datos del autor:

Nombre: Luis Angel Ortega ¨LAO 1996Dirección: [email protected] visitar su Página Personal

———————————————sic

//*************************************************************************

// Cartel.java: Applet //

// //

// Propiedad LAO ® 1996 Luis Angel Ortega Todos los derechos reservados //

// //

// Permiso para usar copiar, modificar, y distribuir este software //

// y su documentacion, sin propositos comerciales. //

// //

// Para cualquier duda o comentario con el autor [email protected] //

// //

// Pagina WEB http://wwww.arrakis.es/~lao //

// //

// El autor no se resposabiliza de posibles daños que el software //

// pueda efectuar en cualquier equipo informatico. //

// //

370 Tutorial de Java

// //

// Autor: Luis Angel Ortega LAO 25/11/1996 //

// Creado con Microsoft Visual J++ Version 1.0 //

// //

// El Applet es de libre distribucion, presenta un texto que se //

// desplaza a derecha o izquierda. //

// //

// Los parámetros son los siguientes: //

// //

// ==================================================================== //

// PARAMETROS | VALORES DE LOS PARAMETROS //

// ===================================================================== //

// TEXTO = Cadena de texto a desplazar. | "Texto a desplazar" //

// ===================================================================== //

// DESPLAZAMIENTO = Desplazar el texto | "IZQUIERDA" //

// a izquierda o derecha. | "DERECHA" //

// =================================================================== //

// COLORFONDO = Color para el fondo | "ROJO" //

// del texto. | "AZUL" //

// | "NEGRO" //

// | "BLANCO" //

// =================================================================== //

// COLORTEXTO = Color para el texto | "ROJO" //

// | "AZUL" //

// | "NEGRO" //

// | "BLANCO" //

// =================================================================== //

// WIDTH = Ancho del Applet | Un numero mayor que 12. //

// =================================================================== //

// HEIGHT = Ancho del Applet | Un numero mayor que 12. //

// =================================================================== //

// VELOCIDAD =Velocidad en milisegundos | Un numero mayor que 1 //

// de la actualización en pantalla del | //

// applet, depende de la velocidad del | //

// ordenador donde se visualiza el | //

// applet | //

Tutorial de Java 371

// =================================================================== //

// //

// Notas: //

// El parámetro de la VELOCIDAD es opcional, por defecto es 10. //

// El texto se ajusta automáticamente al alto del Applet. //

// La velocidad de desplazamiento depende de la velocidad del //

// procesador. //

// Los valores de los parámetros pueden ser en minúsculas o mayúsculas. //

// //

// Ejemplo: //

// //

// <applet code="Cartel.class" //

// width="200" height="50" id="Cartel"> //

// <param name="COLORFONDO" value="negro"> //

// <param name="COLORTEXTO" value="rojo"> //

// <param name="TEXTO" value="Ejemplo de Applet"> //

// <param name="DESPLAZAMIENTO" value="izquierda"> //

// <param name="VELOCIDAD" value="5"> //

// Tu Visualizador WEB no puede ejecutar código JAVA</applet> //

// //

//*************************************************************************

import java.applet.*;

import java.awt.*;

public class Cartel extends Applet implements Runnable

{

Thread m_Cartel = null;

// Todas las constantes y Variables excepto Texto, son declaradas

// privadas para no poder ser usadas desde otra clase

// constantes

private final int RESTAFONT = 14;

// Variables

372 Tutorial de Java

private int Velocidad; // Velocidad del thread en milisegundos

private int CordY; // Cordenada y donde situar el texto

private int CordX; // Cordenada x donde situar el texto

private int StringAncho; // Ancho de la cadena a desplazar

private int SizeDescent;

private Font font; // Tipo de letra del texto

private Dimension d; // Dimensiones del applet

// La misión de estas tres variables es almacenar un objeto gráfico

// en memoria, que es una copia del objeto que se crea al pintar el applet.

private Dimension offDimension;

private Image offImage;

private Graphics offGraphics;

private Color ColorTexto; // Color del texto

private Color ColorFondo; // Color del fondo

public String Texto; // Texto a desplazar

private boolean Scroll; // TRUE=Izquierda FALSE=Derecha

//**********************************************************************//

// Metodo Actualizar() //

// Utilizado para poder cambiar los parametros del applet, mediante //

// codigo en JavaScript o VBscript. //

// Nota: //

// Es posible cambiar el texto sin llamar al metodo Actualizar //

// //

// Entrada: //

// String Text = Texto a desplazar. //

// String Desplaza = Desplaza el texto a "IZQUIERDA O "DERECHA" //

// String ColFondo = Cambia el color del fondo //

// "ROJO","AZUL","NEGRO","BLANCO" //

// String ColTexto = Cambia el color del texto //

// "ROJO","AZUL","NEGRO","BLANCO" //

// String Veloc = Velocidad del Thread en milisegundos. //

// //

// Salida: void //

//**********************************************************************//

Tutorial de Java 373

public void Actualizar (String Text,String Desplaza,String ColFondo,

String ColTexto, String Veloc)

{

try {

// Comprueba si los datos pasados al metodo son los correctos

ColFondo = ColFondo.toUpperCase(); // Lo convierte a Mayusculas

ColorFondo = (ColFondo.compareTo("ROJO")==0) ? Color.red : ColorFondo;

ColorFondo = (ColFondo.compareTo("AZUL")==0) ? Color.blue : ColorFondo;

ColorFondo = (ColFondo.compareTo("NEGRO")==0) ? Color.black : ColorFondo;

ColorFondo = (ColFondo.compareTo("BLANCO")==0) ? Color.white : ColorFondo;

ColTexto = ColTexto.toUpperCase(); // Lo convierte a Mayusculas

ColorTexto = (ColTexto.compareTo("ROJO")==0) ? Color.red : ColorTexto;

ColorTexto = (ColTexto.compareTo("AZUL")==0) ? Color.blue : ColorTexto;

ColorTexto = (ColTexto.compareTo("NEGRO")==0) ? Color.black : ColorTexto;

ColorTexto = (ColTexto.compareTo("BLANCO")==0) ? Color.white : ColorTexto;

Desplaza = Desplaza.toUpperCase(); // Lo convierte a Mayusculas

Scroll = (Desplaza.compareTo("DERECHA")==0) ? false : true;

Texto=Text;

offGraphics = null; // La proxima vez que pinte crea un nuevo Graphics

} catch (NullPointerException e) { // Ejecuta este codigo en el caso que

ColorTexto = Color.red; // algunas de las cadenas que preceden

ColorFondo = Color.black; // al catch sean un nulo

Scroll = true;

Texto= "Parametro del applet incorrecto";

Velocidad=10;

}

}

//**********************************************************************//

374 Tutorial de Java

// Metodo CogerParametros() //

// Recoge los valores pasados como parametros dentro del codigo HTML //

// //

// Entrada: Ninguna //

// //

// Salida: void //

//**********************************************************************//

void CogerParametros ()

{

try {

String Parametro=getParameter("VELOCIDAD");// Obtiene el parametro velocidad

Integer Veloc=Integer.valueOf(Parametro); // Convierte el String en un

// objeto tipo Integer

Velocidad=Veloc.intValue(); // Convierte el valor del objeto Integer

// en un entero (int)

} catch (NumberFormatException e) { // La cadena introducida comoparametro

// no es un numero entero

Velocidad=10; // Establece la velocidad del Thread

// en 10 milisegundos

} catch (NullPointerException e ) { // Cadena introducida es un nulo

Velocidad=10;

}

try {

// Coge los parametros del applet, los compara y almacena en variables

// el valor adecuado

String Parametro=getParameter ("COLORFONDO");

Parametro=Parametro.toUpperCase(); // Lo convierte a Mayusculas

Tutorial de Java 375

ColorFondo = (Parametro.compareTo("ROJO")==0) ? Color.red : ColorFondo;

ColorFondo = (Parametro.compareTo("AZUL")==0) ? Color.blue : ColorFondo;

ColorFondo = (Parametro.compareTo("NEGRO")==0) ? Color.black : ColorFondo;

ColorFondo = (Parametro.compareTo("BLANCO")==0) ? Color.white : ColorFondo;

Parametro=getParameter ("COLORTEXTO");

Parametro=Parametro.toUpperCase(); // Lo convierte a Mayusculas

ColorTexto = (Parametro.compareTo("ROJO")==0) ? Color.red : ColorTexto;

ColorTexto = (Parametro.compareTo("AZUL")==0) ? Color.blue : ColorTexto;

ColorTexto = (Parametro.compareTo("NEGRO")==0) ? Color.black : ColorTexto;

ColorTexto = (Parametro.compareTo("BLANCO")==0) ? Color.white : ColorTexto;

Parametro=getParameter ("DESPLAZAMIENTO");

Parametro=Parametro.toUpperCase(); // Lo convierte a Mayusculas

Scroll = (Parametro.compareTo("DERECHA")==0) ? false : true;

Texto=getParameter ("TEXTO"); // Coge el texto

}

catch (NullPointerException e) { // Excepcion producida por alguna

// cadena vacia o nula

ColorTexto = Color.red;

ColorFondo = Color.black;

Scroll = true;

Texto = "Parametro del applet incorrecto";

}

}

//**********************************************************************//

// Metodo update() //

// Es llamado por el metodo repaint(), cada vez que se dispara el thread//

// //

// La primera vez que es llamado crea una copia en memoria //

// (doble buffer) del objeto Graphics g. //

// Una vez creado, todos los elementos graficos, son dibujados //

// sobre el buffer offGraphics, una vez dibujados son mostrados en la //

376 Tutorial de Java

// pantalla del sistema. //

// //

// Entrada: Graphics g //

// //

// Salida: void //

//**********************************************************************//

public void update (Graphics g)

{

// Si offGraphics no existe o cambia de tamaño lo crea

if ( (offGraphics == null) ||

(d.width != offDimension.width) ||

(d.height != offDimension.height) ) {

offDimension = d;

offImage = createImage (d.width, d.height); // Crea la imagen en

// memoria

offGraphics = offImage.getGraphics(); // Almacena el objeto

// grafico en memoria

// Establece el font

offGraphics.setFont (font);

// Establece la medida descent del Font

FontMetrics fontmetrics = offGraphics.getFontMetrics();

SizeDescent = fontmetrics.getDescent();

// Almacena el ancho del la cadena

StringAncho = fontmetrics.stringWidth(Texto);

// Establece las cordenadas de comienzo dependiendo de la variable

// Scroll (desplazamiento a derecha o izquierda)

CordX = Scroll ? d.width : -StringAncho;

CordY=(d.height-SizeDescent)-7;

}

// Borra la imagen previa que tenemos en el buffer

Tutorial de Java 377

offGraphics.setColor(getBackground());

offGraphics.fillRect(0,0, d.width , d.height);

offGraphics.setColor (Color.black);

offGraphics.setColor (ColorFondo); // Establece el color de fondo

offGraphics.fillRect (0,0,d.width,d.height); // Rellena el fondo

offGraphics.setColor(ColorTexto); // Establece el color de texto

offGraphics.drawString (Texto,CordX,CordY); // Pinta el texto

CordX= Scroll ? --CordX : ++CordX; //Suma o resta la cordenada X,

// dependiendo del valor de la

// variable Scroll

// Comprueba si el texto que se desplaza llega al final del margen

// izquierdo o derecho del applet

if (CordX == d.width ) CordX=-StringAncho; //Derecha

else if (CordX == -StringAncho) CordX=d.width; // Izquierda

// Dibuja el marco del applet

offGraphics.setColor (Color.lightGray);

offGraphics.fill3DRect (0,0,d.width,5,true);

offGraphics.fill3DRect (0,(d.height-5),d.width,5,true);

offGraphics.fill3DRect (0,0,5,d.height,true);

offGraphics.fill3DRect ((d.width-5),0,5,d.height,true);

g.drawImage (offImage, 0 ,0 ,this); // Muestra la imagen almacenada

// en el buffer

}

//**********************************************************************//

// Metodo Cartel() //

// Constructor de la clase //

// //

// Entrada: Ninguna //

// //

378 Tutorial de Java

// Salida: void //

//**********************************************************************//

public Cartel()

{

// Sin codigo

}

//**********************************************************************//

// Metodo getAppletInfo() //

// //

// Retorna los datos del applet, autor y sistema de desarrollo //

// //

// Entrada: Ninguna //

// //

// Salida: String //

//**********************************************************************//

public String getAppletInfo()

{

return "Name: Cartel\r\n" +

"Author: Luis Angel Ortega LAO 25/11/1996\r\n" +

"Created with Microsoft Visual J++ Version 1.0";

}

//**********************************************************************//

// Metodo init //

// //

// Este metodo es llamado por el AWT cuando el applet es cargado por //

// primera vez, o necesita ser recargado. //

// //

// Entrada: Ninguna //

// //

// Salida: Void //

//**********************************************************************//

Tutorial de Java 379

public void init()

{

d = size(); // Almacena las dimensiones del applet

resize(d.width,d.height); // Redimensiona el applet de nuevo

// Es nesario en el caso de que se vuelva a

// cargar el applet

Toolkit toolkit = Toolkit.getDefaultToolkit();

String h[] = toolkit.getFontList(); // Almacena en h todos los

// tipos de letra

// Crea un nuevo font con con el atributo de normal

// y el tamaño del ancho del applet menos RESTAFONT (14)

font = new Font (h[1],Font.PLAIN,(d.height-RESTAFONT));

CogerParametros(); // Llama al metodo para coger los

// parametros del applet

}

//**********************************************************************//

// Metodo destroy() //

// //

// Este metodo es llamado cuando el applet es destruido de memoria //

// //

// Entrada: Ninguna //

// //

// Salida: Void //

//**********************************************************************//

public void destroy()

{

// Sin codigo

}

//**********************************************************************//

// Metodo paint() //

380 Tutorial de Java

// //

// Este metodo es llamado cuando el applet necesita ser pintado. //

// Si colocamos una ventana que oculte el applet, o parte del mismo, //

// desplazamos o quitamos la ventana, se produce el evento paint. //

// //

// Nota: //

// La llamada de repaint no produce necesariamente la llamada al paint//

// //

// Entrada: Graphics g //

// //

// Salida: Void //

//**********************************************************************//

public void paint(Graphics g)

{

if (offImage != null ) { // Si offImage existe visuliza la imagen

g.drawImage(offImage, 0 ,0 ,this);

}

}

//**********************************************************************//

// Metodo start //

// //

// Entrada: Ninguna //

// //

// Salida: Void //

//**********************************************************************//

public void start()

{

if (m_Cartel == null) // Si no existe el Thread lo crea

{

m_Cartel = new Thread(this);

m_Cartel.start(); // Produce la llamada al metodo run()

}

}

Tutorial de Java 381

//**********************************************************************//

// Metodo stop //

// //

// Este metodo se produce cuando cambiamos de pagina o cerramos //

// el visualizador //

// La llamada al metodo provoca la parada del Thread //

// //

// Entrada: Ninguna //

// //

// Salida: Void //

//**********************************************************************//

public void stop()

{

if (m_Cartel != null) // Provoca la parada del Thread

{

m_Cartel.stop();

m_Cartel = null;

}

}

//**********************************************************************//

// Metodo run() //

// //

// Este metodo es llamado cuando el applet del Thread es llamado //

// //

// Entrada:Ninguna //

// //

// Salida: Void //

//**********************************************************************//

public void run()

{

while (true) // Produce un bucle infinito

382 Tutorial de Java

{

try

{

repaint(); // Llama al metodo update()

Thread.sleep(Velocidad); // Establece la velocidad del Thread

}

catch (InterruptedException e) // Excepcion

{

stop(); // Llama al metodo stop() para el Thread

}

}

}

} //Final del la clase

Tutorial de Java 383

Final y Agradecimientos

...Y esto ha sido todo, amigos!

Espero que todo el esfuerzo que hemos realizado para que este Tutorial de Java pudiese ver la luz hayaservido para que, al menos, alguien de los que se hayan atrevido a seguirlo dominen un poco más el lenguajeque representa Java.

Nosotros (yo) sí que he aprendido mucho durante estos meses que me he pasado escribiendo y desarrollandoejemplos. Me he encontrado con problemas, que quizás en un uso normal de Java no se hubiesen presentado,sobre todo porque he utilizado tres plataformas diferentes para probar lo que hacía: Solaris 2.5, Linux 1.3.20 yWindows ’95. El escoger los volcados de Windows ’95 para los ejemplos ha sido porque todo el Tutorial se haescrito en Word para Windows ’95.

Agradecimientos

Mi agradecimiento a Juan Antonio Ruíz y Ramón M. Gómez ( bético hasta la médula),webmaster de este site y para la Facultad de Informática de Sevilla, por haberme permitidoutilizar su servidor para que todo el mundo pueda conocer mi trabajo. Y por supuesto a mifamilia, sin cuya comprensión no hubiese podido llevar este proyecto a buen trérmino, por-que les he robado gran cantidad del tiempo que debería estar con ellos.

Copyrights

Este documento no tiene, en principio, copyright; aunque si se utiliza alguna información enél contenida, agradecría que se citase la procedencia. Por supuesto, cualquier tipo de distri-bución del Tutorial está absolutamente permitida, aunque también me gustaría conocerla.Las fuentes principales de las que he recogido información para poder desarrollar esteTutorial de Java corresponden a documentación de Sun, aunque también a otras:

· · The Java Tutorial de Sun Microsystems· · Brewing Java: A Tutorial de Elliot Rusty Harold· Revista Electrónica · JavaWorld· · Gamelan, gran fuente de applets

384 Tutorial de Java

· Revista Electrónica · SunWorld Online· Faq’s de Java en · DigitalFocus· · WWWiz, lista impresionante de libros sobre Java, Internet, etc.· · Java Programming Resources, de Marty Hall, infinidad de recursos sobre Java

HotJava(tm), Java(tm) y Duke son Marcas Registradas de Sun MicrosystemsMicrosoft es una marca registrada y el logo del Microsoft Internet Explorer es una marcaregistrada de Microsoft