Libro MVC Con Java
Libro MVC Con Java
por
Agustín Froufe
([email protected])
[email protected]
2 Tutorial de Java
Indice Tabla 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
Windows 95 32
Solaris 32
Linux 32
Programación en Java 35
Tutorial de Java 3
· · 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
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
4 Tutorial de Java
· · Punteros C/C++ y Referencias Java 57
Programas Básicos en Java 59
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
El parámetro ARCHIVE 77
· · Depuración general 78
· · Ciclo de vida de un Applet 79
· · Protección de Applets 79
Tutorial de Java 5
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
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
6 Tutorial de Java
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
AWT 105
Tutorial de Java 7
Estructura del AWT 106
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
8 Tutorial de Java
Control de Eventos 150
· · La clase Event 151
· · Tipos de Eventos 152
Eventos de Ventana 152
10 Tutorial de Java
Prioridades, demonios... 221
· · Prioridades 221
· · Threads Demonio 221
· · Diferencia de Threads con fork() 221
Ejemplo de Animación 222
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
Tutorial de Java 11
Cierre de FileInputStream 240
Sockets 250
· · Sockets Stream 250
· · Sockets Datagrama 250
· · Sockets Raw 250
Diferencias entre Sockets Stream y Datagrama 251
12 Tutorial de Java
· · 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
Observable 276
· · Cómo utilizar Observer y Observable 276
Extender un Observable 277
Tutorial de Java 13
Aplicaciones en Java 281
Etiqueta 281
Persiana 303
Solapas 310
Transparencia 329
Calculadora 341
Cuenta-Kilómetros 350
Potenciómetro 354
Agradecimientos 383
Copyrights 383
14 Tutorial de Java
Capítulo 1 Introducción a Java
El uso principal que se hace de Internet e incluso de las redes internas (corporativas) es
correo 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 clases
y proporcionar enlaces hipertexto hacia cualquier lugar de la red. Los navegadores utilizan
documentos 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 ejecutar
un programa de demostración, primero hemos de bajarnos (download) el fichero en cuestión
y luego utilizar un programa en nuestro ordenador capaz de entender el formato de ese
fichero, o bien cargar un módulo (plug-in) en nuestro navegador para que pueda interpretar
el 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 entre
formularios definidos en lenguaje HTML y programas escritos en Perl o en C. Esta interfaz
resulta muy incómoda de programar y es pobre en sus posibilidades.
El lenguaje Java y los navegadores con soporte Java, proporcionan una forma diferente de
hacer que ese navegador sea capaz de ejecutar programas. Con Java se puede reproducir
sonido 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, cuando
se pueda transmitir video por las líneas telefónicas, nuestro navegador estará preparado
para mostrar esas imágenes.
Además, Java proporciona una nueva forma de acceder a las aplicaciones. El software viaja
Tutorial de Java 15
transparentemente a través de la red. No hay necesidad de instalar las aplicaciones, ellas
mismas vienen cuando se necesitan. Por ejemplo, la mayoría de los navegadores del Web
pueden procesar un reducido número de formatos gráficos (típicamente GIF y JPEG). Si se
encuentran 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. Sin
embargo, un navegador con soporte Java puede enlazar con el servidor que contiene el
algoritmo 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 seguro
de que hay una copia en código Java de ese algoritmo instalada en el servidor que contiene
las imágenes que quiere publicar. Es decir, los navegadores con soporte Java se actualizan
a 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 mucho
tiempo es «the network is the computer» (lo que quiere dar a entender que el verdadero
ordenador es la red en su conjunto y no cada máquina individual), es quien ha desarrollado
el lenguaje Java, en un intento de resolver simultáneamente todos los problemas que se le
plantean 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 y
sistemas de ventanas que funcionaban sobre una misma máquina, añadiendo la dificultad
de crear aplicaciones distribuidas en una red como Internet.
He podido leer más de cinco versiones distintas sobre el origen, concepción y desarrollo de
Java, desde la que dice que este fue un proyecto que rebotó durante mucho tiempo por
distintos 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.
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ía
unos 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 de
desarrollo. James Gosling, el miembro del equipo con más experiencia en lenguajes de
programación, decidió que las ventajas aportadas por la eficiencia de C++ no compensaban
el gran coste de pruebas y depuración. Gosling había estado trabajando en su tiempo libre
en un lenguaje de programación que él había llamado Oak, el cual, aún partiendo de la
sintaxis de C++, intentaba remediar las deficiencias que iba observando.
16 Tutorial de Java
Los lenguajes al uso, como C o C++, deben ser compilados para un chip, y si se cambia el
chip, todo el software debe compilarse de nuevo. Esto encarece mucho los desarrollos y el
problema 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 los
fabricantes a incluirlo en las nuevas series de sus cadenas de producción, por pequeña que
sea la diferencia en precio ya que, multiplicada por la tirada masiva de los aparatos, supone
un ahorro considerable. Por tanto, Gosling decidió mejorar las características de Oak y
utilizarlo.
El primer proyecto en que se aplicó este lenguaje recibió el nombre de proyecto Green y
consistía en un sistema de control completo de los aparatos electrónicos y el entorno de un
hogar. Para ello se construyó un ordenador experimental denominado *7 (Star Seven). El
sistema presentaba una interfaz basada en la representación de la casa de forma animada
y el control se llevaba a cabo mediante una pantalla sensible al tacto. En el sistema aparecía
Duke, la actual mascota de Java. Posteriormente se aplicó a otro proyecto denominado
VOD (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 a
ser un gran éxito, urgieron a FirstPerson a desarrollar con rapidez nuevas estrategias que
produjeran 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 los
desarrolladores principales del Unix de Berkeley, juzgó que Internet podría llegar a ser el
campo de juego adecuado para disputar a Microsoft su primacía casi absoluta en el terreno
del software, y vio en Oak el instrumento idóneo para llevar a cabo estos planes. Tras un
cambio de nombre y modificaciones de diseño, el lenguaje Java fue presentado en sociedad
en agosto de 1995.
Lo mejor será hacer caso omiso de las historias que pretenden dar carta de naturaleza a la
clarividencia industrial de sus protagonistas; porque la cuestión es si independientemente
de su origen y entorno comercial, Java ofrece soluciones a nuestras expectativas. Porque
tampoco 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 de
programación, son:
Es SIMPLE:
Java ofrece toda la funcionalidad de un lenguaje potente, pero sin las características menos
usadas 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
Tutorial de Java 17
reducidas las especificaciones del lenguaje y añadir características muy útiles como el garbage
collector (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 en
acción, permite liberar bloques de memoria muy grandes, lo que reduce la fragmentación de
la memoria.
Java reduce en un 50% los errores más comunes de programación con lenguajes como C y
C++ 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), ya
que 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 cosas
para mantener el objetivo de la simplicidad del lenguaje. Java trabaja con sus datos como
objetos 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 de
objetos son llamadas, como en C++, clases y sus copias, instancias. Estas instancias, como
en 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 sistema
operativo Next. En C++ se suele trabajar con librerías dinámicas (DLLs) que obligan a
recompilar 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 variables
de instancias o implementación de métodos. Las clases en Java tienen una representación
en el runtime que permite a los programadores interrogar por el tipo de clase y enlazar
dinámicamente la clase con el resultado de la búsqueda.
Es DISTRIBUIDO:
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
18 Tutorial de Java
máquinas, interactuando.
Es ROBUSTO:
Para establecer Java como parte integral de la red, el compilador Java compila su código a
un fichero objeto de formato independiente de la arquitectura de la máquina en que se
ejecutará. Cualquier máquina que tenga el sistema de ejecución (run-time) puede ejecutar
ese 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, Windows
NT, Linux, Irix, Aix, Mac, Apple y probablemente haya grupos de desarrollo trabajando en el
porting a otras plataformas.
Tutorial de Java 19
El código fuente Java se «compila» a un código de bytes de alto nivel independiente de la
má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 parte
de la arquitectura de Java sobre una plataforma genérica, obtendríamos una figura como la
siguiente:
En ella podemos ver que lo verdaderamente dependiente del sistema es la Máquina Virtual
20 Tutorial de Java
Java (JVM) y las librerías fundamentales, que también nos permitirían acceder directamente
al hardware de la máquina. Además, habrá APIs de Java que también entren en contacto
directo con el hardware y serán dependientes de la máquina, como ejemplo de este tipo de
APIs 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 o
el casting implícito que hacen los compiladores de C y C++ se eliminan para prevenir el
acceso ilegal a la memoria. Cuando se usa Java para crear un navegador, se combinan las
caracterí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 de
alineació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 la
memoria y pueda sumar (o restar) algún valor, para referirse a otro lugar de la memoria. Si
otros programadores conocen nuestras estructuras de datos pueden extraer información
confidencial 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 un
programa 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 programador
deja un comando destructivo bajo esta referencia, se puede correr el riesgo de ejecutar
có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 Troya
haya enviado por correo el /etc/shadow a su creador, ejecuta la funcionalidad de ls persentando
el 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 a
través de un verificador de byte-codes que comprueba el formato de los fragmentos de
código y aplica un probador de teoremas para detectar fragmentos de código ilegal -código
que falsea punteros, viola derechos de acceso sobre objetos o intenta cambiar el tipo o
clase 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 y
correctos
Tutorial de Java 21
· No ha ocurrido ninguna conversión ilegal de datos, tal como convertir enteros
en 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. Esto
limita cualquier aplicación del tipo Caballo de Troya, ya que las clases se buscan primero
entre las locales y luego entre las procedentes del exterior.
Dada, pues la concepción del lenguaje y si todos los elementos se mantienen dentro del
estándar marcado por Sun, no hay peligro. Java imposibilita, también, abrir ningún fichero
de 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 ninguna
aplicació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ún
más restrictivos. Bajo estas condiciones (y dentro de la filosofía de que el único ordenador
seguro es el que está apagado, desenchufado, dentro de una cámara acorazada en un
bunker 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.
22 Tutorial de Java
Es PORTABLE:
Más allá de la portabilidad básica por ser de arquitectura independiente, Java implementa
otros 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 interfaces
de usuario a través de un sistema abstracto de ventanas de forma que las ventanas puedan
ser 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 los
desarrolladores con Java pasarán más tiempo desarrollando y menos esperando por el
ordenador. No obstante, el compilador actual del JDK es bastante lento. Por ahora, que
todavía no hay compiladores específicos de Java para las diversas plataformas, Java es
más lento que otros lenguajes de programación, como C++, ya que debe ser interpretado y
no 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 Java
proyectos 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 ingente
cantidad de folletos electrónicos que supuran fanatismo en favor y en contra de los distintos
lenguajes contendientes con Java. Lo que se suele dejar de lado en todo esto, es que
primero habría que decidir hasta que punto Java, un lenguaje en pleno desarrollo y todavía
sin definición definitiva, está maduro como lenguaje de programación para ser comparado
con 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 operativo
y 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 editor
se compila generando el byte-code. Este código intermedio es de muy bajo nivel, pero sin
alcanzar las instrucciones máquina propias de cada plataforma y no tiene nada que ver con
el 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 ello
hace falta el run-time, que sí es completamente dependiente de la máquina y del sistema
operativo, que interpreta dinámicamente el byte-code y añade el 20% de instrucciones que
faltaban 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:
Tutorial de Java 23
portamiento en tiempo real. Aunque el comportamiento en tiempo real está limitado a las
capacidades del sistema operativo subyacente (Unix, Windows, etc.), aún supera a los
entornos de flujo único de programa (single-threaded) tanto en facilidad de desarrollo como
en rendimiento.
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. Las
librería nuevas o actualizadas no paralizarán las aplicaciones actuales (siempre que man-
tengan el API anterior).
24 Tutorial de Java
Java, para evitar que los módulos de byte-codes o los objetos o nuevas clases, haya que
estar 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.
HotJava
HotJava, 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ándar
y URLs estándares, aunque no soporta completamente el estándar HTML 3.0. La ventaja
sobre el resto de navegadores, sin soporte Java, es que puede ejecutar programas Java
sobre la red. La diferencia con Netscape, es que tiene implementado completamente los
sistemas de seguridad que propone Java, esto significa que puede escribir y leer en el disco
local, aunque esto hace disminuir la seguridad, ya que se pueden grabar en nuestro disco
programas 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.
Durante años, las grandes empresas se han convencido de que la «red» corporativa es la
arteria por donde fluye la sangre que mantiene vivo su negocio. Desde el gran servidor de
sus oficinas centrales, hasta los servidores de las delegaciones, las estaciones de trabajo
de 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.
¿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 aplicaciones
corporativas, ¿sería una buena tecnología allí donde la red es el punto crítico? Vamos a
intentar responder comparando las capacidades de Java contra la lista de necesidades de
la red corporativa.
Tutorial de Java 25
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 lugar
mejor 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, la
tecnologí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), se
podría conectar con otras partes para proporcionar diferentes utilidades al usuario.
El lenguaje C++ es una buena herramienta, pero no cumple totalmente la premisa. Visual
Basic y NextStep, se acercan cada vez más al poder de los objetos. Java facilita la creación
de entornos de desarrollo-aplicaciones de modo similar, pero además es flexible, poderoso
y efectivo. Los programadores ahora disponen de herramientas de programación de calidad
beta, que apuntan hacia esa meta, como son el Java WorkShop de SunSoft, el entorno Java
de Borland, el Café de Symantec, y pronto, herramientas más sofisticadas como Netcode o
FutureTense. Esto proporciona una gran progresión a los entornos de desarrollo Java.
Muchas de las implementaciones de Java actuales son puros intérpretes. Los byte-codes
son interpretados por el sistema run-time de Java, la Máquina Virtual Java (JVM), sobre el
ordenador 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, las
secciones (clases) del byte-code de la aplicación se compilarán hacia la arquitectura nativa
del 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 de
un 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, como
una 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 es
inherentemente multithreaded, a la vez que ofrece posibilidades de multithread como la
26 Tutorial de Java
clase Thread y mecanismos muy sencillos de usar de sincronización; Java en sí utiliza
threads. 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 la
aplicación. Más específicamente, este thread podría detectar qué métodos de una clase se
están usando con más frecuencia e invocar a sucesivos niveles de optimización en tiempo
de 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).
Con un entorno run-time de Java portado a cada una de las arquitecturas de las plataformas
presentes 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 posibilidad
hará tender a los programadores hacia Java, justo donde otros intentos anteriores con
entornos universales (como Galaxy o XVT) han fracasado. Estos APIs eran simplemente
inadecuados, no orientados a redes y, verdaderamente, pesados.
Una vez que los programas estén escritos en Java, otro lado interesante del asunto es que
los programadores también son portables. El grupo de programadores de la empresa puede
ahora enfrentarse a un desarrollo para cualquiera de las plataformas. La parte del cliente y
del 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 el
grupo de software de la empresa.
Costes de desarrollo
En contraste con el alto coste de los desarrollos realizados sobre estaciones de trabajo, el
coste 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
Tutorial de Java 27
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ía
de 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 niveles
confortables de precio de las herramientas de PCs. Y con el crecimiento cada día mayor de
la 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.
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ódigo
fuente Java (como comenzar un comentario con /** y terminarlo con */), javadoc se puede
fácilmente generar páginas HTML con el contenido de esos comentarios, que pueden
visualizarse en cualquier navegador. La documentación del API de Java ha sido creada de
este modo. Esto hace que el trabajo de documentar el código de nuevas clases Java sea
trivial.
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 de
programador a programador, quitando la información que no es esencial, siempre que se
puede. Esto hace que muchos de los makefiles de las aplicaciones contengan docenas de
librerías, una miríada de ficheros de cabecera y ultra-confusos macros. Es como mirar en el
estó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 un
solo fichero. Java tiene la inteligencia de make en el propio lenguaje para simplificar la
compilació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]
28 Tutorial de Java
[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. Ahora
vamos a forzarlo a que recompile pepe.java sin cambiar guitarra.java, podremos comprobar
que 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) y
compilamos pepe.java, el compilador Java se dará cuenta de que debe recompilar tanto
pepe.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 compilador
de Java, si tienes oportunidad, no dejes de leerlo.
Aprendizaje
Si la empresa está llena de programadores de C++ con alguna experiencia en el manejo de
librería gráficas, aprenderán rápidamente lo esencial de Java. Si el equipo de ingenieros no
conoce C++, pero maneja cualquier otro lenguaje de programación orientada a objetos, les
llevará pocas semanas dominar la base de Java. Lo que sí que no es cierto es que haya que
aprender C++ antes de aprender Java.
Portar o crear un servidor no-crítico en Java, de forma que tanto cliente como servi-
dor estén escritos en 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 y
clientes. Java tiene todas las cartas para ser una herramienta de inestimable valor en el
desarrollo de aplicaciones corporativas.
30 Tutorial de Java
Capítulo 2 Instalación del JDK
Actualmente ya hay entornos de desarrollo integrados completos para Java, diferentes del
JDK 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 esto
proporciona, Symantec Café. Borland también está trabajando en ello y la nueva versión de
su entorno de desarrollo soporta Java. Sun ha lanzado la versión comercial de su propio
entorno de desarrollo para Java, el Java Workshop, enteramente escrito en Java. Y Microsoft
ha puesto en el mercado Visual J++, que sigue el estilo de todas sus herramientas de desa-
rrollo.
El entorno habitual pues, consiste en un navegador que pueda ejecutar applets, un compilador
que 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 se
necesita un editor para escribir el código fuente, y no son estrictamente necesarias otras
herramientas como el debugger, un entorno visual, la documentación o un visualizador de
jerarquía de clases. Tan es así, que disponiendo del navegador Netscape 2.0 no se necesita
ni tan siquiera el JDK (a petición de varios amigos que disfrutan del uso de Linux pero no
disponen de soporte ELF para poder utilizar el JDK portado por Randy Chapman, les indica-
ré como conseguir utilizar el compilador embebido en Netscape).
Tutorial de Java 31
Windows
La versión del JDK para Windows es un archivo autoextraible. Se necesitan alrededor de 6
Mb de espacio libre en disco. Ejecutar el fichero, que desempaquetará el contenido del
archivo. El directorio donde se instale no es importante, pero supondremos que se instala en
el raiz del disco C:, en cuyo caso los archivos colgarán de c:\java. Es necesario añadir c:\java\bin
a la variable de entorno PATH.
Además de los ficheros java, el JDK incluye dos librerías dinámicas, MSVCRT20.DLL y
MFC30.DLL, que se instalarán en el directorio de Java. Si tienes ninguna copia de estos
ficheros en tu ordenador (probablemente en el directorio system de Windows) copia estos
ficheros en el directorio c:\java\bin. Si estos ficheros ya están en tu ordenador, elimina las
copias extra que instala el JDK.
Solaris
La versión del JDK para Solaris es un fichero tar comprimido. Se necesitan alrededor de 9
Mb de disco para descomprimir el JDK, aunque el doble de espacio sería una cifra más
có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 los
ficheros. 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 el
path completo donde se haga no tiene relevancia (también es posible colocarlo en /opt que
es 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).
Linux
Necesitas un kernel que soporte binarios ELF, por lo tanto tu Linux debe ser la versión
1.2.13 o superior, las anteriores tienen un bug que hacen que javac no funcione. Necesitas
tambié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:
32 Tutorial de Java
· Bajarse el JDK, · linux.jdk-1.0-try4.static-motif.tar.gz y · l i n u x . j d k - 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_HOME
y PRG, para que queden como:
J_HOME=/usr/local/java
PRG=/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 en
el 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 tener
problemas, es el momento de recurrir a las FAQ.
Siguiendo los pasos indicados ya se puede ejecutar el ejemplo del Tic-Tac-Toe que propone
la hoja de instalación que Sun ha incluido en todas sus versiones y que en Linux consistiría
en 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á falseado
por lo que es posible ganarle.
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 que
hacer sería lo siguiente.
Tutorial de Java 33
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), el
fichero java/lib/classes.zip y guardarlo en el mismo sitio que el fichero moz2_0.zip; esta
localización no es necesaria, pero simplifica la estructura.
Tercero. Fijar la variable de entorno CLASSPATH para que Netscape pueda encontrar sus
propias clases además de las clases del Java de Sun. Asegurarse de incluir el «directorio
actual», para poder compilar a la vez que se usan los ficheros .zip de Netscape y Sun. Por
ejemplo:
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á el
código fuente Java en byte-code, generándose el archivo [fichero].class.
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.zip
netscape -java sun.tools.javac.Main $1
y lo almacenaríamos como javac. Se ha de hacer el script ejecutable y cambiar /bin/csh por
el 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.
34 Tutorial de Java
Capítulo 3 Conceptos básicos de Java
Ahora que ya hemos visto a grandes rasgos lo que Java puede ofrecernos, y antes de entrar
a 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 mayor
parte de las construcciones. Esperemos que este capítulo no resulte demasiado intenso, no
obstante, 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 por
los que aquí pasaremos de puntillas e iremos presentando ejemplos de código de cada uno
de esos aspectos de la programación en Java.
Programación en Java
Cuando se programa en Java, se coloca todo el código en métodos, de la misma forma que
se 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 utilizan
del mismo modo. Los comentarios de documentación, colocados inmediatamente antes de
una declaración (de variable o función), indican que ese comentario ha de ser colocado en
la documentación que se genera automáticamente cuando se utiliza la herramienta de Java,
javadoc. Dichos comentarios sirven como descripción del elemento declarado permitiendo
generar una documentación de nuestras clases escrita al mismo tiempo que se genera el
código.
Identificadores
Los identificadores nombran variables, funciones, clases y objetos; cualquier cosa que el
programador 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 de
las minúsculas y no hay longitud máxima.
Literales
Un valor constante en Java se crea utilizando una representación literal de él. Java utiliza
cinco 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
36 Tutorial de Java
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 una
funció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:
Tutorial de Java 37
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 a
un 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 la
siguiente 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 se
encargará el garbage collector de devolver al sistema la memoria ocupada por el objeto
eliminado.
38 Tutorial de Java
Separadores
Sólo hay un par de secuencias con otros caracteres que pueden aparecer en el código Java;
son los separadores simples, que van a definir la forma y función del código. Los separadores
admitidos en Java son:
Control de Flujo
Muchas 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:
Tutorial de Java 39
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-
40 Tutorial de Java
rre un error en un programa, el código que encuentra el error lanza una excepción, que se
puede capturar y recuperarse de ella. Java proporciona muchas excepciones predefinidas.
Clases
Las clases son lo más simple de Java. Todo en Java forma parte de una clase, es una clase
o describe como funciona una clase. El conocimiento de las clases es fundamental para
poder entender los programas Java.
Todas las acciones de los programas Java se colocan dentro del bloque de una clase o un
objeto. 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 pueden
definir métodos fuera del bloque de la clase, pero esta posibilidad es más un intento de no
separarse 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
Tutorial de Java 41
uso de ellos. En C la unidad fundamental son los ficheros con código fuente, en Java son las
clases. De hecho son pocas las sentencias que se pueden colocar fuera del bloque de una
clase. La palabra clave import (equivalente al #include) puede colocarse al principio de un
fichero, fuera del bloque de la clase. Sin embargo, el compilador reemplazará esa sentencia
con el contenido del fichero que se indique, que consistirá, como es de suponer, en más
clases.
Tipos de Clases
Hasta ahora sólo se ha utilizado la palabra clave public para calificar el nombre de las clases
que hemos visto, pero hay tres modificadores más. Los tipos de clases que podemos definir
son:
abstract
Una clase abstract tiene al menos un método abstracto. Una clase abstracta no se
instancia, 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 se
puede 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 por
herencia. Son accesibles dentro del mismo paquete en el que se han declarado. Para
acceder desde otros paquetes, primero tienen que ser importadas.
synchronizable
Este modificador especifica que todos los métodos definidos en la clase son
sincronizados, es decir, que no se puede acceder al mismo tiempo a ellos desde
distintos threads; el sistema se encarga de colocar los flags necesarios para evitarlo.
Este mecanismo hace que desde threads diferentes se puedan modificar las mismas
variables sin que haya problemas de que se sobreescriban.
42 Tutorial de Java
}
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 de
la clase y Suma_a_i( int j ).
El siguiente ejemplo intenta declarar dos variables separadas con el mismo nombre. En C y
C++ son distintas, porque están declaradas dentro de ámbitos diferentes. En Java, esto es
ilegal.
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 la
clase.
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 objeto
de dicha clase.
Utilizando el código de ejemplo anterior, cuando se crea una nueva instancia de MiClase, se
crean (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 instanciada
con new no consume memoria, simplemente es una declaración de tipo. Después de ser
instanciado un nuevo objeto mc, el valor de i en el objeto mc será igual a 10. Se puede
referenciar 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
Tutorial de Java 43
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 recoger
automáticamente todos los objetos que se salen del alcance. No obstante proporciona un
método que, cuando se especifique en el código de la clase, el reciclador de memoria (garbage
collector) llamará:
// Cierra el canal cuando este objeto es reciclado
protected void finalize() {
close();
}
44 Tutorial de Java
Herencia
La Herencia es el mecanismo por el que se crean nuevos objetos definidos en términos de
objetos 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 variables
de instancia. Se dice que Pato deriva o hereda de Ave.
Además, se pueden sustituir los métodos proporcionados por la clase base. Utilizando nuestro
anterior ejemplo de MiClase, aquí hay un ejemplo de una clase derivada sustituyendo a la
funció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 a
10, 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 y
de la clase animal no se puede derivar nada, sería como obtener el objeto toro mecánico a
partir de una máquina motorizada (aparato con motor) y un toro (aminal). En realidad, lo que
se pretende es copiar los métodos, es decir, pasar la funcionalidad del toro de verdad al toro
mecánico, con lo cual no sería necesaria la herencia múltiple sino simplemente la compartición
de funcionalidad que se encuentra implementada en Java a través de interfaces.
Control de acceso
Cuando se crea una nueva clase en Java, se puede especificar el nivel de acceso que se
quiere 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 de
instacia públicos.
protected
Tutorial de Java 45
protected void SoloSubClases(){}
Sólo las subclases de la clase y nadie más puede acceder a las variables y métodos
de instancia protegidos.
private
private String NumeroDelCarnetDeIdentidad;
Las variables y métodos de instancia privados sólo pueden ser accedidos desde
dentro de la clase. No son accesibles desde las subclases.
Los métodos protegidos (protected) pueden ser vistos por las clases derivadas, como en
C++, y también, en Java, por los paquetes (packages). Todas las clases de un paquete
pueden ver los métodos protegidos de ese paquete. Para evitarlo, se deben declarar como
private 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.
De la misma forma se puede declarar un método como estático, lo que evita que el método
pueda 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
}
46 Tutorial de Java
}
La modificación de la variable numero_de_capitulos no funciona porque se está violando una
de las reglas de acceso al intentar acceder desde un método estático a una variable no
estática.
Todas las clases que se derivan, cuando se declaran estáticas, comparten la misma página
de 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ícitamente
el 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 se
creasen de UnaClase.
this Y super
Al acceder a variables de instancia de una clase, la palabra clave this hace referencia a los
miembros 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;
Tutorial de Java 47
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á a
15 y finalmente el método Suma_a_i() de la clase padre (MiClase) lo dejará en 25:
MiNuevaClase mnc;
mnc = new MiNuevaClase();
Clases abstractas
Una 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 implementar
métodos. Esto es muy útil cuando la implementación es específica para cada usuario, pero
todos los usuarios tienen que utilizar los mismos métodos. Un ejemplo de clase abstracta en
Java 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 new
de una clase abstracta.
Una clase abstracta en Java es lo mismo que en C++ virtual func() = 0; lo que obliga a que
al derivar de la clase haya que implementar forzosamente los métodos de esa clase abstrac-
ta.
48 Tutorial de Java
Interfaces
Los métodos abstractos son útiles cuando se quiere que cada implementación de la clase
parezca 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. Los
métodos de una clase son public, static y final.
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 y
proporcionarán el código necesario para implementar los métodos que se han definido para
el 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 del
có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 implementada
por 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 que
implementen el interface.
class MiOtraClase implements VideoClip {
void play() {
<código nuevo>
}
void bucle() {
<código nuevo>
Tutorial de Java 49
}
void stop() {
<código nuevo>
}
Métodos nativos
Java proporciona un mecanismo para la llamada a funciones C y C++ desde nuestro código
fuente 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 de
la 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 la
descompilación completa del código Java.
Paquetes
La palabra clave package permite agrupar clases e interfaces. Los nombres de los paquetes
son palabras separadas por puntos y se almacenan en directorios que coinciden con esos
nombres.
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;
50 Tutorial de Java
Y las clases que se obtienen de la compilación de los ficheros anteriores, se encuentran con
el 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 del
paquete como ruta y nombre de clase (es lo mismo que #include de C/C++). Se pueden
cargar 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 defecto
sin nombre. Es decir, en el mismo directorio que el fichero fuente, y la clase puede ser
cargada con la sentencia import:
import MiClase;
Paquetes de Java
El lenguaje Java proporciona una serie de paquetes que incluyen ventanas, utilidades, un
sistema de entrada/salida general, herramientas y comunicaciones. En la versión actual del
JDK, los paquetes Java que se incluyen son:
java.applet
Este paquete contiene clases diseñadas para usar con applets. Hay una clase Applet
y tres interfaces: AppletContext, AppletStub y AudioClip.
java.awt
El paquete Abstract Windowing Toolkit (awt) contiene clases para generar widgets y
componentes GUI (Interfaz Gráfico de Usuario). Incluye las clases Button, Checkbox,
Choice, Component, Graphics, Menu, Panel, TextArea y TextField.
java.io
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, incluye
las clases Socket, URL y URLConnection.
Tutorial de Java 51
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).
Referencias
Java se asemeja mucho a C y C++. Esta similitud, evidentemente intencionada, es la mejor
herramienta para los programadores, ya que facilita en gran manera su transición a Java.
Desafortunadamente, tantas similitudes hacen que no nos paremos en algunas diferencias
que son vitales. La terminología utilizada en estos lenguajes, a veces es la misma, pero hay
grandes diferencias subyacentes en su significado.
C tiene tipos de datos básicos y punteros. C++ modifica un poco este panorama y le añade
los tipos referencia. Java también especifica sus tipos primitivos, elimina cualquier tipo de
punteros y tiene tipos referencia mucho más claros.
Todo este maremágnum de terminología provoca cierta consternación, así que vamos a
intentar 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 poco
su 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 booleano
en sentencias condicionales.
Punteros
C y C++ permiten la declaración y uso de punteros, que pueden ser utilizados en cualquier
lugar. Esta tremenda flexibilidad resulta muy útil, pero también es la causa de que podamos
colgar todo el sistema.
52 Tutorial de Java
Referencias en C++
Las referencias se incorporaron a C++ en un intento de manejar punteros de C de forma más
limpia y segura. Sin embargo, como no elimina los punteros, la verdad es que su propósito
lo consigue a medias. Es más, podríamos decir que con las referencias C++, el lenguaje se
vuelve 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ón
basada en punteros, pero es más por las deficiencias de los punteros que por las ventajas
de las referencias.
Referencias en Java
Las referencias en Java no son punteros ni referencias como en C++. Este hecho crea un
poco 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 referencia
dirige 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.
public Habitacion() {
habitacion( 0 );
}
Tutorial de Java 53
}
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ódigo
anterior indica al gerente del Hotel que ya dispone de una nueva habitación. La parte C
llama 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 de
identidad del hotel. El código electrónico que nos permitirá acceder a la habitación se
genera en la parte D, una vez conocido el interior de la habitación y se programa en la
llave en la parte A.
Si dejamos el ejemplo real a un lado y nos vamos a lo que ocurre en la ejecución del
código, vemos que el operador new busca espacio para una instancia de un objeto de
una clase determinada e inicializa la memoria a los valores adecuados. Luego invoca al
método constructor de la clase, proporcionándole los argumentos adecuados. El operador
new devuelve una referencia a sí mismo, que es inmediatamente asignada a la variable
referencia.
Una llave puede ser programada para que funcione solamente con una habitación en
cualquier momento, pero podemos cambiar su código electrónico para que funcione con
alguna otra habitación; por ejemplo, para cambiar una habitación anteriormente utilizada
por un empedernido fumador por otra limpia de olores y con vistas al mar. Cambiemos
pues la llave duplicada de la habitación del fumador (la 222) por la habitación con olor a
sal 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 llave
maestra, 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ñeros
que tengan llave de esa misma habitación, no tendrán conocimiento de esos cambios
hasta 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 arrays
Como en C y C++, Java dispone de arrays de tipos primitivos o de clases. Los arrays en C y
C++ 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;
Tutorial de Java 55
}
}
Cada paso en el ejemplo es semejante al que ya vimos antes. El paso 1 especifica que el
juego 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, el
gerente 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 este
caso todas las llaves pasan a formar parte del juego maestro. Los números de piso se dan
en 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 listas
Hay gente que piensa que como Java no dispone de punteros, resulta demasiado complejo
construir 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 doblemente
enlazada. El paquete de la lista simple se compone de dos clases. Cada elemento de la lista
es 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 que
se use para proporcionar acceso a los datos que el usuario proporcione.
Revisemos pues el código de nuestro Hotel, ahora Hotel3.java, que será prácticamente el
56 Tutorial de Java
mismo que en el caso de los arrays:
public class Hotel3 {
// Número de habitaciones por ala
public static final int habPorAla = 12;
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íamos
acelerar el tiempo de compilación metiendo la lista genérica ListaEnlazada dentro de una
ListaEnlazadaHabitacion.
Los pasos 2 a 5 son equivalentes a los del primer ejemplo. Construimos e inicializamos una
nueva 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 con
diferente sintaxis. En Java, los arrays y el operador [] son internos del lenguaje. Como Java
no soporta la sobrecarga de operadores por parte del usuario, tenemos que usarlo siempre
en su forma normal.
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, el
sistema 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 de
ejecución por el compilador, para asegurarse de que se trata de una Habitacion). El paso 12
usa la llave para imprimir la información.
Tutorial de Java 57
Los punteros en C y C++ están orientados hacia un modelo físico de funcionamiento. Es
decir, que el modelo de punteros se mapea directamente sobre el modelo hardware. Este
modelo 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 la
distribució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, cosa
que no puede suceder con las referencias en Java. Cualquier intento de hacer esto sería
abortado 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 de
generar un error del sistema cuando un puntero accede a una posición no válida. Por el
contrario, con el uso de las referencias, Java nos protege contra nuestras propias tenden-
cias autodestructivas.
58 Tutorial de Java
Capítulo 4 Programas Básicos en Java
Como cualquier otro lenguaje, Java se usa para crear aplicaciones. Pero, también Java
tiene 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 a
ver en detalle lo mínimo que podemos hacer en ambos casos.
HolaMundo
Vamos ver en detalle la aplicación anterior, línea a línea. Esas líneas de código contienen
los 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 un
comentario orientado a línea.
class HolaMundoApp {
Esta línea declara la clase HolaMundoApp. El nombre de la clase especificado en el fichero
fuente se utiliza para crear un fichero nombredeclase.class en el directorio en el que se
compila la aplicación. En nuestro caso, el compilador creará un fichero llamado
HolaMundoApp.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
Tutorial de Java 59
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 clase
HolaMundoApp y no a ninguna instancia de la clase. De esta forma, si alguien intenta
hacer 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 estricta
comprobació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 el
nombre 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 y
mé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 salida
está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.
60 Tutorial de Java
Compilación
El compilador javac se encuentra en el directorio bin por debajo del directorio java, donde se
haya 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 que
revisar 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 en
los 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 fichero
fuente ni al compilador, no debería aparecer mensaje alguno en la pantalla, y cuando vuelva
a aparecer el prompt del sistema, se debería ver un fichero HolaMundoApp.class nuevo en
el directorio donde se encuentra el fichero fuente.
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 con
la 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 se
indiquen 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 la
hora de compilar un fichero con código fuente Java, nos basaremos en errores provocados
sobre nuestra mínima aplicación Java de la sección anterior, pero podría generalizarse sin
demasiados problemas.
%javac: Command not found
No se ha establecido correctamente la variable PATH del sistema para el compilador
javac. El compilador javac se encuentra en el directorio bin, que cuelga del directorio
java, que cuelga del directorio donde se haya instalado el JDK (Java Development
Tutorial de Java 61
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étodo
main de la aplicación.
%Can´t find class HolaMundoApp
Este es un error muy sutil. Generalmente significa que el nombre de la clase es
distinto al del fichero que contiene el código fuente, con lo cual el fichero
nombre_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.java
colocamos 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úscula
y mayúscula.
Applet
La definición más extendida de applet, muy bien resumida por Patrick Naughton, indica que
un applet es «una pequeña aplicación accesible en un servidor Internet, que se transporta
por la red, se instala automáticamente y se ejecuta in situ como parte de un documento
web». Claro que así la definición establece el entorno (Internet, Web, etc.). En realidad, un
applet 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) basada
en un formato gráfico sin representación independiente: es decir, se trata de un elemento a
embeber 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, que
cansada de empezar siempre a codificar desde cero, diseña un formulario con los datos
bá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. El
hecho de que se integre estática (embebido en un ejecutable) o dinámicamente (intérpretes,
62 Tutorial de Java
DLLs, etc.) no afecta en absoluto a la esencia de su comportamiento como componente con
que construir diálogos con sentido autónomo.
Pues bien, así es un applet. Lo que ocurre es que, dado que no existe una base adecuada
para soportar aplicaciones industriales Java en las que insertar nuestras miniaplicaciones
(aunque todo se andará), los applets se han construido mayoritariamente, y con gran acierto
comercial (parece), como pequeñas aplicaciones interactivas, con movimiento, luces y soni-
do... en Internet.
Arquitectura de Appletviewer
El appletviewer representa el mínimo interfaz de navegación. En la figura se muestran los
pasos que seguiría appletviewer para presentarnos el resultado de la ejecución del código
de nuestra clase.
Tutorial de Java 63
Esta es una visión simplificada del appletviewer. La función principal de esta aplicación es
proporcionar al usuario un objeto de tipo Graphics sobre el que dibujar, y varias funciones
para facilitar el uso del objeto Graphics.
Métodos de Appletviewer
Vamos a utilizar como excusa la función asociada al appletviewer de los siguientes métodos
para 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 este
método se debe fijar el tamaño del applet, aunque en el caso de Netscape el tamaño
que 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 necesarios
para la ejecución del applet. Y, por supuesto, la asignación de valores a las variables
globales a la clase que se utilicen. En el caso de los applet, este método únicamente
es llamado por el sistema al cargar el applet.
start()
start() es la llamada para arrancar el applet cada vez que es visitado. La clase Applet
no 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 cada
vez 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 puede
ser apreciado (aunque el programador puede variar este comportamiento y hacer
que 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á al
método init(), pero no a start(), que sí será llamado cuando restauremos el navegador
a un tamaño que permita ver el applet. Naturalmente, start() se puede ejecutar varias
64 Tutorial de Java
veces: la primera tras init() y las siguientes (porque init() se ejecuta solamente una
vez) tras haber aplicado el método stop().
stop()
stop() es la llamada para detener la ejecución del applet. Se llama cuando el applet
desaparece de la pantalla. La clase Applet tampoco hace nada en este método, que
debería ser sobrecargado por las clases derivadas para detener la animación, el
sonido, etc. Esta función es llamada cuando el navegador no incluye en su campo de
visión al applet; por ejemplo, cuando abandona la página en que está insertado, de
forma que el programador puede paralizar los threads que no resulten necesarios
respecto de un applet no visible, y luego recuperar su actividad mediante el método
start().
destroy()
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 clase
derivada, obviamente, la que debería sobrecargar este método para representar algo
inteligente 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 un
redimensionamiento 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 ser
pintada; 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 )
Tutorial de Java 65
su color de fondo por defecto. Pudiera parecer así que se trata de un método de
efecto 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 lo
comú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 pintado
en una sobrecarga del método update() y escribir un método paint() que sólo llame a
update(). 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étodo
update(). Estas son las mismas razones que aconsejan usar el método resize() inser-
to en init(), para evitar el mismo desagradable efecto.
repaint
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 html
conteniendo el tag (marca) <APPLET>, o bien un URL hacia un fichero HTML que contenga
esa marca.
La única opción válida que admite la llamada a appletviewer es -debug, que arranca el applet
en el depurador de Java, jdb. Para poder ver el código fuente en el depurador, se tiene que
compilar 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ásico
que crearemos en la sección siguiente y que lanzaremos desde un fichero html del mismo
nombre 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 en
pantalla la ventana siguiente:
66 Tutorial de Java
Funciones de menú de Appletviewer
El 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 puede
utilizar 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 applet
actual. A continuación carga una nueva copia del applet y la arranca llamando al
método start().
· Clone
La función Clone crea una copia del applet actual en una ventana de appletviewer
nueva. En realidad es un appletviewer idéntico con el mismo URL.
· Tag
Tutorial de Java 67
La función Tag muestra en una ventana hija del appletviewer el código html cargado
para su ejecución. Es similar a la función View Source que figura en la mayoría de los
navegadores, Netscape, Internet Explorer y HotJava incluidos.
· Info
La función Info lee los comentarios de documentación contenidos en el fichero html y
muestra 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 la
función Properties (propiedades de la red) permite cambiar o establecer el modo de
seguridad 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 appletviewer
que se encuentren lanzadas, concluyendo la ejecución de todas ellas y terminando
entonces el appletviewer.
HolaMundo
A continuación está el código fuente del applet HolaMundo, que es la versión applet de la
mínima aplicación Java que antes habíamos escrito. Guardar este código en un fichero
fuente Java como HolaMundo.java.
//
// Applet HolaMundo de ejemplo
//
import java.awt.Graphics;
import java.applet.Applet;
68 Tutorial de Java
Componentes básicos de un Applet
El lenguaje Java implementa un modelo de Programación Orientada a Objetos. Los objetos
sirven de bloques centrales de construcción de los programas Java. De la misma forma que
otros lenguajes de programación, Java tiene variables de estado y métodos.
/*
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, y
só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 clase
desde un paquete de Java hace que esa clase importada esté disponible para todo el código
incluido en el fichero fuente Java que la importa. Por ejemplo, en el applet HolaMundo se
importa la clase java.awt.Graphics, y podremos llamar a los métodos de esta clase desde
cualquier método de nuestro programa que se encuentre en el fichero HolaMundo.java. Esta
clase 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 la
clase Graphics para generar su salida.
La clase Applet
Se puede crear una nueva clase, en este caso HolaMundo, extendiendo la clase básica de
Java: Applet. De esta forma, se hereda todo lo necesario para crear un applet. Modificando
determinados métodos del applet, podemos lograr que lleve a cabo las funciones que de-
seamos.
import java.applet.Applet;
. . .
public class HolaMundo extends Applet {
Tutorial de Java 69
Métodos de Applet
La parte del applet a modificar es el método paint(). En la clase Applet, se llama al método
paint() cada vez que el método arranca o necesita ser refrescado, pero no hace nada. En
nuestro 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ío
de la clase Applet. Luego, aquí se ejecuta el método drawString(), que le dice al applet
cómo debe aparecer un texto en el área de dibujo.
Compilación de un applet
Ahora 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 compilador
Java, 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 applets
Java.
Llamada a Applets
¿Qué tienen de especial HotJava, Microsoft Explorer o Netscape con respecto a otros
navegadores? Con ellos se puede ver html básico y acceder a todo el texto, gráfico, sonido
e hipertexto que se pueda ver con cualquier otro navegador. Pero además, pueden ejecutar
applets, que no es html estándar. Ambos navegadores entienden código html que lleve la
marca <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. Cuando
se acceda a la página Web donde se encuentre incluida la marca, se ejecutará el byte-code
contenido en SuCodigo.class, obteniéndose el resultado de la ejecución del applet en la
ventana del navegador, con soporte Java, que estemos utilizando.
70 Tutorial de Java
Prueba de un Applet
71El JDK, Kit de Desarrollo de Java, incluye el visor de applets básico, appletviewer, que
puede utilizarse para la visualización rápida y prueba de nuestros applets, tal como se ha
visto 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 es
HolaMundo.html que generará la salida que se mostraba en la sección sobre el Ejemplo de
uso de appletviewer.
Atributos opcionales:
CODEBASE : URL base del applet
Los applets se incluyen en las páginas Web a través de la marca <APPLET>, que para
quien conozca html resultará muy similar a la marca <IMG>. Ambas necesitan la referencia
Tutorial de Java 71
a un fichero fuente que no forma parte de la página en que se encuentran embebidos. IMG
hace esto a través de SRC=parámetro y APPLET lo hace a través CODE=parámetro. El
parámetro de CODE indica al navegador dónde se encuentra el fichero con el código Java
compilado .class. Es una localización relativa al documento fuente.
Por razones que no entiendo muy bien, pero posiblemente relacionadas con los packages y
classpaths, si un applet reside en un directorio diferente del que contiene a la página en que
se encuentra embebido, entonces no se indica un URL a esta localización, sino que se
apunta al directorio del fichero .class utilizando el parámetro CODEBASE, aunque todavía
se 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 indican
en 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 la
pá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 escribir
en el disco duro de nuestro ordenador, pero en las características de seguridad tenemos
bloqueada 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 ha
definido la marca </APPLET>, que finaliza la descripción del applet. Un navegador con
soporte 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 texto
que se encuentre entre ellas.
Atributos de APPLET
Los atributos que acompañan a la etiqueta <APPLET>, algunos son obligatorios y otros son
opcionales. Todos los atributos, siguiendo la sintaxis de html, se especifican de la forma:
atributo=valor. Los atributos obligatorios son:
CODE
Indica la anchura inicial que el navegador debe reservar para el applet en pixels.
HEIGHT
72 Tutorial de Java
Indica la altura inicial en pixels. Un applet que disponga de una geometría fija no se
verá redimensionado por estos atributos. Por ello, si los atributos definen una zona
menor que la que el applet utiliza, únicamente se verá parte del mismo, como si se
visualiza 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 que
se indican a continuación:
CODEBASE
ALT
NAME
Otorga un nombre simbólico a esta instancia del applet en la página que puede ser
empleado por otros applets de la misma página para localizarlo. Así, un applet puede
ser cargado varias veces en la misma página tomando un nombre simbólico distinto
en cada momento.
ALIGN
VSPACE
Indica el espaciado vertical entre el applet y el texto, en pixels. Sólo funciona cuando
se ha indicado ALIGN = LEFT o RIGHT.
HSPACE
Tutorial de Java 73
Paso de parámetros a los applets
El espacio que queda entre las marcas de apertura y cierre de la definición de un applet, se
utiliza para el paso de parámetros al applet. Para ello se utiliza la marca PARAM en la
página HTML para indicar los parámetros y el método getParameter() de la clase
java.applet.Applet para leerlos en el código interno del applet. La construcción puede repe-
tirse cuantas veces se quiera, una tras otra.
NAME
VALUE
Texto HTML
Texto HTML que será interpretado por los navegadores que no entienden la marca
APPLET en sustitución del applet mismo.
Para mostar esta posibilidad vamos a modificar nuestro applet básico HolaMundo para que
pueda saludar a cualquiera. Lo que haremos será pasarle al applet el nombre de la persona
a quien queremos saludar. Generamos el código para ello y lo guardamos en el fichero
HolaTal.java
import java.awt.Graphics;
import java.applet.Applet;
El hecho de que las marcas <APPLET> y <PARAM> sean ignoradas por los navegadores
que no entienden Java, es inteligentemente aprovechado a la hora de definir un contenido
alternativo 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 ser
visualizado en cualquier navegador y en unos casos presente la información alternativa y en
otros, 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>
Tutorial de Java 75
Para recoger los parámetros pasados en este último caso, bastaría con hacer un pequeño
bucle 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 del
mismo 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 que
contiene 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ía
el que se muestra a continuación:
// Capturamos el parámetro
p = getParameter( «p» );
// Creamos el almacenamiento
cads = new String[ st.countTokens() ];
76 Tutorial de Java
En el caso de que utilicemos un fichero como verdadera entrada de parámetros al applet y el
fichero se encuentre en una dirección URL, utilizamos el método StreamTokenizer() para
obtener los tokens que contiene ese fichero:
// Creamos el objeto URL para acceder a él
url = new URL( «http://www.prueba.es/Fichero» );
El parámetro ARCHIVE
Una de las cosas que se achacan a Java es la rapidez. El factor principal en la percepción
que 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 esperando
má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.
Me imagino que ya el lector habrá pensado la solución al problema: poner todos los ficheros
en uno solo, con lo cual solamente sería necesaria una conexión para descargar todo el
có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 formato
de 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 de
ficheros especiales en extensiones separadas de la marca <APPLET>.
No vamos a contar la creación de ficheros CAB; quien esté interesado puede consultar la
documentación de Java que proporciona Microsoft con su SDK para Java, que es bastante
exhaustiva al respecto. Una vez que disponemos de este fichero, podemos añadir un
Tutorial de Java 77
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 los
ficheros .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.
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 de
Netscape 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, o
de 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, ponemos
las 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 nuevo
formato de ficheros, que incorporará en del JDK 1.1, para incluir juntos todos los ficheros de
imá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 del
parámetro ARCHIVES. Y dejamos al lector el trabajo de poner los tres formatos juntos bajo
el mismo paraguas de la marca <APPLET>.
Depuración general
Compilar y ejecutar el programa HolaMundo.java a través del fichero HolaMundo.html no
debería suponer ningún problema, pero alguna vez nos encontraremos frente a programas
78 Tutorial de Java
más difíciles y se necesitará el truco de depuración al que se recurre en el desarrollo de
programas 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.println
imprime la cadena que se le especifique en la ventana de texto en la que se invocó al
navegador. 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()» );
}
Lo primero que aparece son los mensajes «initializing... starting...», como resultado de la
carga del applet en el navegador. Una vez cargado, lo que sucede es:
Si se utiliza la opción del navegador de Reload, es decir, volver a cargar la página, el applet
es 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 de
sus trazas. Después de esto, el applet se descarga de la memoria y vuelve a cargarse
volviendo a comenzar su inicialización.
Protección de applets
Como curiosidad, más que como algo verdaderamente útil, podemos proteger nuestros applets
de forma muy sencilla, o por lo menos evitar que nadie pueda ocultar en sus páginas HTML
que nosotros somos los legales autores de un applet.
Tutorial de Java 79
El método es muy sencillo y se basa en la utilización de un parámetro del cual comprobamos
su 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ámetro
copyright 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 de
forma más elegante:
copyright = getParameter( «copyright» );
// System.out.println( copyright.hashCode() );
init()
Esta función miembro es llamada al crearse el applet. Es llamada sólo una vez. La clase
Applet no hace nada en init(). Las clases derivadas deben sobrecargar este método para
cambiar el tamaño durante su inicialización, y cualquier otra inicialización de los datos que
solamente 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 );
80 Tutorial de Java
valor_global1 = 0;
valor_global2 = 100;
destroy()
Esta función miembro es llamada cuando el applet no se va a usar más. La clase Applet no
hace nada en este método. Las clases derivadas deberían sobrecargarlo para hacer una
limpieza final. Los applet multithread deberán usar destroy() para «matar» cuanquier thread
del 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;
stop()
Llamada para detener el applet. Se llama cuando el applet desaparece de la pantalla. La
clase Applet no hace nada en este método. Las clases derivadas deberían sobrecargarlo
para detener la animación, el sonido, etc.
public void stop() {
estaDetenido = true;
Tutorial de Java 81
En el navegador Netscape, el tamaño del applet es el que se indica en la marca APPLET del
HTML, 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 del
APPLET. 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 del
APPLET. 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.
paint( Graphics g )
Se llama cada vez que se necesita refrescar el área de dibujo del applet. La clase Applet
simplemente dibuja una caja con sombreado de tres dimensiones en el área. Obviamente, la
clase derivada debería sobrecargar este método para representar algo inteligente en la
pantalla.
Para repintar toda la pantalla cuando llega un evento Paint, se pide el rectángulo sobre el
que se va a aplicar paint() y si es más pequeño que el tamaño real del applet se invoca a
repaint(), 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 clase
Applet simplemente limpia el área y llama al método paint(). Esta funcionalidad es suficiente
en la mayoría de los casos. De cualquier forma, las clases derivadas pueden sustituir esta
funcionalidad para sus propósitos.
82 Tutorial de Java
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ía
sobrecargarse, 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 pueden
pasar parámetros, se puede modificar el rectángulo a repintar.
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 que
contiene el applet, es decir, el lugar donde está la hoja en todo Internet o en el disco.
Tutorial de Java 83
getCodeBase()
Indica la ruta http, o el directorio del disco, de donde se ha cargado el código bytecode que
forma 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 la
impresora el mapa de bits del dibujo.
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 se
importa la clase Date, la aplicación se llama ahora FechaApp en vez de HolaMundoApp, se
crea un objeto Date y el mensaje de salida a pantalla es diferente. Almacenaremos esta
nueva 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 las
funciones y variables en Java, existen dentro de una clase o un objeto, Java no soporta
funciones 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 a
objetos como Java, es la plantilla que usamos para describir los datos y el entorno asociado
a las instancias de esa clase. Cuando se instancia una clase, se crea un objeto del tipo
definido por la clase y exactamente igual que cualquier otra instancia realizada de la misma
clase. Los datos asociados a la clase u objeto son las variables y el entorno asociado con la
clase u objeto son los métodos.
Un ejemplo de clase es la clase que representa un rectángulo. Esta clase contiene las
variables que indican las coordenadas del origen del rectángulo y su ancho y alto. La clase
84 Tutorial de Java
puede contener un método para calcular el área de ese rectángulo. Ahora podemos
instanciarlo para muy diferentes propósitos, es decir, podemos tener objetos rectángulo
específicos, así podremos obtener información de las dimensiones de nuestro dormitorio o
de 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 class
inicia la definición de la clase NombreDeLaClase. Las variables y métodos de la clase han
de ir colocados entre las llaves que delimitan el bloque de definición de la clase. FechaApp
no 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 una
aplicación Java utilizando el intérprete Java, se debe especificar el nombre de la clase que
se desea ejecutar. El intérprete entonces, invoca al método main() definido dentro de esa
clase, que debe controlar el flujo del programa, pedir al sistema los recursos que necesite y
ejecutar 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étodo
propio 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 un
programa escrito en C o C++, arranca llamando en primer lugar a la función main(), que
llamará 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 para
completar la ejecución de la aplicación. Si se intenta ejecutar una clase con el intérprete
Java que no contenga el método main(), el intérprete generará un mensaje de error.
La aplicación FechaApp es el programa más simple que podemos hacer que realice algo
Tutorial de Java 85
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 nos
proporciona el entorno de desarrollo de Java. La clase System proporciona un acceso al
sistema independiente del hardware sobre el que estemos ejecutando la aplicación y la
clase Date proporciona un acceso a las funciones de Fecha independientemente del siste-
ma en que estemos ejecutando la aplicación.
86 Tutorial de Java
Capítulo 5 El 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, en
principio, tiene escasa practicidad y es necesaria una verdadera emergencia para tener que
recurrir a él.
Trataremos por encima los comandos que proporciona el jdb, pero sin entrar en detalles de
su 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ón
de depuración, debug, y habremos puesto en marcha jdb.
Depurar HolaMundo
Hemos modificado nuestro applet de ejemplo para utilizarlo en nuestra sesión de ejemplo
con 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.java
sería el siguiente:
//
// Applet HolaMundo de ejemplo, para depurar
//
import java.awt.Graphics;
import java.applet.Applet;
Tutorial de Java 87
%appletviewer -debug hm.html
El fichero hm.html contiene las líneas mínimas para poder activar el applet, estas líneas son
las 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 la
pantalla a medida que vamos introduciendo comandos:
%appletviewer -debug hm.html
Loading jdb...
0xee301bf0:class(sun.applet.AppletViewer)
>
Comando help
El comando help proporciona una lista de los comandos que están disponibles en la sesión
de jdb. Esta lista es la que sigue, en donde hemos aprovechado la presencia de todos los
comandos para comentar la acción que cada uno de ellos lleva a cabo.
>help
** command list **
threads [threadgroup] — lista threads
thread <thread id> — establece el thread por defecto
suspend [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 thread
threadgroups — lista los grupos de threads
threadgroup <name> — establece el grupo de thread actual
88 Tutorial de Java
catch <class id> — parar por la excepción especificada
ignore <class id> — ignorar la excepción especificada
Comando threadgroups
El comando threadgroups permite ver la lista de threads que se están ejecutando. Los
grupos system y main deberían estar siempre corriendo.
>threadgroups
1.(java.lang.ThreadGroup)0xee300068 system
2.(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.
>threads
Group system:
1.(java.lang.Thread)0xee300098 clock handler cond
2.(java.lang.Thread)0xee300558 Idle thread run
3.(java.lang.Thread)0xee3005d0 sync Garbage Collector cond
4.(java.lang.Thread)0xee300620 Finalizer thread cond
5.(java.lang.Thread)0xee300a20 Debugger agent run
6.(java.tools.debug.BreakpointHandler)0xee300b58) Breakpoint handler cond
Group 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.
>run
run sun.applet.AppletViewer hm.html
running...
Tutorial de Java 89
main[1]threads
threads
Group sun.applet.AppletViewer.main:
1.(java.lang.Thread)0xee3000c0 AWT-Motif running
2.(sun.awt.ScreenUpdater)0xee302ed0 ScreenUpdater cond. Waiting
Group applet-hm.class:
3.(java.lang.Thread)0xee302f38 Thread-6 cond. Waiting
main[1]
El visor de applets de Sun aparecerá en la pantalla y mostrará el conocido mensaje de
saludo al Mundo. Ahora vamos a rearrancar el appletviewer con un punto de ruptura, para
detener la ejecución del applet, y podamos seguir mostrando los comandos disponibles en
el jdb.
main[1]exit
%appletviewer -debug hm.html
Loading jdb...
0xee3009c8:class(sun.applet.AppletViewer)
>stop in hm.paint
Breakpoint set in hm.paint
>run
run sun.applet.AppletViewer hm.html
running...
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 que
contienen el código Java de las clases que se están depurando. Por defecto, utilizará el
camino 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 que
hayamos fijado.
AWT-Motif[1]list
90 Tutorial de Java
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 g
g = (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 el
indicador si hemos utilizado el comando list.
AWT-Motif[1]step
Breakpoint hit: hm.paint(hm.java:11)
AWT-Motif[1]list
9 public void paint( Graphics g ) {
10 i = 10;
11 => g.drawString( «Hola Mundo!»,25,25 );
12 }
13 }
AWT-Motif[1]
Tutorial de Java 91
92 Tutorial de Java
Capítulo 6 Clases Java
En cualquier lenguaje orientado a objetos, las clases definen cualquier objeto que se pueda
manipular. Java tiene muchas clases útiles, no solamente aquellas que se utilizan para
gráficos y sonido, usadas en la construcción de applets mucho más complejos.
La clase Math
La clase Math representa la librería matemática de Java. Las funciones que contiene son las
de todos los lenguajes, parece que se han metido en una clase solamente a propósito de
agrupación, por eso se encapsulan en Math, y lo mismo sucede con las demás clases que
corresponden 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 haya
que inicializarla.
Funciones matemáticas
Si se importa la clase, se tiene acceso al conjunto de funciones matemáticas estándar:
Tutorial de Java 93
Math.random() devuelve un double
Math.max( a,b ) para int, long, float y double
Math.min( a,b ) para int, long, float y double
Math.E para la base exponencial
Math.PI para PI
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 Character
Al 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 pueden
crear 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 al
objeto, por eso hay que crealo antes.
94 Tutorial de Java
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 );
La clase Float
Cada tipo numérico tiene su propia clase de objetos. Así el tipo float tiene el objeto Float. De
la misma forma que con la clase Character, se han codificado muchas funciones útiles
dentro 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» );
Tutorial de Java 95
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 raiz
cuadrada 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 Double
Cada 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 útiles
dentro 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
96 Tutorial de Java
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 Integer
Cada tipo numérico tiene su propia clase de objetos. Así el tipo int tiene el objeto Integer. De
la misma forma que con la clase Character, se han codificado muchas funciones útiles
dentro 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();
Tutorial de Java 97
boolean I.equals( Object obj );
En los métodos toString(), parseInt() y valueOf() que no se especifica la base sobre la que
se trabaja, se asume que es base 10.
La clase Long
Cada tipo numérico tiene su propia clase de objetos. Así el tipo long tiene el objeto Long. De
la misma forma que con la clase Character, se han codificado muchas funciones útiles
dentro 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 que
se trabaja, se asume que es base 10.
La clase Boolean
Los valores boolean también tienen su tipo asociado Boolean, aunque en este caso hay
menos métodos implementados que para el resto de las clases numéricas.
98 Tutorial de Java
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 String
Java posee gran capacidad para el manejo de cadenas dentro de sus clases String y
StringBuffer. Un objeto String representa una cadena alfanumérica de un valor constante
que 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 de
las 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, con
lo 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.
Tutorial de Java 99
Funciones Básicas
La primera devuelve la longitud de la cadena y la segunda devuelve el carácter que se
encuentra en la posición que se indica en indice:
int length();
char charAt( int indice );
Funciones ValueOf
La clase String posee numerosas funciones para transformar valores de otros tipos de
datos 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.
String PI = Uno.valueOf( f );
String PI = String.valueOf( f ); // Mucho más correcto
Funciones de Conversión
La clase StringBuffer
Java posee gran capacidad para el manejo de cadenas dentro de sus clases String y
StringBuffer. Un objeto String representa una cadena alfanumérica de un valor constante
que 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-
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 funciones
de comparación.
return( destino.toString() );
}
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.
Uso de conversiones
Veamos un ejemplo de utilidad de estas funciones. En el applet Conversion.java, que se
muestra en el código que sigue, se usan estas funciones para producir una salida útil en un
programa, 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»;
RatonX = x;
RatonY = y;
Status = X.toString()+»,»+Y.toString();
repaint();
return true;
}
}
Introducción al AWT
AWT 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 decanta
por Abstracto, seriedad por encima de todo. Se trata de una biblioteca de clases Java para
el desarrollo de Interfaces de Usuario Gráficas. La versión del AWT que Sun proporciona
con el JDK se desarrolló en sólo dos meses y es la parte más débil de todo lo que representa
Java 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ón
gráfica; veremos que la simple acción de colocar un dibujo sobre un botón se vuelve una
tarea harto complicada. Quizá la presión de tener que lanzar algo al mercado haya tenido
mucho 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.
No obstante y pese a ello, vamos a abordar en este momento la programación con el AWT
para tener la base suficiente y poder seguir profundizando en las demás características del
lenguaje Java, porque como vamos a ir presentando ejemplos gráficos es imprescindible el
conocimiento del AWT. Mientras tanto, esperemos que JavaSoft sea fiel a sus predicciones
y lo que ahora veamos nos sirva de base para migrar a un nuevo y maravilloso AWT.
Interface de usuario
La 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ínea
de comandos hasta las interfaces gráficas que proporcionan las aplicaciones más moder-
nas.
Al nivel más bajo, el sistema operativo transmite información desde el ratón y el teclado
como 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 leer
el teclado, ni tampoco atender a detalles como la escritura en pantalla. El AWT constituye
una librería de clases orientada a objeto para cubrir estos recursos y servicios de bajo nivel.
Componentes y contenedores
Una 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
En la imagen siguiente presentamos una interface de usuario muy simple, con la apariencia
que presenta cuando se visualiza bajo Windows ’95.
Los Componentes deben circunscribirse dentro del Contenedor que los contiene. Esto hace
que el anidamiento de Componentes (incluyendo Contenedores) en Contenedores crean
árboles de elementos, comenzando con un Contenedor en la raiz del árbol y expandiéndolo
en sus ramas. A continuación presentamos el árbol que representa la interface que corres-
ponde con la aplicación gráfica generada anteriormente.
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
Componentes
Component 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.
Botones
Veremos ejemplos de cómo se añaden botones a un panel para la interacción del usuario
con la aplicación, pero antes vamos a ver la creación de botones como objetos.
Eventos Button
Cada vez que el usuario pulsa un botón, se produce un evento, de la misma forma que se
produce un evento cuando se aprieta el botón del ratón. Los eventos de pulsación de un
botó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 destino
pasado por el objeto Event y comparándolo con los objetos botón que hemos dispuesto en
nuestro interface:
import java.awt.*;
import java.applet.Applet;
this.add( b1 );
this.add( b2 );
this.add( b3 );
return true;
}
}
En el applet anterior, Botones.java, observamos que se imprime el texto asociado al botón
que 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, AWT
ofrece 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 de
elementos. Por ejemplo, podríamos implementar una selección de colores y mantenerla en
un botón Choice:
import java.awt.*;
import java.applet.Applet;
Selector.addItem( «Rojo» );
Selector.addItem( «Verde» );
Selector.addItem( «Azul» );
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 de
selección, comprobamos en primer lugar que se trate efectivamente de un evento generado
en 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;
add( Relleno );
}
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;
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 dentro
de la propia clase extendida de botón. Se pueden añadir estos botones a la aplicación, sin
tener que preocuparse de los eventos que pudieran generar.
public BotonAceptar() {
setLabel( «Aceptar» );
}
Etiquetas
Las etiquetas (Label) proporcionan una forma de colocar texto estático en un panel, para
mostrar 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:
Listas
Las listas (List) aparecen en los interfaces de usuario para facilitar a los operadores la
manipulación de muchos elementos. Se crean utilizando métodos similares a los de los
botones Choice. La lista es visible todo el tiempo, utilizándose una barra de desplazamiento
para 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;
return true;
}
}
Para acceder a los elementos seleccionados se utilizan los métodos getSelectedItem() o
getSelectedItems(). Para listas de selección simple, cualquier selección con doble-click en
la lista disparará el método action() de la misma forma que con los eventos de selección en
menús.
import java.awt.*;
import java.applet.Applet;
lm.addItem( «Mercurio» );
lm.addItem( «Venus» );
lm.addItem( «Tierra» );
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 el
evento que hace que el applet recoja los elementos que hay seleccionados en la lista.
Campos de texto
Para la entrada directa de datos se suelen utilizar los campos de texto, que aparecen en
pantalla 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 de
las características anteriores. La imagen muestra los distintos tipos de campos.
Areas de texto
Java, 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 han
descrito en la creación de Campos de Texto. Pero además, para las áreas de texto hay que
especificar el número de columnas.
Se puede permitir que el usuario edite el texto con el método setEditable() de la misma forma
que 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 no
editable.
import java.awt.*;
import java.applet.Applet;
t1 = new TextArea();
t2 = new TextArea( «Tutorial de Java»,5,40 );
t2.setEditable( false );
add( t1 );
add( t2 );
add( boton );
}
System.out.println( texto );
}
}
return true;
}
}
Para acceder al texto actual de una zona de texto se utiliza el método getText(), tal como
muestra el código anterior. Las áreas de texto no generan eventos por sí solas, por lo que
hay que utilizar eventos externos, para saber cuando tenemos que acceder a la información
contenida en el TextArea. En este caso hemos utilizado un botón que generará un evento al
pulsarlo que hará que se recoja el texto que haya escrito en el área de texto que constituye
el applet.
Canvas
Si tenemos un applet que trabaja con imágenes directamente, ya sea un applet gráfico o de
dibujo, los lienzos o zonas de dibujo (Canvas) resultan muy útiles.
Los Canvas son un Componente básico que captura eventos de exposición (expose), de
rató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 eseos
eventos.
Al permitir saltarse el manejo normal de eventos, y junto con los métodos de representación
gráfica, los canvas simplifican la producción de applets que necesitan una única funcionalidad
para distintas áreas. Por ejemplo, el applet Lienzo.java, contiene un manejador de eventos
que controla el evento mouseDown en el canvas. Si el evento no se genera en el canvas, se
le pasa al applet que lo tratará como un evento de ratón normal.
import java.awt.*;
add( «Center»,canv );
add( «South»,boton );
}
reshape( 0,0,anc,alt );
}
import java.awt.*;
import java.applet.Applet;
add( rojo );
add( verde );
add( azul );
}
}
Este tipo de interfaz proporciona al usuario un punto de referencia visual de un rango y al
mismo tiempo la forma de cambiar los valores. Por ello, las barras de desplazamiento son
Componentes un poco más complejos que los demás, reflejándose esta complejidad en sus
constructores. Al crearlos hay que indicar su orientación, su valor inicial, los valores mínimo
y 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;
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 lugar
del 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 no
disponen de un display o zona donde se muestren directamente los valores asociados a los
desplazamientos. Al contrario, si se desea eso, es necesario añadir explícitamente una caja
de texto, tal como se muestra en el ejemplo RangoRojo.java.
import java.awt.*;
import java.applet.Applet;
add( etiqueta );
add( rango );
add( valor );
}
Para que podamos reusarlos, tenemos que poner cuidado en el diseño, implementando
métodos get y set, lanzando excepciones cuando proceda y permitiendo el acceso a los
campos de que disponga nuestro Componente.
int orientacion;
Dimension sepTama,sepDim;
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 de
nuestro separador recién creado:
import java.awt.*;
import java.applet.Applet;
add( «North»,texto1 );
add( «Center»,raya );
static {
defFuente = new Font( «Dialog»,0,12 );
}
}
Un ejemplo de uso, lo podremos observar compilando y ejecutando el código que se puestra
a continuación, EjGrupo.java:
import java.awt.*;
import java.applet.Applet
Contenedores
Container es una clase abstracta derivada de Component, que representa a cualquier
componente que pueda contener otros componentes. Se trata, en esencia, de añadir a la
Window
Es una superficie de pantalla de alto nivel (una ventana). Una instancia de la clase Window
no puede estar enlazada o embebida en otro Contenedor. Una instancia de esta clase no
tiene ni título ni borde.
Frame
Es una superficie de pantalla de alto nivel (una ventana) con borde y título. Una instancia de
la clase Frame puede tener una barra de menú. Una instancia de esta clase es mucho más
aparente 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 de
la 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, simplemente
proporciona 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
En el siguiente ejemplo se crea un Frame vacío. El título del Frame, que corresponderá al
tí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.*;
f.show();
}
}
En el código de ejemplo que sigue, extendemos el código anterior para que la nueva clase
sea una subclase de la clase Panel. En el método main() de esta nueva clase se crea una
instancia de ella y se le incorpora un objeto Frame llamando al método add(). El resultado de
ambos ejemplos es idéntico a efectos de apariencia en pantalla:
import java.awt.*;
f.add( «Center»,ej );
f.pack();
f.show();
}
}
Derivando la nueva clase directamente de la clase Applet en vez de Panel, nuestro ejemplo
puede ahora ejecutarse tanto como una aplicación solitaria como dentro de una página
HTML en un navegador. El código siguiente muestra esta circunstancia:
import java.awt.*;
f.add( «Center»,ej );
f.pack();
f.show();
}
}
Un objeto Window, y en algunos casos incluso un objeto Dialog, pueden reemplazar al
objeto Frame. Son Contenedores válidos y los Componentes se añaden en ellos del mismo
modo que se haría sobre un Frame.
En el código siguiente, incorporamos dos botones al código del último ejemplo. La creación
se realiza en el método init() porque éste siempre es llamado automáticamente al inicializarse
el applet. De todos modos, al inciarse la ejecución se crean los botones, ya que el método
init() es llamado tanto por el navegador como por el método main():
import java.awt.*;
ej.init();
f.add( «Center»,ej );
f.pack();
f.show();
}
}
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)
130 Tutorial de Java
Crear el marco de la aplicación
El Contenedor de los Componentes es el Frame. También es la ventana principal de la
aplicació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 para
la 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, ya
que 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, para
que nos permitan entrar a fondo en otras; y ya conociendo estas últimas, volveremos sobre
las primeras. Un poco lioso, pero imprescindible.
Comenzaremos el desarrollo de nuestra aplicación básica con AWT a partir del código que
mostramos a continuación:
import java.awt.*;
public AplicacionAWT() {
super( «Aplicación Java con AWT» );
pack();
resize( HOR_TAMANO,VER_TAMANO );
show();
}
La llamada a show() es necesaria, ya que por defecto, los Contenedores del AWT se crean
ocultos 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
Si ejecutamos de nuevo la aplicación con los cambios que hemos introducido, aparecerá
ante nosotros la ventana que se muestra a continuación.
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 es
totalmente frustrante radica en la organización de los menús, pero eso, las reglas del diseño
de un buen árbol de menús, no están claras. Hay un montón de libros acerca de la ergonomía
y de cómo se debe implementar la interacción con el usuario. Lo cierto es que por cada uno
que defienda una idea, seguro que hay otro que defiende la contraria. Todavía no hay un
acuerdo para crear un estándar, con cada Window Manager se publica una guía de estilo
diferente. 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.
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 los
Botones, Listas, etc. En el caso de menús, es el evento ACTION_EVENT de la clase
java.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 no
sea estrictamente necesario, lo primero que hacemos es asegurarnos de que el objeto
evt.target es realmente un objeto MenuItem, es decir, procede de la barra de menús; y
despué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 que
estamos 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 sido
generado mediante la aplicación Cascada.java.
Tutorial de Java 135
Básicamente se utiliza la técnica ya descrita, pero en vez de crear un nuevo MenuItem se
crea un nuevo Menu, lo que origina el menú en cascada.
Dialogos y Ventanas
Una Ventana genérica, Window, puede utilizarse simplemente para que sea la clase padre
de 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 del
JDK 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 clases
que 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, no
permite hacer nada al usuario hasta que responda al diálogo. Esto es lo que se usa en las
cajas de diálogo «Acerca de...», en la selección en listas, cuando se pide una entrada numé-
rica, etc.
Las aplicaciones independientes deberían heredar tomando como padre la ventana princi-
pal de esa aplicación. Así pueden implementar la interface MenuContainer y proporcionar
menús.
Paneles
La 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 a
los 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 en
la clase Container, llama al método createPanel() del Toolkit adecuado, creando así un
PanelPeer. 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ónico
del AWT; se trata del bien conocido esquema de separación interface/implementación que
establece por un lado una clase de interface y por otro distintas clases de implementación
para 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 modificarse
con mucha flexibilidad. Permite que cada Contenedor pueda tener su propio esquema de
fuentes de caracteres, color de fondo, zona de diálogo, etc.
Podemos, por ejemplo, crear una barra de herramientas para la zona superior de la ventana
de la aplicación o incorporarle una zona de estado en la zona inferior de la ventana para
mostrar información útil al usuario. Para ello vamos a implementar dos Paneles:
class BarraHerram extends Panel {
public BarraEstado() {
setLayout( new FlowLayout() );
add( texto = new Label( «Creada la barra de estado» ) );
add( mas_texto = new Label( «Información adicional» ) );
}
Layouts
Los layout managers o manejadores de composición, en traducción literal, ayudan a adaptar
los diversos Componentes que se desean incorporar a un Panel, es decir, especifican la
apariencia que tendrán los Componentes a la hora de colocarlos sobre un Contenedor. Java
dispone de varios, en la actual versión, tal como se muestra en la imagen:
Sigamos imaginando, ahora, que un hábil equipo de desarrollo ha previsto las disposiciones
gráficas más usadas y ha creado un gestor para cada una de tales configuraciones, que se
En el applet que genera la figura siguiente, se utilizan los diferentes tipos de layouts que
proporciona el AWT.
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ía
o cuando el Contenedor tiene un hijo incorporado o eliminado. La validación se realiza
automáticamente cuando se llama a pack() o show(). Los Componentes visibles marcados
como no válidos no se validan automáticamente.
validate();
return true;
}
}
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;
add( boton1 );
add( boton2 );
add( boton3 );
}
}
Este código, AwtFlow.java, construye tres botones con un pequeño espacio de separación
entre ellos.
BorderLayout
La 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 los
Componentes sobre ellas: Norte, Sur, Este, Oeste y Centro. Es el layout o composición que
se utilizan por defecto Frame y Dialog.
El Norte ocupa la parte superior del panel, el Este ocupa el lado derecho, Sur la zona inferior
y Oeste el lado izquierdo. Centro representa el resto que queda, una vez que se hayan
rellenado las otras cuatro partes.
import java.awt.*;
import java.applet.Applet;
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:
GridLayout
La 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 celdas
de la tabla así definida.
En la figura siguiente se muestra un panel que usa este tipo de composición para posicionar
seis botones en su interior, con tres filas y dos columnas que crearán las seis celdas nece-
sarias para albergar los botones:
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.
GridBagLayout
Es 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 GridBagLayout
para soportar diez botones en su interior:
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;
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 pintar
antes en papel como van a estar posicionados los Componentes; utilizar gridx, gridy, gridwidth
y gridheight en vez de GridBagConstraints.RELATIVE, porque en el proceso de validación
del 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 conveniencia
addComponente() para la incorporación de nuevos Componentes al layout, lo que hace más
sencillo el manejo de los Constraints:
import java.awt.*;
import java.applet.Applet;
import java.awt.*;
import java.applet.Applet;
c.addItem( PanelBoton );
c.addItem( PanelTexto );
ac.add( c );
add( «North»,ac );
card.add( PanelBoton,p1 );
card.add( PanelTexto,p2 );
public MiLayout() {
}
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 eventos
En el pasado, un programa que quisiera saber lo que estaba haciendo el usuario, debía
recoger la información él mismo. En la práctica, esto significaba que una vez inicializado, el
programa entraba en un gran bucle en el que continuamente se bloqueaba para comprobar
que el usuario estuviese haciendo algo interesante (por ejemplo, pulsar un botón, pulsar
una tecla, mover una barra o mover el ratón) y tomar las acciones oportunas. Esta técnica se
conoce como polling.
150 Tutorial de Java
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 el
có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 polling
necesita que el programa esté ejecutando el bucle, consumiendo tiempo de CPU, mientras
está esperando a que el usuario se decida a hacer algo, lo que supone un gran despilfarro
de 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 los
eventos. Un evento describe, con suficiente detalle, una acción particular del usuario. En
lugar de que el programa activamente recoja todos los eventos generados por el usuario, el
sistema Java avisa al programa cuando se produce un evento interesante.
La clase Event
Un contenedor soltado en un entorno gráfico se convierte en rápido receptor de eventos de
todo tipo, singularmente de los relacionados con el movimiento del ratón, pulsaciones de
teclas, 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 de
la clase Event son los que se indican a continuación:
key - Para eventos de teclado, es la tecla que se ha pulsado. Su valor será el valor Unicode
del carácter que representa la tecla. Otros valores que puede tomas son los de las teclas
especiales como INICIO, FIN, F1, F2, etc.
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
Tutorial de Java 151
objeto String que contiene la etiqueta de texto del botón
Una instancia de la clase Event será creada por el sistema Java cada vez que se genere un
evento. Es posible, sin embargo, que un programa cree y envíe eventos a los Componentes
a través de su método postEvent().
Tipos de eventos
Los 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
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 ha
capturado en la línea de comandos desde donde se ha lanzado el applet.
Una Lista es una lista de cadenas o Strings definidas en java.awt.List. Crearemos una lista
de 25 líneas y no permitiremos selección múltiple, que son los dos parámetros que necesita
el constructor del objeto List. El código EventosList.java que se muestra a continuación
corresponde al anterior ejemplo un poco modificado.
Moviendo el ratón o actuando sobre los dos Componentes, botón y lista, podemos observar
los datos que el sistema Java envía en la recolección de esos eventos.
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 clase
Event y rellena sus datos miembro con la información necesaria para describir la acción. Es
en ese momento cuando el sistema Java permite al applet controlar el evento. Este control
comienza por el Componente que recibe inicialmente el evento (por ejemplo, el botón que
ha sido pulsado) y se desplaza hacia arriba en el árbol de Componentes, componente a
componente, 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 (o
má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
Veamos la descripción de una acción con el applet de la figura anterior. El usuario pulsa el
botó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 y
el Componente que ha recibido el click) y empaqueta todos esos datos en una instancia de
la 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() del
Componente, ofrece a éste la posibilidad de reaccionar ante el evento. Si el Componente no
controla el evento, o no lo hace completamente (indicado por un valor de retorno false), el
sistema 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 este
mismo modo hasta que el evento es controlado en su totalidad o ya no hay Componentes a
los que informar. En la figura siguiente mostramos el camino recorrido por el evento en su
intento de que algún Componente lo controle.
Cada Componente del applet añade una línea al objeto TextArea indicando que ha recibido
un 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 siguientes
líneas:
ACTION_EVENT
Algunos de los eventos que más frecuentemente tendremos que controlar son los siguien-
tes:
ACTION_EVENT
MOUSE_DOWN
KEY_PRESS
WINDOW_DESTROY
Como ejemplo del manejo de eventos vamos a ver este evento que se provoca al pulsar un
botón, seleccionar un menú, etc. Para su control podemos manejarlo en el método
handleEvent() o en el método action().
Los dos métodos anteriores pertenecen a la clase Component por lo que todas las clases
derivadas de ésta contendrán estos dos métodos y se pueden sobrecargar para que se
ajuste su funcionamiento a lo que requiere nuestra aplicación.
Veamos el siguiente ejemplo, en que se controla este evento a través del método
handleEvent(), 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. Los
ejemplos 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:
Los diseñadores del AWT parece que se pusieron como meta principal que las clases del
AWT funcionasen correctamente, dejando un poco de lado su apariencia. Sin embargo, han
proporcionado 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 a
ver 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
Utiliza tres tipos de fonts de caracteres (en diferente estilo y diferente tamaño) para llamar la
atención del usuario sobre las tres zonas de la interface. La fuente por defecto para todos
los Componentes es la fuente Dialog. Java proporciona otras fuentes con propósitos más
fr.pack();
fr.show();
}
}
La fuente de caracteres solamente se indica para el objeto Frame, el botón, la caja y el área
de texto también utilizarán esta fuente.
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();
}
}
En este caso, en lugar de llamar a un método para indicar cuál debe ser el tamaño de un
Componente, hay que derivar una nueva clase del Componente y redefinir el método
preferredSize() que devolverá el tamaño preferido. El layout manager llama al método
preferredSize() 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 especificar
un tamaño, pero no debe hacerse. Ambos métodos son usados directamente por el layout
manager, y los ajustes de tamaño se reclacularán la próxima vez que el layout manager
tenga 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 con
la altura y anchura especificadas y se lo devuelve a quien lo ha llamado (normalmente el
layout 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íe
dinámicamente, como ocurre en el applet CambioBoton.java, mostrado en la figura:
Uso de Insets
Insets, o borde interior, al igual que el tamaño preferido, se puede utilizar para proporcionar
a 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.
A continuación mostramos el uso típico de insets(). El código de este ejemplo indica que los
Componentes 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 uso
de insets().
Tomemos como ejemplo ahora el applet que aparece en la figura siguiente, obtenido de la
ejecución de Habilitar.java:
En este applet, los botones Añadir y Borrar están deshabilitados hasta que el usuario haya
entrado en el campo de texto. Este previene una activación inadvertida de los dos botones
en el caso de que no haya texto en el campo. Tan pronto como se teclee el primer carácter
en el campo de texto, se habilitan los dos botones, cada cual asumiendo su propio rol. Si, en
cualquier momento, el campo de texto se volviese a quedar vacío, los dos botones volverían
a 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.
En el fichero fuente, podemos comprobar que las tres imágenes se pueden pasar cómo
pará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, solamente
se 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;
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, querremos
hacer cosas más interesantes que mostrar texto; ya que cualquier página HTML puede
mostrar texto. Para ello, Java proporciona la clase Graphics, que permite mostrar texto a
través del método drawString(), pero también tiene muchos otros métodos de dibujo. Para
cualquier programador, es esencial el entendimiento de la clase Graphics, antes de
adentrarse en el dibujo de cualquier cosa en Java. Esta clase proporciona el entorno de
trabajo 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 a
afectar a todas las operaciones gráficas, incluyendo los colores de fondo y texto, la fuente
de caracteres, la localización y dimensiones del rectángulo en que se va a pintar, e incluso
dispone de información sobre el eventual destino de las operaciones gráficas (pantalla o
imagen). Por otro lado, la clase Graphics proporciona métodos que permiten el dibujo de
primitivas, figuras y la manipulación de fonts de caracteres y colores. También hay clases
para la manipulación de imágenes, doble-buffering, etc.
Para poder pintar, un programa necesita un contexto gráfico válido, representado por una
instancia de la clase Graphics. Pero, como esta clase es abstracta, no se puede instanciar
directamente; 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 que
está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 dos
axiomas:
· Primero, el AWT repinta solamente aquellos Componentes que necesitan ser
repintados, bien porque estuviesen cubiertos por otra ventana o porque se pida su
repintado directamente
· Segundo, si un Componente estaba tapado y se destapa, el AWT repinta solamente
la 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 de
momento, 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
La pantalla en Java se incrementa de izquierda a derecha y de arriba hacia abajo, tal como
muestra la figura:
Líneas
Si 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 ya
que graphics es también una instancia de Graphics. Graphics es una clase abstracta por lo
que no se pueden crear objetos de esta clase, es decir, la siguiente sentencia es totalmente
ilegal:
g = new Graphics();
porque no se puede utilizar new para obtener g.
Para dibujar un rectángulo en pantalla, basta con llamar al método drawRect() indicando la
posición en que deseamos colocar la esquina superior-izquierda del rectángulo y su ancho
y alto. Por ejemplo, el código siguiente, Rect1.java, pinta un rectángulo alrededor del applet
que lo contiene:
import java.awt.*;
import java.applet.Applet;
repaint();
}
repaint();
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 que
en C u otros lenguajes que permiten castings arbitrarios. En Java solamente se pueden
realizar 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 pintar
un rectángulo aleatorio, lo mostraremos en un color aleatorio. En el código, Rect3.java, que
mostramos, se pinta un rectángulo con tamaño, posición y color aleatorios:
import java.awt.*;
import java.applet.Applet;
repaint();
}
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 del
todo el ejemplo, permitiendo que se pinten multitud de rectángulos coloreados. Debido a
que cada uno de los rectángulos va a ser diferente, tendremos que mover los cálculos de
posición, tamaño y color de cada rectángulo al método paint(). Además, vamos a permitir
especificar en el fichero HTML que lance el applet, el número de rectángulos que se van a
dibujar. Para ello fijaremos un número por defecto y en caso de que se incluya el número de
rectángulos como parámetro de la invocación al applet, utilizaremos este último. He aquí el
código, Rect4.java, que realiza todo esto:
import java.awt.*;
import java.applet.Applet;
repaint();
}
g.setColor( color );
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. El
método que pinta rectángulos tridimensionales todavía necesitan un parámetro más que
indica 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 estos
métodos:
Circulos, elipses
Los m‰todos definidos en Java para la realizaci•n de c•rculos y elipses, al igual que en
la mayor•a de los lenguajes, reciben como parßmetros las coordenadas de la esquina
superior-izquierda y el ancho y alto de la caja en la que se circunscribe el c•rculos o la
elipse.
int y = OjoPY;
dx = swap( dx >> 3,OjoR1 << 1 );
dy = swap( dy >> 5,OjoR1 >> 1 );
if( Mx >= 0 && My >= 0 )
{
x -= dx;
y -= dy;
}
return true;
}
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 y
alto, en valor positivo, del rectángulo que circunscribe al óvalo o al arco, como hemos
dicho. Para pintar arcos, necesitamos dos parámetros adicionales, un ángulo de inicio y
un ángulo para el arco; de este modo especificamos el inicio del arco y en tamaño del
arco en grados (no en radianes). En la figura que sigue mostramos cómo se tiene en
cuanta el ángulo a la hora de las especificación de ángulos en los valores de los
parámetros a pasar a los métodos drawArc() y fillArc().
Polígonos
Los polígonos son figuras creadas a partir de una secuencia de segmentos. Cada método
que permite dibujar polígonos necesita como parámetros las coordenadas de los puntos
donde termina cada uno de los segmentos que constituyen el polígono. Estos puntos se
pueden 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 punto
a punto.
El applet, FigPoligono.java, que aparece seguidamente permite ver los métodos de dibujo
de polígonos en acción.
x0 = y0 = 0;
xN = d.width-1;
yN = d.height-1;
}
El applet anterior funciona correctamente, pero adolece de dos dos problemas: Primero, los
senos son operaciones en coma flotante, luego para que el applet tenga realmente utilidad
necesitamos utilizar números en coma flotante. Segundo, el sistema de coordenadas que
maneja 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 la
esquina 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 de
un rectángulo en coordenadas cartesianas y, por otro lado, tomaremos a la pantalla como un
x0 = y0 = 0;
xN = apAncho-1;
yN = apAlto-1;
xmin = -10.0;
xmax = 10.0;
ymin = -1.0;
ymax = 1.0;
}
j1 = ValorY( 0 );
for( int i=0; i < apAncho; i++ )
{
j2 = ValorY( i+1 );
g.drawLine( i,j1,i+1,j2 );
j1 = j2;
}
}
return( retorno );
}
Fractales
No vamos aquí a proporcionar una clase maestra sobre fractales, sino que vamos a mostrar
que Java también se puede emplear para la implementación de algunos ejemplos clásicos
de la geometría de fractales. Vamos a realizar por uno sencillito, en el que se trata de un
conjunto 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 primer
tercio y el último, ambos exclusive.
Para visualizar lo anterior vamos a utilizar segmentos de línea. Eliminaremos el tercio medio
del 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 este
proceso indefinidamente.
La verdad es que parece un poco confusa la explicación, pero si una imagen vale más que
mil palabras, un buen programa Java vale más que mil imágenes. Por ello, aquí esta el
código Java, Tercio.java, que muestra sucesivas líneas en que se demuestra la explicación
del párrafo anterior.
import java.awt.*;
import java.applet.Applet;
import java.util.Vector;
Es de hacer notar que en este programa Java hemos utilizado el objeto Vector. Debido a que
Java no dispone de punteros, este objeto y la clase asociada, disponen de los métodos
necesarios para poder implementar una lista enlazada, que es lo que nosotros hemos utili-
zado.
Líneas flotantes
El ejemplo de las líneas flotantes es más difícil de describir que su código. Se ejecuta sobre
un bucle infinito y son líneas que se están moviendo continuamente por la ventana que
ocupa el applet, rebotando en los bordes y moviéndose continuamente. El código del fichero
Lineas.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;
retornoMath = Math.random();
return( (int)(retornoMath * rango) );
}
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;
}
}
}
DOBLE-BUFFERING de gráficos
Al mostrar gráficos con las técnicas estándar, las imágenes suelen aparecer a trozos o con
parpadeo. Las aplicaciones Java permiten que los programas dibujen en memoria, para
luego 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:
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étodo
paint().
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 utilizar
como tablero de dibujo, es decir, es la zona en que se va a pintar. Ya hemos visto y utilizado
contextos gráfico en las declaraciones del método paint():
// Construye la espiral
public void creaEspiral() {
int cX = iniX;
int cY = iniY;
g.drawLine( cX,cY,(cX-iniAncho),cY );
g.drawLine( (cX-iniAncho),Cy,(cX-iniAncho),(cY-iniAlto) );
cX -= iniAncho;
cY -= iniAlto;
iniAncho -= incremento;
iniAlto -= incremento;
}
}
}
La clase MediaTracker
Si 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 para
manejar objetos multimedia y grupos de objetos.
Se debería utilizar addImage() con cada imagen que necesite un applet. MediaTracker sólo
Si se quiere saber si un determinado gráfico ha sido cargado, se pueden utilizar los métodos
check. Si se quiere que un gráfico sea cargado antes de hacer cualquier otra cosa, usar los
métodos waitFor. El uso de estos métodos es especialmente útil ya que cuando realizamos
la carga de una imagen con getImage(), esta carga se realiza en un thread aparte del de
nuestro 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ón
de la animación, sin haberse cargado todavía todas las imágenes.
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.
try {
// Utilizamos el tracker para asegurar que se
// cargaran todas las imágenes
tracker.waitForAll();
}
catch( InterruptedException e ) {
}
cargado = true;
}
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 puede
acceder 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 mismo
directorio.
Veamos como en el applet Bucle.java utilizamos estos métodos para repetir automáticamente
una pista de audio.
import java.awt.Graphics;
import java.applet.Applet;
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 de
la clase Applet, pero se necesita definir sus acciones asociadas, de la misma forma que se
realiza con init() o con paint().
public boolean mouseDown( Event evt,int x,int y ) {
/* ratón pulsado en x,y */
/* hacer algo */
}
En el applet Raton.java, se muestra la utilización del ratón para recibir las coordenadas en
donde 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;
Las excepciones, pues, pueden originarse de dos modos: el programa hace algo ilegal (caso
normal), 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.
Excepciones predefinidas
Las excepciones predefinidas y su jerarquía de clases es la que se muestra en la figura:
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; // donde
x 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 ha
fallado por falta de memoria. Y siempre tendría que haber memoria suficiente porque
el garbage collector se encarga de proporcionarla al ir liberando objetos que no se
usan 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 a
un 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 un
método a.kk
InternalException
Este error se reserva para eventos que no deberían ocurrir. Por definición, el usuario
nunca debería ver este error y esta excepción no debería lanzarse.
try
Es el bloque de código donde se prevé que se genere una excepción. Es como si dijésemos
«intenta estas sentencias y mira a ver si se produce una excepción». El bloque try tiene que
ir seguido, al menos, por una cláusula catch o una cláusula finally
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 que
asegurarnos de colocar código que no genere excepciones. Se pueden colocar sentencias
catch sucesivas, cada una controlando una excepción diferente. No debería intentarse 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 de
las esperadas. En este caso es mejor dejar que la excepción se propague hacia arriba y dar
un mensaje de error al usuario.
Se pueden controlar grupos de excepciones, es decir, que se pueden controlar, a través del
argumento, excepciones semejantes. Por ejemplo:
class Limites extends Exception {}
class demasiadoCalor extends Limites {}
class demasiadoFrio extends Limites {}
class demasiadoRapido extends Limites {}
class demasiadoCansado extends Limites {}
.
.
.
try {
if( temp > 40 )
throw( new demasiadoCalor() );
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 seguimiento
de lo que está pasando, porque como se ejecuta siempre puede dejarnos grabado si se
producen 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ódigo
que 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 a
tomar. En la mayoría de los casos, bastará con presentar una indicación de error al usuario
y un mensaje avisándolo de que se ha producido un error y que decida si quiere o no
continuar con la ejecución del programa.
Por ejemplo, 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() );
Propagación de excepciones
La cláusula catch comprueba los argumentos en el mismo orden en que aparezcan en el
programa. Si hay alguno que coincida, se ejecuta el bloque y sigue el flujo de control por el
bloque 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, entonces
se ejecutará el código de la cláusula finally (en caso de que la haya). Lo que ocurre en este
caso, es exactamente lo mismo que si la sentencia que lanza la excepción no se encontrase
encerrada 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 el
control 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 de
llamadas sin encontrar un controlador específico para la excepción, entonces la ejecución
se 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 generar
excepciones. Esto debe ser así para que cualquiera que escriba una llamada a ese método
esté 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 o
propagarla hacia arriba en la pila de llamadas. La siguiente línea de código muestra la forma
Cuando se crea una nueva excepción, derivando de una clase Exception ya existente, se
Flujo en programas
En nuestra aplicación de saludo, no vemos el thread que ejecuta nuestro programa. Sin
embargo, Java posibilita la creación y control de threads explícitamente. La utilización de
threads 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 no
se puede con otros lenguajes de tercera generación. En un lenguaje orientado a Internet
como es Java, esta herramienta es vital.
Las aplicaciones (y applets) multithreaded utilizan muchos contextos de ejecución para cumplir
su trabajo. Hacen uso del hecho de que muchas tareas contienen subtareas distintas e
independientes. Se puede utilizar un thread para cada subtarea.
Mientras que los programas de flujo único pueden realizar su tarea ejecutando las subtareas
secuencialmente, un programa multithreaded permite que cada thread comience y termine
tan 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
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 implemente
una interface debe implementar todos los métodos definidos en esa interface. Una interface
tiene 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 instanciada
con 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 se
realizará todo el trabajo de la clase. Extendiendo la clase Thread, se pueden heredar los
métodos y variables de la clase padre. En este caso, solamente se puede extender o derivar
una vez de la clase padre. Esta limitación de Java puede ser superada a través de la
implementació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 pueda
ejecutar el proceso como un thread. Además, el método abstracto run() está definido en la
interface Runnable tiene que ser implementado. La única diferencia entre los dos métodos
es 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 creadas
que necesiten ejecutarse como un thread , implementarán la interface Runnable, ya que
probablemente 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 idea
sobre el diseño de la clase Thread. De hecho, si vemos los fuentes de Java, podremos
comprobar que solamente contiene un método abstracto:
package java.lang;
public interface Runnable {
Arranque de un Thread
Las aplicaciones ejecutan main() tras arrancar. Esta es la razón de que main() sea el lugar
natural 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 el
tiempo 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 otros
programas. run() sirve como rutina main() para los threads; cuando run() termina, también lo
hace el thread. Todo lo que queramos que haga el thread ha de estar dentro de run(), por
eso 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
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 al
usuario 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 el
mé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étodo
resume():
t1.resume();
Parada de un Thread
El último elemento de control que se necesita sobre threads es el método stop(). Se utiliza
para 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 puede
reanudar ya con t1.start(). Cuando se desasignen las variables que se usan en el thread, el
objeto thread (creado con new) quedará marcado para eliminarlo y el garbage collector se
encargará de liberar la memoria que utilizaba.
Si se necesita, se puede comprobar si un thread está vivo o no; considerando vivo un thread
que 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 haya
llamado a su método run() y no haya sido parado con un stop() ni haya terminado el método
run() en su ejecución.
En este caso, la clase App1Thread está forzada a implementar Runnable sobre la clase
Applet 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
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 otros
threads a ejecutarse. Otra forma de detener la ejecución del thread es hacer una llamada al
método stop(). En el contador, el thread se detiene cuando se pulsa el ratón mientras el
cursor 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 se
estará todavía incrementando, pero no se visualiza en pantalla.
...
return( true );
}
...
Estados de un THREAD
Durante el ciclo de vida de un thread, éste se puede encontrar en diferentes estados. La
figura siguiente muestra estos estados y los métodos que provocan el paso de un estado a
otro. Este diagrama no es una máquina de estados finita, pero es lo que más se aproxima al
funcionamiento 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 sistema
no ha destinado ningún recurso para él. Desde este estado solamente puede arrancarse
llamando 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 de
una excepción de tipo IllegalThreadStateException.
Cuando el thread se encuentra en este estado, todas las instrucciones de código que se
encuentren 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 se
llama al método sleep(), cuando el thread está bloqueado en un proceso de entrada/salida o
cuando el thread utiliza su método wait() para esperar a que se cumpla una determinada
condició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 el
procesador 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 hacerle
caso cuando se encuentre disponible.
Para cada una de los cuatro modos de entrada en estado Parado, hay una forma específica
de 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
Muerto
Un thread se puede morir de dos formas: por causas naturales o porque lo maten (con
stop()). 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 la
iteració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.
El método stop() envía un objeto ThreadDeath al thread que quiere detener. Así, cuando un
thread es parado de este modo, muere asíncronamente. El thread morirá en el momento en
que reciba la excepción ThreadDeath.
Los applets utilizarán el método stop() para matar a todos sus threads cuando el navegador
con soporte Java en el que se están ejecutando le indica al applet que se detengan, por
El método isAlive()
La interface de programación de la clase Thread incluye el método isAlive(), que devuelve
true 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 ante
un 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», ni
entre un thread «Ejecutable» o «Parado».
Scheduling
Java tiene un Scheduler, una lista de procesos, que monitoriza todos los threads que se
están ejecutando en todos los programas y decide cuales deben ejecutarse y cuales deben
encontrarse preparados para su ejecución. Hay dos características de los threads que el
scheduler identifica en este proceso de decisión. Una, la más importante, es la prioridad del
thread; 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 nuevos
threads heredan la prioridad y el indicador de demonio de los threads que los han creado. El
scheduler determina qué threads deberán ejecutarse comprobando la prioridad de todos los
threads, aquellos con prioridad más alta dispondrán del procesador antes de los que tienen
prioridad más baja.
Prioridades
El scheduler determina el thread que debe ejecutarse en función de la prioridad asignada a
cada uno de ellos. El rango de prioridades oscila entre 1 y 10. La prioridad por defecto de un
thread 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, y
Thread.MAX_PRIORITY, aque tiene un valor de 10. El método getPriority() puede utilizarse
para conocer el valor actual de la prioridad de un thread.
Threads Demonio
Los threads demonio también se llaman servicios, porque se ejecutan, normalmente, con
prioridad baja y proporcionan un servicio básico a un programa o programas cuando la
actividad de la máquina es reducida. Un ejemplo de thread demonio que está ejecutándose
continuamente es el recolector de basura (garbage collector). Este thread, proporcionado
por la Máquina Virtual Java, comprueba las variables de los programas a las que no se
accede nunca y libera estos recursos, devolviéndolos al sistema. Un thread puede fijar su
indicador de demonio pasando un valor true al método setDaemon(). Si se pasa false a este
mé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()).
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 mismo
array. Los lenguajes como C y C++ necesitan de las funciones lock() y unlock() para antes y
despué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-
Ejemplo de animación
Este es un ejemplo de un applet, Animacion.java, que crea un thread de animación que nos
presenta el globo terráqueo en rotación. Aquí podemos ver que estamos creando un thread
de sí mismo, concurrencia. Además, animacion.start() llama al start() del thread, no del
applet, que automáticamente llamará a run():
import java.awt.*;
import java.applet.Applet;
int maxAncho,maxAlto;
Image offScrImage; // Componente off-screen para doble buffering
Graphics offScrGC;
try {
// Utilizamos el tracker para comprobar que todas las
// imágenes están cargadas
tracker.waitForAll();
} catch( InterruptedException e ) {
;
}
cargado = true;
}
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»;
Consumidor
Veamos ahora el código del consumidor, que también extenderá la clase Thread:
class Consumidor extends Thread {
private Tuberia tuberia;
Monitor
Una vez vistos el productor de la información y el consumidor, nos queda por ver qué es lo
que hace la clase Tuberia.
Lo que realiza la clase Tuberia, es una función de supervisión de las transacciones entre los
dos 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 entre
los 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;
Aquí vemos que la variable estaVacia es un semáforo, como los de toda la vida. La naturaleza
privada de los datos evita que el productor y el consumidor accedan directamente a éstos. Si
se 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, el
consumidor no la puede retirar y viceversa. Esta sincronización es vital para mantener la
integridad de cualquier objeto compartido. No sería lo mismo sincronizar la clase en vez de
los métodos, porque esto significaría que nadie puede acceder a las variables de la clase en
paralelo, mientras que al sincronizar los métodos, sí pueden acceder a todas las variables
que están fuera de los métodos que pertenecen a la clase.
Se pueden sincronizar incluso variables, para realizar alguna acción determinada sobre
ellas, 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 de
nuevo al objeto. En el método wait() hacemos que el thread se quede a la espera de que le
llegue un notify(), ya sea enviado por el thread o por el sistema.
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 ventana
reaccionaría a esa información (el consumidor).
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 dos
clases Java: la primera implementa el método main() y la segunda, HolaMundo, tiene un
método nativo que presenta el mensaje de saludo. La implementación de este segundo
método la realizaremos en C.
Las acciones que debemos realizar, para conseguir que nuestra nueva versión del saludo
funcione, serán las que desarrollaremos en las páginas siguientes del Tutorial.
Las siguientes líneas de código definen la clase HolaMundo, que consta de un método y un
segmento 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 HolaMundo
está escrito en otro lenguaje, porque la palabra reservada native aparece como parte de la
definición del método. Esta definición, proporciona solamente la definición para
presentaSaludo() y no porporciona ninguna implementación para él. La implementación la
proporcionaremos desde un fichero fuente separado, escrito en lenguaje C.
El código C que implementa el método presentaSaludo() debe ser compilado en una librería
dinámica y cargado en la clase Java que lo necesite. Esta carga, mapea la implementación
del 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 Java
ejecutará 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 clase
HolaMundo. En un fichero separado, Main.java, vamos a crear una aplicación Java que
instancie 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 otro
mé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 argumentos
que se le pasen. En este caso, el método presentaSaludo() no recibe ningún tipo de argu-
mento.
Compilaremos los dos ficheros fuentes de código Java que hemos creado, tecleando los
siguientes comandos:
> javac HolaMundo.java
> javac Main.java
#ifndef _Included_HolaMundo
#define _Included_HolaMundo
#ifdef __cplusplus
extern «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. Pero
como HolaMundo no tiene ninguna variable, la estructura se encuentra vacía. Se pueden
utilizar los miembros de la estructura para referenciar a variables instanciadas de la clase
desde 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étodo
nativo presentaSaludo() de la clase HolaMundo. Debemos utilizar esa definición cuando lo
implementemos. Si HolaMundo llamase a otros métodos nativos, las definiciones de las
funciones también aparecerían aquí.
El nombre de la función C que implementa el método nativo está derivado del nombre del
paquete, el nombre de la clase y el nombre del método nativo. Así, el método nativo
presentaSaludo() dentro de la clase HolaMundo es HolaMundo_presentaSaludo(). En este
ejemplo, no hay nombre de paquete porque HolaMundo se considera englobado dentro del
paquete por defecto.
Para generar este fichero, debemos indicar el parámetro .stubs al ejecutar la aplicación
javah 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á el
nombre 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>
Escribir la función C
Escribiremos la función C para el método nativo en un fichero fuente de código C. La
implementación será una función habitual C, que luego integraremos con la clase Java. La
definición de la función C debe ser la misma que la que se ha generado con javah en el
fichero HolaMundo.h.
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 el
flag -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 Java
Development 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 al
compilador donde se encuentran los ficheros de cabecera y las librerías, tendremos que fijar
dos variables de entorno:
c:\>SET INCLUDE=%JAVAHOME%\include;%INCLUDE%
c:\>SET LIB=%JAVAHOME%\lib;%LIB%
Ejecutar el programa
Y, por fin, utilizaremos el intérprete de Java, java, para ejecutar el programa que hemos
construido 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 como
UnsatisfiedLinkError, es porque no tenemos fijado correctamente el camino de la librería
dinámica que hemos generado. Este camino es la lista de directorios que el sistema Java
utilizará 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.
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, el
manejo de cadenas, etc. Pero eso supondría entrar en mucha más profundidad dentro de
Java de la que aquí pretendemos (por ahora).
La clase System
Java tiene acceso a la entrada/salida estándar a través de la clase System. En concreto, los
tres ficheros que se implementan son:
Stdin
System.in implementa stdin como una instancia de la clase InputStream. Con System.in, se
accede 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 pueden
utilizar 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 tiene
acceso a los métodos de PrintStream.
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, probablemente
será necesario el acceso directo a ficheros.
Ficheros
Antes de realizar acciones sobre un fichero, necesitamos un poco de información sobre ese
fichero. La clase File proporciona muchas utilidades relacionadas con ficheros y con la
obtención de información básica sobre esos ficheros.
class InfoFichero {
Streams de Entrada
Hay 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 orden
secuencial, byte a byte. Con FileInputStream, se puede elegir acceder a un byte, varios
bytes o al fichero completo.
Apertura de un FileInputStream
Para abrir un FileInputStream sobre un fichero, se le da al constructor un String o un objeto
File:
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 );
int read();
Lee longitud bytes en b comenzando por b[offset]. Devuelve el número de bytes leídos
o -1 si se alcanzó el final del stream.
Cierre de FileInputStream
Cuando se termina con un fichero, existen dos opciones para cerrarlo: explícitamente, o
implícitamente cuando se recicla el objeto (el garbage collector se encarga de ello).
try {
fis = new FileInputStream( «/etc/kk» );
} catch( FileNotFoundException e ) {
/* Hacer algo */
}
try {
Objetos DataInputStream
Los objetos DataInputStream se comportan como los FileInputStream. Los streams de datos
pueden leer cualquiera de las variables de tipo nativo, como floats, ints o chars. General-
mente se utilizan DataInputStream con ficheros binarios.
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-
Para el método String readLine(), se marca el final de la cadena con \n, \r, \r\n o con EOF.
Objetos FileOutputStream
Los objetos FileOutputStream son útiles para la escritura de ficheros de texto. Como con los
ficheros 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 un
fichero 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;
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:
Cierre de FileOutputStream
Cerrar un stream de salida es similar a cerrar streams de entrada. Se puede utilizar el
método explícito:
miFicheroSt.close();
O, se puede dejar que el sistema cierre el fichero cuando se recicle miFicheroSt.
Una vez que el usuario ha terminado de teclear la lista, el programa creará un fichero de
salida 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;
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.
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 el
tamañ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 );
...
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() y
write() de las clases DataInputStream y DataOutputStream.
long getFilePointer();
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
import java.io.*;
Comunicaciones en UNIX
El sistema de Entrada/Salida de Unix sigue el paradigma que normalmente se designa como
Abrir-Leer-Escribir-Cerrar. Antes de que un proceso de usuario pueda realizar operaciones
de entrada/salida, debe hacer una llamada a Abrir (open) para indicar, y obtener permisos
para su uso, el fichero o dispositivo que quiere utilizar. Una vez que el objeto está abierto, el
proceso 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 de
usuario, mientras que Escribir transfiere datos desde el proceso de usuario al objeto. Una
vez que todos estos intercambios de información estén concluidos, el proceso de usuario
llamará a Cerrar (close) para informar al sistema operativo que ha finalizado la utilización
del objeto que antes había abierto.
Socktes
Los sockets son puntos finales de enlaces de comunicaciones entre procesos. Los procesos
los tratan como descriptores de ficheros, de forma que se pueden intercambiar datos con
otros procesos transmitiendo y recibiendo a través de sockets.
Sockets Raw
Son sockets que dan acceso directo a la capa de software de red subyacente o a protocolos
de más bajo nivel. Se utilizan sobre todo para la depuración del código de los protocolos.
En UDP, cada vez que se envía un datagrama, hay que enviar también el descriptor del
socket local y la dirección del socket que va a recibir el datagrama, luego éstos son más
grandes 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 tiempo
empleado en el establecimiento de la conexión, que no existe en UDP.
UDP es un protocolo desordenado, no garantiza que los datagramas que se hayan enviado
sean 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 socket
destino 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 los
programas que utilicen la red, el usar un flujo TCP en vez de un datagrama UDP es más
sencillo y hay menos posibilidades de tener problemas. Sin embargo, cuando se requiere un
rendimiento óptimo, y está justificado el tiempo adicional que supone realizar la verificación
de 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 un
control remoto (rlogin, telnet) y transmisión de ficheros (ftp); que necesitan transmitir datos
de longitud indefinida. UDP es menos complejo y tiene una menor sobrecarga sobre la
conexió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 sockets
Podemos 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 la
cual 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:
Las comunicaciones de información relacionada con Web tienen lugar a través del puerto
80 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 al
puerto 13 utilizando el protocolo UDP. Un servidor que lo emule en Java usaría un objeto
DatagramSocket.
La clase URL
La clase URL contiene contructores y métodos para la manipulación de URL (Universal
Resource Locator): un objeto o servicio en Internet. El protocolo TCP necesita dos tipos de
información: la dirección IP y el número de puerto. Vamos a ver como podemos recibir pues
la 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 como
su dirección IP, o lo que es lo mismo, cuando indicamos yahoo.com es como si hubiesemos
indicado 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.
Ya conocemos la dirección IP, nos falta el número del puerto. Si no se indica nada, se
utilizará el que se haya definido por defecto en el fichero de configuración de los servicios
del sistema. En Unix se indican en el fichero /etc/services, en Windows-NT en el fichero
services y en otros sistemas puede ser diferente.
El puerto habitual de los servicios Web es el 80, así que si no indicamos nada, entraremos
en 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 el
puerto 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.
Dominios de comunicaciones
El 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 dos
procesos que se comunican se encuentran en el mismo sistema o en sistemas diferentes y
cómo pueden ser direccionados.
Las comunicaciones intrasistema (entre dos procesos en el mismo sistema) ocurren (en una
má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 Level
Interface).
En el dominio Unix no se permiten sockets de tipo Raw.
Dominio Internet
Las comunicaciones intersistemas proporcionan acceso a TCP, ejecutando sobre IP (Internet
Protocol). De la misma forma que el dominio Unix, el dominio Internet permite tanto sockets
stream 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 comunicarse
a y desde esos sockets por medio de bloques. UDP es un protocolo no fiable y la entrega de
los 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 se
utiliza para comunicarse entre varias entidades IP.
Apertura de sockets
Si 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 y
numeroPuerto es el puerto (un número) del servidor que está corriendo sobre el cual nos
queremos conectar. Cuando se selecciona un número de puerto, se debe tener en cuenta
que 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
En el ejemplo anterior no se usan excepciones; sin embargo, es una gran idea la captura de
excepciones 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 el
siguiente 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 socket
desde 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
En el lado del servidor, podemos utilizar la clase PrintStream para enviar información al
cliente:
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 de
información desde el cliente.
class smtpCliente {
public static void main( String args[] ) {
Socket s = null;
DataInputStream sIn = null;
DataOutputStream sOut = null;
Servidor de ECO
En el siguiente ejemplo, vamos a desarrollar un servidor similar al que se ejecuta sobre el
puerto 7 de las máquinas Unix, el servidor echo. Básicamente, este servidor recibe texto
desde un cliente y reenvía ese mismo texto al cliente. Desde luego, este es el servidor más
simple 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 aceptase
múltiples clientes simultáneos mediante el uso de threads.
import java.net.*;
import java.io.*;
Cliente/Servidor TCP/IP
class minimoServidor {
public static void main( String args[] ) {
class minimoCliente {
public static void main( String args[] ) throws IOException {
int c;
Socket s;
InputStream sIn;
// 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 fichero
class HTTPlog {
public static void error( String entrada ) {
System.out.println( «Error: «+entrada );
}
if( !f.canRead() )
{
sout.write( HttpUtilidades.error( 404,
«Permiso Denegado»,fich ) );
return;
}
sin.close();
} catch( IOException e ) {
HTTPlog.error( «Excepcion: «+e );
}
}
while( ( c = sin.read() ) != -1 )
{
switch( c ) {
case ‘\r’:
break;
case ‘\n’:
if( esCR )
return( new String( buf,0,0,pos ) );
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 opciones
tengan nombre diferente:
Los valores correspondientes a las otras cuatro pestañas pueden dejarse los que hay
por defecto
Picar Aceptar
Comprobación de la red
Abrir una sesión MS-DOS
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 estuviera
en red. Dos o más programas que se comuniquen en red a través de sockets debería poder
ejecutarse ahora dentro de los dominios del ordenador que acabamos de configurar
Esto sucede porque tiene el DNS activado y no puede encontrar ese DNS o servidor
de direcciones, porque estamos solos en la red. Asegurarse de que en el paso 8 de la
Configuración la opción de DNS está deshabilitada.
Es un objeto utilizado en las aplicaciones servidor para escuchar las peticiones que
realicen los clientes conectados a ese servidor. Este objeto no realiza el servicio, sino
que crea un objeto Socket en función del cliente para realizar toda la comunicación a
través de él.
DatagramSocket
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).
// Cerramos el socket
s.close();
}
}
Y también vamos a implementar el cliente, clienteUDP.java, del socket UDP correspondiente
al servidor que acabamos de presentar:
import java.net.*;
import java.io.*;
import sun.net.*;
Vamos a presentar, por curiosidad más que por otra cosa, una introducción a la interface
Observer y a la clase Observable que proporciona Java. Vamos a implementar programas
basados en la arquitectura Modelo/Vista/Controlador, popularizada por el lenguaje Smalltalk.
Presentamos el problema: queremos diseñar un programa que visualice los datos de una
escena en tres dimensiones y lo pase a dos dimensiones. El programa debe ser modular y
permitir vistas múltiples y simultáneas de la misma escena. Cada vista debe ser capaz de
presentar 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, en
donde tenemos una serie de datos que queremos ver representados gráficamente, para ello
dispondremos 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.
Arquitectura Modelo/Vista/Controlador
La arquitectura MVC (Model/View/Controller) fue introducida como parte de la versión
Smalltalk-80 del lenguaje de programación Smalltalk. Fue diseñada para reducir el esfuerzo
de programación necesario en la implementación de sistemas múltiples y sincronizados de
los mismos datos. Sus características principales son que el Modelo, las Vistas y los
Controladores 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 posteriormente
implementaremos, 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 presentan
partes del diseño con diferente escala de aumento, en ventanas separadas.
El Modelo es el objeto que representa los datos del programa. Maneja los datos y controla
todas sus transformaciones. El Modelo no tiene conocimiento específico de los Controladores
o de las Vistas, ni siquiera contiene referencias a ellos. Es el propio sistema el que tiene
encomendada 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 el
Modelo. 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, actuando
sobre los datos representados por el Modelo. Cuando se realiza algún cambio, entra en
acció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.
La porción del programa que transforma los datos dentro del Modelo en una presentación
gráfica es la Vista. La Vista incorpora la visión del Modelo a la escena; es la representación
gráfica de la escena desde un punto de vista determinado, bajo condiciones de iluminación
determinadas.
El Controlador sabe que puede hacer el Modelo e implementa el interface de usuario que
permite iniciar la acción. En este ejemplo, un panel de datos de entrada es lo único que se
necesita, para permitir añadir, modificar o borrar vértices o caras de la figura.
Observador y observable
El lenguaje de programación Java proporciona soporte para la arquitectura MVC mediante
dos clases:
· Observer: Es cualquier objeto que desee ser notificado cuando el estado de otro
objeto sea alterado
· Observable: Es cualquier objeto cuyo estado puede representar interés y sobre el
cual otro objeto ha demostrado ese interés
Estas dos clases se pueden utilizar para muchas más cosas que la implementación de la
arquitectura MVC. Serán útiles en cualquier sistema en que se necesite que algunos objetos
sean notificados cuando ocurran cambios en otros objetos.
Observer
public void update( Observableobs,Object obj )
Llamada cuando se produce un cambio en el estado del objeto Observable
Observable
public 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 lo
notifica a todos los observadores
public void notifyObservers( Object obj )
Comprueba el flag interno para ver si el Observable ha cambiado de estado y lo
notifica a todos los observadores. Les pasa el objeto especificado en la llamada para
que lo usen los observadores en su método notify().
setChanged();
notifyObservers();
}
Implementar un Observador
Una nueva clase de objetos que observe los cambios en el estado de otro objeto se puede
crear implementando la interface Observer. Esta interface necesita un método update() que
se 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(). El
observador entonces, debería interrogar al objeto Observable para determinar su nuevo
estado; y, en el caso de la arquitectura MVC, ajustar su Vista adecuadamente.
vo.addObserver( to );
}
Se llama a los métodos update() de cada Observador, indicando que hay un cambio en el
estado del objeto que estaban observando. El Observador accede entonces a los
datos del Modelo a través del método público del Observable y actualiza las Vistas.
El Modelo de este ejemplo es muy simple. Su estado interno consta de un valor entero. Este
valor, o estado, es manipulado exclusivamente a través de métodos públicos de acceso. El
código del modelo se encuentra implementado en ValorObservable.java.
Inicialmente, hemos escrito una clase simple de Vista/Controlador. La clase combina las
caracterí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 del
Modelo). 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 para
que el Modelo, la Vista y el Controlador de texto en una clase monolítica), el sistema puede
ser fácilmente rediseñado para manejar otra Vista y otro Controlador. En este caso, hemos
visto 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 puede
ser alterado a través de movimientos del marcador sobre la barra por acción del usuario. El
código de esta clase se encuentra en BarraObservador.java. Se pueden crear instancias de
esta clase pulsando el botón inferior del applet de esta página.
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 estos
applets, habría que añdirles más detalles de configuración y, sobre todo, capturar todas las
excepciones posibles, porque lo que en un sitio va bien, en otro puede generar una excep-
ción.
Etiqueta
Vamos a desarrolllar un nuevo Componente para el AWT, para mejorar el Label que se
proporciona 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 se
presentará en pantalla.
El applet EjEtiqueta.java presenta las tres posibilidades de presentar texto en pantalla que
hemos implementado: Texto normal, resaltado y hundido. A continuación se muestra el re-
sultado de la ejecución del applet.
import java.awt.*;
La interface Temporizador consiste en cuatro métodos, de los cuales los más interesante
son 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 entonces
necesitarí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 no
habrá problemas al estar varios timers corriendo a la vez.
//
// 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
import java.lang.*;
import java.util.*;
while( enEjecucion )
{
// Esperamos el tiempo que nos hayan dicho en la configuracion
// del intervalo
try {
esperar( intervalo );
} catch( InterruptedException e ) {
return;
}
if( enEjecucion )
dfinal = actual;
else
dfinal = parada;
//
// 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
//
// 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 );
}
//
// 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.
//
import java.awt.*;
import java.lang.*;
import java.util.*;
import java.applet.*;
reloj.init();
reloj.start();
horaActual.setText( tiempo );
}
//
// 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.*;
Los ficheros de código fuente que se emplean para la implementación del sistema de sola-
pas propuesto son:
CuerpoFicha.java
Solapa.java
Se encarga de gestionar cada una de las solapas. Nos indica cuando se ha pulsado
en 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 y
otro de clase CuerpoFicha. Se encarga de gestionar la presentación de la Ficha al
completo, de forma que cuando se selecciona una solapa, se presenta el contenido
del 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, que
estarán representadas por las Solapas que el usuario podrá elegir con el ratón.
import java.awt.*;
area = bounds();
//
// Solapa.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 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.*;
delante = ( seleccionada ) ? 2 : 0;
area = bounds();
g.setColor( getBackground() );
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 );
//
// Ficha.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 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;
fm = g.getFontMetrics( solapa.getFuenteSel() );
anchoRotulo = fm.stringWidth( solapa.getRotulo() );
altoRotulo = fm.getHeight();
solapa.reshape( solapaXY.x,solapaXY.y,anchoRotulo+altoRotulo,
altoRotulo+(altoRotulo / 3) );
//
// ListaFichas.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 cuan-
do
import java.awt.*;
import java.util.Vector;
import Ficha;
super();
return( nextF );
}
return( f );
}
return( manejado );
}
medidas( g );
f = primeraFicha();
while( f != null )
{
if( !f.estaSeleccionada() )
f.paint( g );
f = getSigFicha( f );
}
getSeleccionada().paint( g );
contenido.paintAll( g );
}
TRANSPARENCIA
Vamos 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 dibujo
de 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 la
zona que queremos reproducir en pantalla, y utilizando los eventos del ratón, presentamos
una imagen u otra. Primero desarrollamos una clase para que controle el repintado de las
distintas imágenes, AppletFijo.java, que se van a ir presentando y que no se repinte la zona
total 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 como
botones 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 donde
creamos la clase principal, cargando la imagen de fondo y recuperando todos los parámetros
que se pasen en la llamada al applet.
//
// AppletFijo.java
// Copyright (c) 1996, Agustin Froufe
// Todos los derechos reservados.
//
import java.awt.*;
import java.applet.Applet;
public AppletFijo() {}
}
//
// 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.*;
// 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
//
// MenuDeBotones.java
// Copyright (c) 1996, Agustin Froufe
// Todos los derechos reservados.
//
import java.awt.*;
// 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" ) );
// 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 MenuDeBotones() {}
}
CALCULADORA
El applet Calculadora.java presenta una calculadora básica, tomando como herramienta de
construcción el AWT. Es una calculadora que realiza las operaciones más habituales, tipo
calculadora 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 colocan
en posiciones determinadas.
Las operaciones matemáticas no son motivo de estudio, pero sí se podr•a completar la
calculadora con más funciones y hacerla semejante a las que se proporcionan con casi
todos 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.
import java.awt.*;
import java.applet.Applet;
String cadDecimal;
c.init();
f.add( "Center",c );
f.pack();
f.resize( 395,181 );
f.show();
}
repaint();
}
// 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;
return( true );
}
}
CUENTA-KILOMETROS
El applet Contador.java presenta una imagen conocida de cuenta-kilómetros de coche, pero
cuyo código nos sirve para demostrar varias cosas. Podemos comprobar cómo se manipula
el offset de un dibujo porque la imagen de los números es un fichero gráfico que contiene los
10 dígitos posibles, y el contador va seleccionando cada uno de ellos en función de la cifra
que tiene que representar el cuanta-kilómetros. También podemos comprobar la utilización
de threads para la implementación del intervalo de tiempo que tarda el cuenta-kilómetros en
pasar 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 incial
en que arranca el contador. Además, como es un applet muy sencillo, lo hemos adornado
con 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
// 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,
POTENCIOMETRO
El applet EjPotenciometro.java presenta un ejemplo de uso de un Componente nuevo que
hemos implementado para incorporar a las posibilidades que ofrece el AWT. Se trata de una
Barra 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 el
applet ejemplo, hemos utilizado el Componente Grupo, que ya habíamos implementado el
capítulos anteriores.
import java.awt.*;
import java.applet.*;
//
// 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
import java.awt.*;
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 );
}
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 );
}
// 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();
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 );
}
static {
defFuente = new Font( "Dialog",0,16);
CARTEL
de Luis Angel Ortega
Este applet, Cartel.java, resenta un texto en una sola línea que se desplaza a derecha o
izquierda y es una contribución de un lector del Tutorial. Lo siguiente es suyo.
———————————————sic
Applet de libre distribución, puedes enviar tus sugerencias a Luis Angel Ortega para
compartir información y conocimientos sobre el lenguaje Java y Internet.
//*************************************************************************
// 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. //
// //
import java.applet.*;
import java.awt.*;
// Variables
//**********************************************************************//
// 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 //
//**********************************************************************//
try {
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;
}
}
//**********************************************************************//
void CogerParametros ()
{
try {
String Parametro=getParameter("VELOCIDAD");// Obtiene el parametro velocidad
try {
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;
}
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 //
// Establece el font
offGraphics.setFont (font);
//**********************************************************************//
// Metodo Cartel() //
// Constructor de la clase //
// //
// Entrada: Ninguna //
// //
public Cartel()
{
// Sin codigo
}
//**********************************************************************//
// Metodo getAppletInfo() //
// //
// Retorna los datos del applet, autor y sistema de desarrollo //
// //
// Entrada: Ninguna //
// //
// Salida: String //
//**********************************************************************//
//**********************************************************************//
// 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 //
//**********************************************************************//
//**********************************************************************//
// Metodo destroy() //
// //
// Este metodo es llamado cuando el applet es destruido de memoria //
// //
// Entrada: Ninguna //
// //
// Salida: Void //
//**********************************************************************//
//**********************************************************************//
// Metodo paint() //
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()
}
}
//**********************************************************************//
// Metodo run() //
// //
// Este metodo es llamado cuando el applet del Thread es llamado //
// //
// Entrada:Ninguna //
// //
// Salida: Void //
//**********************************************************************//
Espero que todo el esfuerzo que hemos realizado para que este Tutorial de Java pudiese ver la luz haya
servido para que, al menos, alguien de los que se hayan atrevido a seguirlo dominen un poco más el lenguaje
que representa Java.
Nosotros (yo) sí que he aprendido mucho durante estos meses que me he pasado escribiendo y desarrollando
ejemplos. 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 y
Windows ’95. El escoger los volcados de Windows ’95 para los ejemplos ha sido porque todo el Tutorial se ha
escrito en Word para Windows ’95.
Agradecimientos
Copyrights