Notas para el Curso de Java

Anuncio
Notas para el Curso de Java
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
inte
lec
tua
l
Are
nas
Notas para
el Curso de Introducción a la
Programación Orientada a
Objetos en el lenguaje
JAVA
Autor
Jose Abraham Arenas Barrios
Primera edición: Noviembre 2000
1
(c) 2000 by José Abraham Arenas Barrios
inte
lec
tua
l
Are
nas
Notas para el Curso de Java
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
Programación
Orientada a
Objetos
2
Notas para el Curso de Java
¿Qué es la programación orientada a objetos?
La Programación orientada a objetos es un método de implementación por el cual los programas son organizados
en colecciones de objetos que cooperan entre sí, dichos objetos, por sí mismos representan una instacia de alguna
clase siendo estas clases miembros de una jerarquía de clases unidas por medio de las relaciones de herecia.
inte
lec
tua
l
Are
nas
¿Qué es un Objeto?
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
Según la defnición que nos proporciona algún diccionario un objeto es algo tangible o visible de una forma
relativamente estable. Es algo que puede ser aprendido por medio del intelecto, es un ente abstracto el cual nos ayuda a
comprender de manera genérica un concepto del mundo que nos rodea. Pero para nuestros fines prácticos sugerimos la
siguiente definición:
Un objeto es un ente que tiene estado, comportamiento y una cierta identidad. Cada objeto se caracteriza por un
conjunto de operaciones que se pueden realizar sobre él y por un conjunto de operaciones que él puede realizar sobre
otros objetos, además por el conjunto de estados que le suceden durante su tiempo de actividad o vida.
El estado de un objeto consiste en una de las posibles condiciones en que un objeto puede existir, esto es, supongamos un objeto esférico como una pelota, sabemos que dicha pelota puede rodar sobre una superficie, puede estar
estática sobre tal superfície o puede estar botando sobre dicha superfície, por lo tanto cada una de estas condiciones
representa un estado. Estos estados que manifiesta el objeto son activados por medio de mensajes.
El comportamiento es la manera en que un objeto actúa y reacciona de acuerdo a sus cambios de estado y la
recepción de mensajes. Dicho de otro modo el comportamiento de un objeto es completamente definido por medio de
sus acciones. Un objeto puede actuar sobre otros objetos o inclusive sobre él mismo.
La identidad es la propiedad con la cual se puede distinguir un objeto (de la misma clase) de otros
objetos (de la misma clase). En nuestro caso un objeto sera identificable de otro a partir de su identificador
de nombre (como un nombre de variable tal y como lo conocemos).
Un objeto se dice que está en estado abstracto cuando está definido de una manera genérica, es decir, estamos
hablando únicamente de un concepto, por ejemplo cuando hablamos de un triángulo este no existe en la realidad ya que
solamente existen formas triangulares.
Un objeto está en estado concreto cuando se ha instanciado (ejemplificado) con un elemento que corresponde al
ámbito de este objeto, esto es, supongamos que tenemos el objeto manzana nosotros al invocar mentalmente al objeto
no podemos decir si la manzana es verde o roja puesto que esto depende de lo que cada persona se imagine del concepto manzana, de otra forma si decimos por ejemplo manzana golden roja entonces la disparidad de opiniones ya no
difiere tanto, y esto de debe a que hemos definido dos atributos del objeto.
Un atributo es una propiedad que posee el objeto y que en conjunto con algunas otras propiedades
definen al objeto y lo distinguen de otros.
La estructura y comportamiento de objetos similares están definidas en una clase común.
Los términos instancia de una clase y objeto de una clase son intercambiables.
¿Qué es una clase?
Una clase en un conjunto o colección de objetos que comparten estructura, comportamiento y características
comúnes y similares.
Tradicionalmente podemos definir una clase como un conglomerado, grupo o conjunto de elementos que comparten atributos comúnes o un atributo en común.
Un objeto no es una clase, curiosamente nosotros podemos describir una clase como un tipo particular de objeto.
Esto se realiza por medio de buscar, por así decirlo, objetos muy abstractos y muy genéricos,
Componentes de una clase
Una clase se compone básicamente de dos partes: datos y métodos que operan y trabajan de acuerdo a esos datos.
Un método es la operación que se puede efectuar sobre los campos de un objeto. Cuando se invoca el m etodo de
un objeto en particular se dice que se envía un mensaje al objeto a través del método (selector), es cuando el objeto
3
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
cambia su estado.
Los datos se consideran como los campos del objeto es decir, estos campos representan características bien
definidas de un objeto y son susceptibles de ser atributos, cantidades medibles o propiedades únicas inherentes a dicho
objeto.
inte
lec
tua
l
Are
nas
¿Qué es la encapsulación?
La encapsulación es mejor conocida bajo el nombre de ocultamiento de la información y es un recurso para poder
manejar la abstracción (que es una manera natural de manipular información, ya que esta es abstracta por naturaleza).
El proceso de ocultar todos los detalles de un objeto que no contribuyen a definir sus características esenciales es
otra dedefinición de encapsulación, típicamente la estructura de un objeto está oculta al igual que la implementación de
sus métodos. La encapsulación nos permite ver a los datos y las operaciones sobre estos como una sola unidad, también
oculta muchos detalles de la implementación de un objeto, es decir, nos guarda de entender la complejidad de un cierto
objeto y además nos proporciona un apoyo para la construcción de grandes sistemas de software ya que estos son
complejos y es necesario que posean una determinada integridad.
¿Qué es la herencia?
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
La herencia es una relación entre clases donde una clase comparte la estructura o comportamiento definida en
una o más de dos clases distintas, la herencia define un tipo de relación entre clases en donde las subclases heredan de
una o mas superclases. Típicamente una subclase aumenta o deroga la estructura y comportamiento de una superclase.
¿Qué es el polimorfismo?
Polimorfismo quiere decir «de muchas formas» en griego. Es la capacidad de un objeto de responder de manera
distinta a cosas distintas, es decir, el polimorfismo nos permite mandar el mismo mensaje a diferentes objetos y tener
que cada objeto responde de manera apropiada dependiendo de que tipo de objeto se trate.
Mitos y realidades de la P.O.O.
En el trancurso de las investigaciones en el campo de la POO se han producido algunas distorsiones de información y creando mitos en contra de la POO como lo siguiente.
!
La POO solo es para graficación. En parte es cierto, muchos paquetes de gráficas se basan en el modelo
orientado a objetos, pero no es lo único, existen aplicaciones de bases de datos, lenguajes de consulta, sistemas de
gestión, simulación, etc. y todas ellas basadas en la POO.
!
La POO es ineficiente. Si bien presenta problemas de velocidad y espacio en memoria, parámetros que
son dependientes de la implementación local de hardware esto se ha corregido a grandes pasos dadas las técnicas de
dispersión tablas hash para localizar los métodos de una manera mas eficiente con lo que la velocidad es incrementada.
Aparte el uso extensivo de herencia a demostrado acortar el tiempo de desarrollo de una aplicación en un 25%.
!
La POO es dificíl de aprender. La POO representa una manera más natural de representar el mundo en
una computadora, ya que se hace que la computadora de adapte al problema y el programador se enfoque mas en el
diseño, sin embargo la programación tradicional lleva mucho tiempo arraigada en la comunidad de programadores y
estos se resisten a aprender y considerar la nueva métodología que se les presenta.
Algunos campos de la aplicación de la P.O.O.
El modelo de objeto es aplicable a un amplio campo de problemas como los citados a continuación:
!
!
4
Control de tráfico aereo.
Animación.
Notas para el Curso de Java
inte
lec
tua
l
Are
nas
Procesamiento de datos de negocios.
Control de procesos químicos.
Diseño asistido por computadora (CAD)
Manufactura asistida por computador (CAM)
Bases de datos.
Sistemas expertos.
Reconocimiento de im agenes.
Análisis matemático.
Sistemas Operativos.
Robótica.
Telecomunicaciones.
Diseño de interfaces de usuario (GUI).
Desarrollo de ambientes de desarrollo de software.
Componentes reusables de software.
Automatización de oficinas.
Composición musical.
Ingeniería petrolera.
Software de satelites orbitales.
Sistemas de Telemetría.
Simulación de naves espaciales y de aviones.
Diseño de VLSI
Redes Neuronales.
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
Metodología de la P.O.O.
Defnimos la resolución de problemas mediante de la programación orienteada a objetos como el desarrollo de
una serie de pasos que consisten de enviar mensajes a objetos. El resultado de enviar un mensaje a un objeto produce
otro objeto.
Los pasos a desarrollar para llegar a una solución orientada al objeto son los siguientes:
1
2
3
4
Plantear el problema.
Identificar a los objetos que intervienen en la solución.
Identificar los mensajes a los cuales los objetos deberán responder.
Establecer una secuencia de mensajes a los objetos que lleven a la solución de dicho problema.
Por lo tanto, a manera de resumen:
!
Programar consiste en mandar mensajes a objetos.
!
El resultado de enviar un mensaje a un objeto es otro objeto.
5
(c) 2000 by José Abraham Arenas Barrios
inte
lec
tua
l
Are
nas
Notas para el Curso de Java
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
Introducción a
Java
El logo de Java, Duke y SUN Microsystems son propiedad de SUN Microsystems.
6
Notas para el Curso de Java
Origen de Java
S
inte
lec
tua
l
Are
nas
un 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
computadora 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 incompatibles, 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 finalmente encontró su nicho de mercado en la aldea global que es Internet; hasta
la más difundida, que justifica a Java como lenguaje de pequeños electrodomésticos.
Hace algunos años, Sun Microsystems decidió intentar introducirse en el mercado de la electrónica de consumo y
desarrollar programas para pequeños dispositivos electrónicos. Tras unos comienzos dudosos, Sun decidió crear una
filial, denominada FirstPerson Inc., para dar margen de maniobra al equipo responsable del proyecto.
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
El mercado inicialmente previsto para los programas de FirstPerson eran los equipos domésticos: microondas,
tostadoras y, fundamentalmente, televisión interactiva. Este mercado, 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 proliferaban 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.
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 aparició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 computadora
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 desarrollados 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
7
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
inte
lec
tua
l
Are
nas
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
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.
8
Notas para el Curso de Java
Caracteristicas del Lenguaje Java
inte
lec
tua
l
Are
nas
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 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 memoria, 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.
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
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 paradigma 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ámica de métodos. Esta
característica deriva del lenguaje Objective C, propietario del sistema operativo NeXT. En C++ se suele trabajar con
bibliotecas de enlace dinámico (DLL’s en inglés) 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:
Java se ha construido con extensas capacidades de interconexión TCP/IP. Existen librerías de rutinas para
acceder e interactuar con protocolos como http y ftp. Esto permite a los programadores acceder a la información a
través de la red con tanta facilidad como a los ficheros locales.
La verdad es que Java en sí no es distribuido, sino que proporciona las librerías y herramientas para que los
programas puedan ser distribuidos, es decir, que se corran en varias máquinas, interactuando.
Es ROBUSTO:
Java realiza verificaciones en busca de problemas tanto en tiempo de compilación como en tiempo de ejecución.
La comprobación de tipos en Java ayuda a detectar errores, lo antes posible, en el ciclo de desarrollo. Java obliga a la
declaración explícita de métodos, reduciendo así las posibilidades de error. Maneja la memoria para eliminar las
preocupaciones por parte del programador de la liberación o corrupción de memoria. También implementa los arrays
auténticos, en vez de listas enlazadas de punteros, con comprobación de límites, para evitar la posibilidad de
sobreescribir o corromper memoria resultado de punteros que señalan a zonas equivocadas. Estas características
reducen drásticamente el tiempo de desarrollo de aplicaciones en Java.
Además, para asegurar el funcionamiento de la aplicación, realiza una verificación de los byte-codes, que son el
resultado de la compilación de un programa Java. Es un código de máquina virtual que es interpretado por el intérprete
Java. No es el código máquina directamente entendible por el hardware, pero ya ha pasado todas las fases del
compilador: análisis de instrucciones, orden de operadores, etc., y ya tiene generada la pila de ejecución de órdenes.
9
Notas para el Curso de Java
Java proporciona, pues:
!
Comprobación de referencias (apuntadores)
!
Comprobación de límites de arrays
!
Excepciones
!
Verificación de byte-codes
(c) 2000 by José Abraham Arenas Barrios
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
inte
lec
tua
l
Are
nas
Es de ARQUITECTURA NEUTRAL:
Para establecer Java como parte integral de la red, el compilador Java compila su código a un archivo 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. Actualmente 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.
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ética 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 Java (JVM) y las
librerías fundamentales, que también nos permitirían acceder directamente al hardware de la máquina. Además, habrá
10
inte
lec
tua
l
Are
nas
Notas para el Curso de Java
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 solo algunas:
!
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
!
Java API TV: incorpora transmisión de video tipo TV interactiva.
!
Java SWING: es una GUI mas robusta con mayor alcance que AWT y capaz de emular otras GUI’s
!
JDBC: ofrece conectivadad de Java con servidores de bases de datos, parecido a lo que ofrece ODBC.
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
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 aritmé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 enteros 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 sabemos 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
!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 espacio de nombres del
sistema de archivos 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.
Las clases importadas de la red se almacenan en un espacio de nombres privado, asociado con el origen. Cuando
una clase del espacio de nombres privado accede a otra clase, primero se busca en las clases predefinidas (del sistema
local) y luego en el espacio de nombres de la clase que hace la referencia. Esto imposibilita que una clase suplante a
una predefinida.
En resumen, las aplicaciones de Java resultan extremadamente seguras, ya que no acceden a zonas delicadas de
memoria o de sistema, con lo cual evitan la interacción de ciertos virus. Java no posee una semántica específica para
modificar la pila de programa, la memoria libre o utilizar objetos y métodos de un programa sin los privilegios del
kernel del sistema operativo. Además, para evitar modificaciones por parte de los crackers de la red, implementa un
11
inte
lec
tua
l
Are
nas
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
método ultraseguro de autentificación por clave pública. El Cargador de Clases puede verificar una firma digital antes
de realizar una instancia de un objeto. Por tanto, ningún objeto se crea y almacena en memoria, sin que se validen los
privilegios de acceso. Es decir, la seguridad se integra en el momento de compilación, con el nivel de detalle y de
privilegio que sea necesario.
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 archivo de la máquina local (siempre que se realizan
operaciones con archivos, éstas trabajan sobre 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 puente, es decir,
nadie puede utilizar nuestra máquina para hacer peticiones o realizar operaciones 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 computadora 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 considerar que Java es un lenguaje seguro y
que los applets están libres de virus.
Respecto a la seguridad del código fuente, no ya del lenguaje, JDK proporciona un desemsamblador de bytecode, que permite que cualquier programa pueda ser convertido a código fuente, lo que para el programador significa
una vulnerabilidad total a su código. Utilizando javap no se obtiene el código fuente original, pero sí desmonta el
programa mostrando el algoritmo que se utiliza, que es lo realmente interesante. La protección de los programadores
ante esto es utilizar llamadas a programas nativos, externos (incluso en C o C++) de forma que no sea descompilable
todo el código; aunque así se pierda portabilidad. Esta es otra de las cuestiones que Java tiene pendientes.
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
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 enteros 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 (el sistema run-time) puede ejecutar directamente el código objeto. Enlazar (ligar) 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 a que el compilador termine de resolver los llamados y ligados. 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. Sin embargo, ya se pueden conseguir
programas que traducen los byte-codes de un archivo .class de Java a un archivo con los equivalentes binarios de una
plataforma local. Por ejemplo, la última versión del Visual Java Café de Symantec produce código nativo para
procesadores Intel.
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 comparaciones 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.
Aunque Java es interpretado, SUN le incorporó en las versiones 1.1.x un compilador Just-In-Time, este
compilador genera código nativo cuando es posible, resultando en una mejora significativa del desempeño de un
programa en Java. Poco después SUN anunció la aparición del «HOTSPOT» una idea revolucionaria que cambiaría por
completo la lentitud del desempeño de los programas Java. HotSpot se distribuye aparte del JDK o bien se instala junto
con la versión 1.3 de Java.
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 aplicació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
12
Notas para el Curso de Java
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 operativo utilizado.
inte
lec
tua
l
Are
nas
Es MULTITHREADED:
Al ser multithreaded (multihilvanado, en mala traducción), Java permite muchas actividades simultáneas en un
programa. Los threads (a veces llamados, procesos ligeros), son básicamente pequeños procesos o piezas independientes de un gran proceso. Al estar los threads contruidos en el lenguaje, son más fáciles de usar y más robustos que sus
homólogos en C o C++.
El beneficio de ser miltithreaded consiste en un mejor rendimiento interactivo y mejor comportamiento 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.
Cualquiera que haya utilizado la tecnología de navegación concurrente, sabe lo frustrante que puede ser esperar
por una gran imagen que se está trayendo. En Java, las imágenes se pueden ir trayendo en un thread independiente,
permitiendo que el usuario pueda acceder a la información en la página sin tener que esperar por el navegador.
Es DINAMICO:
Java se beneficia todo lo posible de la tecnología orientada a objetos. Java no intenta conectar 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 mantengan el API anterior).
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
Java también simplifica el uso de protocolos nuevos o actualizados. Si su sistema ejecuta una aplicación Java
sobre la red y encuentra una pieza de la aplicación que no sabe manejar, tal como se ha explicado en párrafos anteriores, Java es capaz de traer automáticamente cualquiera de esas piezas que el sistema necesita para funcionar.
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 persistencia, para que no se eliminen cuando de limpie la
memoria caché de la máquina.
13
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
Java para aplicaciones Empresariales
inte
lec
tua
l
Are
nas
Java actualmente está en boca de todos, Java e Intranet son las palabras de moda. Pero, surge la pregunta de si
esta es una buena tecnología para desarrollar aplicaciones corporativas. Y la respuesta es afirmativa y voy a proponer
argumentos para esa afirmación. En donde la red sea algo crítico, Java facilita tremendamente la vida de la programación corporativa.
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.
Si esta red no se mantiene sana, los pedidos no llegan, el inventario no se actualiza, el software no se desarrolla
adecuadamente, los clientes no están satisfechos y, fundamentalmente, el dinero no entra. La necesidad de diagnosticar
y reducir la arterioesclerosis de la red, hace que se estén inyectando continuamente nuevas metodologías que subsanen
este grave problema.
¿Es Java la medicina? Está claro que cuando vemos un cepillo animado limpiando los dientes, cubos moviéndose
en 3-D, o una banda de gatos locos en applets de Java, nos convencemos 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.
Desarrollo rápido de aplicaciones (RAD)
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
Hace años, se decía que los programadores pronto desaparecerían. Los generadores automá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 acelerada 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 "generar 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 VisualCafé de Symantec, y pronto, herramientas más sofisticadas como Netcode o FutureTense o el
actualmente disponible Forté for Java de Netobjets (que ya es de Sun). Esto proporciona una gran progresión a los
entornos de desarrollo Java.
Aplicaciones efectivas y eficientes
Las aplicaciones que se crean en grandes empresas deben ser más efectivas que eficientes; es decir, conseguir que
el programa funcione y el trabajo salga adelante es más importante que el que lo haga eficientemente. Esto no es una
crítica, es una realidad de la programación corporativa. Al ser un lenguaje más simple que cualquiera de los que ahora
están en el cajón de los programadores, Java permite a éstos concentrarse en la mecánica de la aplicación, en vez de
pasarse horas y horas incorporando APIs para el control de las ventanas, controlando minuciosamente la memoria,
sincronizando los ficheros de cabecera y corrigiendo los agónicos mensajes del linker. Java tiene su propio toolkit para
interfaces, maneja por sí mismo la memoria que utilice la aplicación, no permite ficheros de cabecera separados (en
aplicaciones puramente Java) y solamente usa enlace dinámico.
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 computadora del usuario. Aunque ya hay ciertos
proveedores que ofrecen compiladores nativos 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 computadora del usuario. Los programas Java en ese momento rivalizarán con el rendimiento 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 eficiencia de su aplicación,
empiezan a hacerlo demasiado pronto en el ciclo de vida de la aplicació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 clase Thread y mecanismos muy sencillos de usar de sincronización; Java en sí utiliza threads. Los
14
inte
lec
tua
l
Are
nas
Notas para el Curso de Java
desarrolladores de compiladores inteligentes pueden utilizar esta característica 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).
Si un compilador JIT está embebido en el entorno run-time de Java, el programador no se preocupa de hacer que
la aplicación se ejecute óptimamente. Siempre he pensado que en los Sistemas Operativos tendría que aplicarse esta
filosofía; un optimizador progresivo es un paso más hacia esta idea.
Portabilidad para programador y programa
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
En una empresa de relativo tamaño hay un conjunto heterogéneo de computadoras. Probablemente nos encontremos con estaciones de trabajo Sun para el desarrollo de software, hordas de PC’s para los empleados, alguna Mac en el
departamento de documentacióny diseño gráfico, una estación de trabajo HP en administración y una estación SGI en
la sala de demos y presentaciones. Desarrollar aplicaciones corporativas para un grupo tan diferente de plataformas
resulta excesivamente complejo y caro. Hasta ahora era complicado convencer a los programadores de cada arquitectura que utilizasen un API común para reducir el coste de las aplicaciones.
Con un entorno run-time de Java portado a cada una de las arquitecturas de las plataformas presentes en la
empresa y una buena biblioteca de clases ("packages" en Java), los programadores 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, programadores
trabajando sobre Visual Basic en el departamento de documentación y programadores sobre GNU en proyectos especiales; ahora todos ellos podrán estar juntos y formar el grupo de software de la empresa.
Costos 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 problema en muchas
empresas. La eficiencia del hardware y el poco coste de mantenimiento 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 herramientas 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 proporcionan el código fuente, los programadores corporativos tienen un amplio campo donde moverse y
muchas oportunidades de aprender y muchos recursos a su disposición.
El éxito que Internet ha proporcionado a los equipos de software corporativos es un regalo. El precio del software
es ahora el mismo para un poderoso equipo corriendo Unix que para un PC. Incluso Netscape tiene al mismo precio la
versión Unix de su servidor Web SuiteSpot que la versión PC/NT. Esta es la filosofía de precios que parece ser será la
que se siga con las herramientas basadas en Java.
Mantenimiento y soporte
Un problema bien conocido que ocurre con el software corporativo es la demanda de cuidados y realimentación.
Java no es, ciertamente, la cura para la enfermedad del mantenimiento, 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. Leerse un makefile es
como estar leyendo la historia de empresa. Normalmente se pasan de programador a programador, quitando la informa15
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
ción que no es esencial, siempre que se puede. Esto hace que muchos de los makefiles de las aplicaciones contengan
docenas de bibliotecas, una miríada de archivos de encabezados y macros confusos de leer.
Java reduce las dependencia de complejos makefiles drásticamente. Primero, no hay archivos de encabezados de
declaraciones. Java necesita que todo el código fuente de una clase se encuentre en un solo archivo. Java tiene la
inteligencia de make en el propio lenguaje para simplificar la compilación de byte-codes. Por ejemplo:
// Archivo: Nodo.java
public class Conexion {
}
// Archivo: Conexion.java
inte
lec
tua
l
Are
nas
public class Nodo {
Conexion liga ;
}
% javac -verbose Nodo.java
[parsed Nodo.java in 720ms]
[loaded C:\JAVA\BIN\..\classes\java\lang\Object.class in 220ms]
[checking class pepe]
[parsed .\\Conexion.java in 50ms]
[wrote Nodo.class]
[checking class Conexion]
[wrote .\\Conexion.class]
[done in 2300ms]
El compilador Java se da cuenta de que necesita compilar el archivo Conexion.java. Ahora vamos a forzarlo a
que recompile Nodo.java sin cambiar Conexion.java, podremos comprobar que el compilador de byte-code Java no
recompila innecesariamente el archivo Conexion.java.
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
% javac -verbose Nodo.java
[parsed Nodo.java in 440ms]
[loaded C:\JAVA\BIN\..\classes\java\lang\Object.class in 160ms]
[checking class Nodo]
[loaded .\\Conexion.java in 0ms]
[wrote Nodo.class]
[done in 1860ms]
Ahora, si modificamos Conexion.java (añadiendo, por ejemplo, otro miembro a la clase) y compilamos
Nodo.java, el compilador Java se dará cuenta de que debe recompilar tanto Nodo.java como Conexion.java
% javac -verbose Nodo.java
[parsed Nodo.java in 710ms]
[loaded C:\JAVA\BIN\..\classes\java\lang\Object.class in 220ms]
[checking class Nodo]
[parsed .\\Conexion.java in 0ms]
[wrote Nodo.class]
[checking class Conexion]
[wrote .\\Conexion.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 bibliotecas de gráficos, 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í es que no
es cierto es que haya que aprender primero C++ antes de aprender Java.
Si los ingenieros de la empresa no conocen ningún lenguaje orientado a objetos, sí que tienen que aprender los
fundamentos de esta tecnología antes de nada, y luego aplicarlos a la programación con Java. El análisis y diseño
orientado a objetos debe ser comprendido antes de intentar nada con Java. Los programadores de Java sin un fondo de
conocimientos de OOA/D producirán código pobre. Además, los libros sobre Java crecen como la espuma, ya hay más
de 50 publicados, y si buscas "Programming in Java" en la Red, encontrarás mas de 500 Web sites, y 60 más dedicados
a "Learning Java", y el número crece dia a dia. Y si esto, evidentemente, no es el sustituto de un instructor humano,
hay ya varias empresas que ofrecen enseñanza de Java, entre ellas, Sun.
16
Notas para el Curso de Java
Resumen
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
inte
lec
tua
l
Are
nas
En base a los argumentos que acabamos de exponer, ¿podría una empresa utilizar Java para sus aplicaciones
críticas? En este instante, sería suficiente un acercamiento a Java. Porque más importante que la elección de Java o
cualquier otro lenguaje de programación es un buen diseño de la arquitectura de la aplicación. Diseñar la aplicación
para que esté distribuida entre servidores y clientes, y la línea de partida debe ser el diseño modular.
Algunas sugerencias para adoptar Java como tecnología corporativa, serían:
Usar Java en el desarrollo de la interface del cliente; Java es suficientemente estable para desarrollar una
interface portable. Utilizar herramientas de programación más estables en los servidores, porque son la parte crítica.
Portar o crear un servidor no-crítico en Java, de forma que tanto cliente como servidor estén escritos en Java.
Utilizar Java en proyectos de envergadura tanto en el cliente como en el servidor, para valorar la efectividad de
Java.
Intranet está creciendo actualmente más rápido que Internet. Las organizaciones corporativas 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.
17
(c) 2000 by José Abraham Arenas Barrios
inte
lec
tua
l
Are
nas
Notas para el Curso de Java
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
Elementos del
lenguaje Java
18
Notas para el Curso de Java
Programando 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:
inte
lec
tua
l
Are
nas
// 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.
En este tipo de comentario para documentación, se permite la introducción de algunos tokens o palabras clave,
que harán que la información que les sigue aparezca de forma diferente al resto en la documentación.
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
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.
Serían identificadores válidos:
identificador
nombre_usuario
Nombre_Usuario
_variable_del_sistema
$transaccion
y su uso sería, por ejemplo:
int contador_principal;
char _lista_de_ficheros;
float $cantidad_en_Pesos;
Palabras clave
Las siguientes son las palabras clave que están definidas en Java y que no se pueden utilizar como
indentificadores:
abstract
boolean
break
byte
byvalue
case
catch
char
class
const
continue
default
do
double
else
extends
false
final
finally
float
for
goto
if
implements
import
instanceof
int
interface
long
native
new
null
package
private
protected
public
return
short
static
super
switch
synchronized
this
threadsafe
throw
transient
true
try
void
while
Palabras Reservadas
Además, el lenguaje se reserva unas cuantas palabras más, pero que hasta ahora no tienen un cometido específico. Son:
cast
operator
future
outer
generic
rest
inner
var
19
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
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 cadenas, que se pueden poner en cualquier lugar del
código fuente de Java. Cada uno de estos literales tiene un tipo correspondiente asociado con él.
Enteros:
Reales en coma flotante:
float
double
Por ejemplo:
Booleanos:
true
false
Caracteres:
Por ejemplo:
8 bits
16 bits
32 bits
64 bits
21
077
complemento
complemento
complemento
complemento
a dos
a dos
a dos
a dos
inte
lec
tua
l
Are
nas
byte
short
int
long
Por ejemplo:
0xDC00
32 bits
64 bits
3.14
2e12
3.1E12
a
\u????
\t
IEEE 754
IEEE 754
[????] es un número unicode
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
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 desbordamientos 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 cualquier 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[] = {
"Janeth","Blanca","Sandra","Sharon","Marielle","Lorena","Wendy"
};
Esto que es equivalente a:
String nombres[];
nombres = new String[7];
nombres[0] = new String(
nombres[1] = new String(
nombres[2] = new String(
nombres[3] = new String(
nombres[4] = new String(
nombres[5] = new String(
nombres[6] = new String(
"Janeth" );
"Blanca" );
"Sandra" );
"Marielle" );
"Sharon" );
"Lorena" );
"Wendy" );
No se pueden crear arrays estáticos en tiempo de compilación:
20
Notas para el Curso de Java
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; // provocara una excepcion en tiempo de ejecucion
Operadores
inte
lec
tua
l
Are
nas
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.
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:
.
++
!
*
+
<<
<
&
&&
? :
=
[]
-~
/
>>
>
^
||
()
op=
(*=
instanceof
%
>>>
<=
|
>=
/=
==
%=
!=
+=
-=
etc.)
,
Los operadores numéricos se comportan como esperamos:
int + int = int
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
Los operadores relacionales devuelven un valor booleano.
Para las cadenas, se pueden utilizar los operadores relacionales para comparaciones ademá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.
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:
() - paréntesis. Para contener listas de parámetros en la definición y llamada a métodos. También se utiliza para
definir precedencia en expresiones, contener expresiones para control de flujo y rodear las conversiones de tipo.
{} - llaves. Para contener los valores de matrices inicializadas automáticamente. También se utiliza para definir
un bloque de código, para clases, métodos y ámbitos locales.
[] - corchetes. Para declarar tipos matriz. También se utiliza cuando se referencian valores de matriz.
; - punto y coma. Separa sentencias.
, - coma. Separa identificadores consecutivos en una declaración de variables. También se utiliza para encadenar
sentencias dentro de una sentencia for.
. - punto. Para separar nombres de paquete de subpaquetes y clases. También se utiliza para separar una variable
o método de una variable de referencia.
21
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
CONTROL DE FLUJO
Muchas de las sentencias de control del flujo del programa se han tomado del C:
if/else
if( Boolean ) {
sentencias;
}
else {
sentencias;
}
inte
lec
tua
l
Are
nas
Sentencias de Bifurcación
switch
switch( expr1 ) {
case expr2:
sentencias;
break;
case expr3:
sentencias;
break;
default:
sentencias;
break;
}
Sentencias de ciclo o repetición condicional e incondicional
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
ciclos 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 colores 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 ciclos for
for( a=0,b=0; a < 7; a++,b+=2 )
Ciclos while
while( Boolean ) {
sentencias;
}
Ciclos do/while
do {
sentencias;
} while( Boolean );
Excepciones
try-catch-throw
try {
sentencias;
22
Notas para el Curso de Java
} catch( Exception ) { sentencias; }
Java implementa excepciones para facilitar la construcción de código robusto. Cuando ocurre 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.
Control General del Flujo
inte
lec
tua
l
Are
nas
break [etiqueta]
continue [etiqueta]
return expr;
etiqueta: sentencia;
En caso de que nos encontremos con ciclos anidados, se permite el uso de etiquetas para poder salirse de ellos,
por ejemplo:
uno: for( )
{
dos: for( )
{
continue;
continue uno;
break uno;
}
}
// seguiría en el bucle interno
// seguiría en el bucle principal
// se saldría del bucle principal
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
En el código de una función siempre hay que ser consecuentes con la declaración que se haya hecho de ella. Por
ejemplo, si se declara una función para que devuelva un entero, es imprescindible que se coloque un return final para
salir de esa función, independientemente de que haya otros en medio del código que también provoquen la salida de la
función. En caso de no hacerlo se generará un Warning, y el código Java no se puede compilar con Warnings.
int func()
{
if( a == 0 )
return 1;
return 0;
// es imprescindible porque se retorna un entero
}
23
Notas para el Curso de Java
(c) 2000 by José Abraham Arenas Barrios
Clases
Tipos de Clases
inte
lec
tua
l
Are
nas
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 funciones 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 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 archivo, fuera del bloque de la clase. Sin embargo, el compilador reemplazará esa sentencia con el
contenido del archivo que se indique, que consistirá, como es de suponer, en más clases.
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
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.
24
Notas para el Curso de Java
Variables y Métodos de Instancia
Una clase en Java puede contener variables y métodos. Las variables pueden ser tipos primitivos como int, char,
etc. Los métodos son funciones.
Por ejemplo, en el siguiente trozo de código podemos observarlo:
inte
lec
tua
l
Are
nas
public MiClase {
int i;
public MiClase() {
i = 10;
}
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 ).
Ambito de una variable
Los bloques de sentencias compuestas en Java se delimitan con dos llaves. Las variables de Java sólo son válidas
desde el punto donde están declaradas hasta el final de la sentencia compuesta que la engloba. Se pueden anidar estas
sentencias compuestas, y cada una puede contener su propio conjunto de declaraciones de variables locales. Sin
embargo, no se puede declarar una variable con el mismo nombre que una de ámbito exterior.
El siguiente ejemplo intenta declarar dos variables separadas con el mismo nombre. En C y C++ son distintas,
porque están declaradas dentro de ámbitos diferentes. En Java, esto es ilegal.
// ámbito exterior
// crea un nuevo ámbito
// error de compilación
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
Class Ambito {
int i = 1;
{
int i = 2;
}
}
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 opcionales 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 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();
}
25
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
Alcance y ámbito de Objetos y Reciclaje de Memoria
Los objetos tienen un tiempo de vida y consumen recursos durante el mismo. Cuando un objeto no se va a
utilizar más, debería liberar el espacio que ocupaba en la memoria de forma que las aplicaciones no la agoten (especialmente las grandes).
En Java, la recolección y liberación de memoria es responsabilidad de un thread llamado automatic garbage
collector (recolector automático de basura). Este thread monitoriza el alcance de los objetos y marca los objetos que se
han salido de alcance. Veamos un ejemplo:
//
//
//
//
no se ha asignado todavia
memoria asignada
se ha asignado nueva memoria
(nuevo objeto)
inte
lec
tua
l
Are
nas
String s;
s = new String( "abc" );
s = "def";
Más adelante veremos en detalle la clase String, pero una breve descripción de lo que hace esto es; crear un
objeto String y rellenarlo con los caracteres "abc" y crear otro (nuevo) String y colocarle los caracteres "def".
En esencia se crean dos objetos:
Objeto String "abc"
Objeto String "def"
Al final de la tercera sentencia, el primer objeto creado de nombre s que contiene "abc" se ha salido de alcance.
No hay forma de acceder a él. Ahora se tiene un nuevo objeto llamado s y contiene "def". Es marcado y eliminado en
la siguiente iteración del thread reciclador de memoria.
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.
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
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.
26
Notas para el Curso de Java
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
inte
lec
tua
l
Are
nas
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.
friendly (sin declaración específica)
void MetodoDeMiPaquete(){}
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
Por defecto, si no se especifica el control de acceso, las variables y métodos de instancia se declaran friendly
(amigas), lo que significa que son accesibles por todos los objetos dentro del mismo paquete, pero no por los externos
al paquete. Es lo mismo que protected.
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
acceder a las variables y métodos protegidos de las clases derivadas.
Variables y Métodos Estáticos
En un momento determinado se puede querer crear una clase en la que el valor de una variable de instancia sea el
mismo (y de hecho sea la misma variable) para todos los objetos instanciados a partir de esa clase. Es decir, que exista
una única copia de la variable de instancia. Se usará para ello la palabra clave static.
class Documento extends Pagina {
static int version = 10;
}
El valor de la variable version será el mismo para cualquier objeto instanciado de la clase Documento. Siempre
que un objeto instanciado de Documento cambie la variable version, ésta cambiará para todos los objetos.
De la misma forma se puede declarar un método como estático, lo que evita que el mé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
}
}
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 memoria. Las funciones estáticas se usan para acceder
solamente a variables estáticas.
class UnaClase {
int var;
UnaClase()
{
var = 5;
}
27
Notas para el Curso de Java
(c) 2000 by José Abraham Arenas Barrios
UnaFuncion()
{
var += 5;
}
}
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
inte
lec
tua
l
Are
nas
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 apuntador 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.
28
Notas para el Curso de Java
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 constructor de la forma siguiente:
inte
lec
tua
l
Are
nas
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:
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
import MiClase;
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();
mnc.Suma_a_i( 10 );
Clases Abstractas
Una de las características más útiles de cualquier lenguaje orientado a objetos es la posibilidad 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 obstante, no todos los métodos
de una clase abstracta tienen que ser abstractos. Las clases abstractas no pueden tener métodos privados (no se podrían
implementar) ni tampoco estáticos. 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 abstracta.
29
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
Interfaces
inte
lec
tua
l
Are
nas
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étodos 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.
La principal diferencia entre interface y abstract es que un interface proporciona un mecanismo de encapsulación
de los protocolos de los métodos sin forzar al usuario a utilizar la herencia.
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:
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
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 programació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>
}
void stop() {
<código nuevo>
}
30
Notas para el Curso de Java
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.
inte
lec
tua
l
Are
nas
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 integrar el código C o C++:
1:
Utilizar javah para crear un fichero de cabecera (.h)
2:
Utilizar javah para crear un fichero de stubs, es decir, que contiene la declaración de las funciones
3:
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
4:
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)
5:
Ejecutar la aplicación con el appletviewer
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
Paquetes (packages)
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 archivos 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;
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
El paquete de entrada/salida contiene las clases de acceso a ficheros: FileInputStream y FileOutputStream.
java.lang
Este paquete incluye las clases del lenguaje Java propiamente dicho: Object, Thread, Exception, System, Integer,
Float, Math, String, etc.
31
Referencias
inte
lec
tua
l
Are
nas
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
java.net
Este paquete da soporte a las conexiones del protocolo TCP/IP y, además, incluye las clases Socket, URL y
URLConnection.
java.util
Este paquete es una miscelánea de clases útiles para muchas cosas en programación. Se incluyen, entre otras,
Date (fecha), Dictionary (diccionario), Random (números aleatorios) y Stack (pila FIFO).
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
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, primitivos 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.
Otra diferencia fundamental entre un apuntador y una referencia es que con un apuntador puede existir
aritmetica, mientras que con una referencia no es posible.
APUNTADORES Y REFERENCIAS C++
Apuntadores
C y C++ permiten la declaración y uso de apuntadores, 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.
La intención principal en el uso de los apuntadores es comunicarnos más directamente con el hardware, haciendo
que el código se acelere. Desafortunadamente, este modelo de tan bajo nivel hace que perdamos robustez y seguridad
en la programación y hace muy difíciles tareas como la liberación automática de memoria, la defragmentación de
memoria, o realizar programación distribuida de forma clara y eficiente.
Referencias en C++
Las referencias se incorporaron a C++ en un intento de manejar apuntadores de C de forma más limpia y segura.
Sin embargo, como no elimina los apuntadores, 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 posteriormente. Esto permite
incrementar la eficiencia en tiempo de ejecución sobre la solución basada en apuntadores, pero es más por las deficiencias de los apuntadores que por las ventajas de las referencias.
32
Notas para el Curso de Java
Referencias en Java
inte
lec
tua
l
Are
nas
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 referencias 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.
Pensemos en una referencia como si se tratase de la llave electrónica de la habitación de un hotel. Vamos a
utilizar precisamente este ejemplo del Hotel para demostrar el uso y la utilización que podemos hacer de las referencias
en Java. Primero crearemos la clase Habitacion, implementada en el fichero Habitacion.java, mediante instancias de la
cual construiremos nuestro Hotel:
public class Habitacion {
private int numHabitacion;
private int numCamas;
public Habitacion() {
habitacion( 0 );
}
public Habitacion( int numeroHab ) {
habitacion( numeroHab );
}
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
public Habitacion( int numeroHab,int camas ) {
habitacion( numeroHab );
camas( camas );
}
public synchornized int habitacion() {
return( numHabitacion );
}
public synchronized void habitacion( int numeroHab ) {
numHabitacion = numeroHab;
}
public synchronized int camas() {
return( camas );
}
public syncronized void camas( int camas ) {
numCamas = camas;
}
}
El código anterior sería el corazón de la aplicación. Vamos pues a construir nuestro Hotel creando Habitaciones y
asignándole a cada una de ellas su llave electrónica; tal como muestra el código siguiente, Hotel1.java:
public class Hotel1 {
public static void main( String args[] ) {
Habitacion llaveHab1;
Habitacion llaveHab2;
llaveHab1 = new Habitacion( 222 );
llaveHab2 = new Habitacion( 1144,3 );
^^^^^^^^^
^^^^^^^^^^^^^^ ^^^^^^
A
B y D
C
}
//
//
// paso 1
// pasos 2, 3, 4 y 5
}
Para explicar el proceso, dividimos las acciones en los cinco pasos necesarios para poder entrar en nuestra
habitación. Aunque no se incluye, podemos también considerar el caso de que necesitemos un cerrajero, para que
cuando perdamos la llave, nos abra la puerta; y que en nuestro caso sería el garbage collector, que recicla la habitación
33
inte
lec
tua
l
Are
nas
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
una vez que se hayan perdido todas las llaves.
El primer paso es la creación de la llave, es decir, definir la variable referencia, por defecto 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 determinado, 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.
Podemos tener múltiples llaves para una misma habitación:
...
Habitacion llaveHab3,llaveHab4;
llaveHab3 = llaveHab1;
llaveHab4 = llavehab2;
De este modo conseguimos copias de las llaves. Las habitaciones en sí mismas no se han tocado en este proceso.
Así que, ya tenemos dos llaves para la habitación 222 y otras dos para la habitación 1144.
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;
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
Ahora tenemos una llave para la habitación 222 y tres para la habitación 1144. Mantendremos 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 camas 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 realizado:
. . .
llaveHab4.printData();
34
Notas para el Curso de Java
Referencias y arreglos
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 ciudadanos de primera clase.
Vamos a expandir nuestro hotel creando todo un ala de habitaciones, Hotel2.java. Crearemos un juego de llaves
maestras y luego construiremos las habitaciones:
public static void main( String args[] ) {
Habitacion llavesMaestras[];
// paso 1
llavesMaestras = new Habitacion[ habPorAla ];
// pasos 2-5
^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^
A
B, C, D y E
int numPiso = 1;
for( int i=0; i < habPorAla; i++ )
// pasos 6-9
llavesMaestras[ i ] = new Habitacion( numPiso * 100 + i,
( 0 == (i%2)) ? 2 : 1 );
for( int i=0; i < habPorAla; i++ )
// pasos 10-11
llavesMaestras[i].printData();
}
}
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
//
//
inte
lec
tua
l
Are
nas
public class Hotel2 {
// Número de habitaciones por ala
public static final int habPorAla = 12;
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 especifica 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. También todas las habitaciones de número par tienen una sola cama, mientras que
las habitaciones impares tendrán dos camas.
Los pasos 10 y 11 nos permiten obtener información de cada una de las habitaciones.
35
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
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í piense 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:
inte
lec
tua
l
Are
nas
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 referencia 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.
La lista enlazada, ListaEnlazada.java, contiene un nodo principio-fin y un contador para el número de nodos en
la lista:
public class ListaEnlazada {
public NodoListaEnlazada PrincipioFin;
private int numNodos;
// . . .
}
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
El nodo especial PrincipioFin es sencillo, para simplificar el código. El contador se usa para optimizar los casos
más habituales.
Revisemos pues el código de nuestro Hotel, ahora Hotel3.java, que será prácticamente el mismo que en el caso
de los arrays:
public class Hotel3 {
// Número de habitaciones por ala
public static final int habPorAla = 12;
public static void main( String args[] ) {
ListaEnlazada llaveMaestra;
llaveMaestra = new ListaEnlazada();
// paso 1
// pasos 2-5
int numPiso = 1;
for( int i=0; i < habPorAla; i++ )
// pasos 6-9
llaveMaestra.insertAt( i,
new Habitacion( numPiso * 100 + i,
( 0 == (i%2)) ? 2 : 1 );
for( int i=0; i < habPorAla; i++ )
// pasos 10-12
( (Habitacion)llaveMaestra.getAt(i) ).printData();
}
}
El paso 1 es la llave maestra de la lista. Está representada por una lista genérica; es decir, una lista de llaves que
cumple la convención que nosotros hemos establecido. Podrí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.
La ListaEnlazada proporciona el método insertAt() que coge el índice en la lista, donde el nuevo nodo ha de ser
insertado, como primer argumento. El segundo argumento es el objeto que será almacenado en la lista. Obsérvese que
no es necesario colocar moldeo alguno para hacer algo a una clase descendiente que depende de uno de sus padres.
Los pasos 10 a 12 provocan la misma salida que los pasos 10 y 11 del ejemplo con arrays. El paso 10 coge la
llave del juego que se indica en el método getAt(). En este momento, 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 haciendo un moldeado a la llave de la habitación (este casting generará un chequeo en tiempo de ejecución por
36
Notas para el Curso de Java
el compilador, para asegurarse de que se trata de una Habitacion). El paso 12 usa la llave para imprimir la información.
Apuntadores de C/C++ vs. Referencias en Java
Ahora que ya sabemos un poco más sobre las referencias en Java, vamos a compararlas con los punteros de C y
C++.
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
inte
lec
tua
l
Are
nas
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 liberació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 prestaciones sea mucho más simple.
C y C++ permiten el uso de apuntadores 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 tendencias autodestructivas.
37
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
Una aplicación mínima en Java
La aplicación más pequeña posible es la que simplemente imprimir un mensaje en la pantalla. Tradicionalmente,
el mensaje suele ser "Hola Mundo!". Esto es justamente lo que hace el siguiente fragmento de código:
inte
lec
tua
l
Are
nas
//
// Aplicación HolaMundo de ejemplo
//
class HolaMundoApp {
public static void main( String args[] ) {
System.out.println( "Hola Mundo!" ) ;
}
}
HolaMundo
Vamos ver en detalle la aplicación anterior, línea a línea. Esas líneas de código 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[] ) {
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
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 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érprete 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étodos.
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.
38
Notas para el Curso de Java
Compilación y Ejecución de una aplicación Java
inte
lec
tua
l
Are
nas
Vamos a ver a continuación como podemos ver el resultado de nuestra primera aplicación Java en pantalla.
Generaremos un archivo con el código fuente de la aplicación, lo compilaremos y utilizaremos el intérprete java para
ejecutarlo.
Archivos Fuente Java
Los archivos fuente en Java terminan con la extensión ".java". Crear un archivo utilizando cualquier editor de
texto ascii que tenga como contenido el código de las ocho líneas de nuestra mínima aplicación, y salvarlo en un
archivo con el nombre de HolaMundoApp.java. Para crear los Archivos con código fuente Java no es necesario un
procesador de textos, aunque puede utilizarse siempre que tenga salida a archivo de texto plano o ascii, sino que es
suficiente con cualquier otro editor.
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 archivo fuente HolaMundoApp.java, se puede compilar con la línea siguiente:
c:\javac HolaMundoApp.java
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
Si no se han cometido errores al teclear ni se han tenido problemas con el path al archivo 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
archivo HolaMundoApp.class nuevo en el directorio donde se encuentra el archivo fuente.
Si ha habido algún problema, en Problemas de compilación al final de esta sección, hemos intentado reproducir
los que más frecuentemente se suelen dar, se pueden consultar por si pueden aportar un poco de luz al error que haya
aparecido.
Ejecución
Para ejecutar la aplicación HolaMundoApp, hemos de recurrir al intérprete java, que también se encuentra en el
directorio bin, bajo el directorio java. Se ejecutará la aplicación con la línea:
c:\java HolaMundoApp
y debería aparecer en pantalla la respuesta de Java:
c:\Hola Mundo!
39
(c) 2000 by José Abraham Arenas Barrios
inte
lec
tua
l
Are
nas
Notas para el Curso de Java
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
Los Applets
de Java
40
Notas para el Curso de Java
El visor de Applets de SUN (appletviewer)
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
inte
lec
tua
l
Are
nas
El visualizador de applets (appletviewer) es una aplicación que permite ver en funcionamiento applets, sin
necesidad de la utilización de un navegador World-Wide-Web como HotJava, Microsoft Explorer o Nescape. En
adelante, recurriremos muchas veces a él, ya que el objetivo del tutorial es el lenguaje Java.
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, 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 sonido... en Internet.
Llamadas a Applets con appletviewer
Un applet es una mínima aplicación Java diseñada para ejecutarse en un navegador Web. Por tanto, no necesita
preocuparse por un método main() ni en dónde se realizan las llamadas. El applet asume que el código se está ejecutando desde dentro de un navegador. El appletviewer se asemeja al mínimo navegador. Espera como argumento el nombre
del fichero html que debe cargar, no se le puede pasar directamente un programa Java. Este fichero html debe contener
una marca que especifica el código que cargará el appletviewer:
<HTML>
<APPLET CODE=HolaMundo.class WIDTH=300 HEIGHT=100>
</APPLET>
</HTML>
El appletviewer crear un espacio de navegación, incluyendo un área gráfica, donde se ejecutará el applet, entonces llamará a la clase applet apropiada. En el ejemplo anterior, el appletviewer cargará una clase de nombre
HolaMundo y le permitirá trabajar en su espacio gráfico.
41
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
Arquitectura interna del appletviewer
inte
lec
tua
l
Are
nas
El appletviewer representa el mínimo interfaz de navegación. En la figura se muestran los pasos que seguiría
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
appletviewer para presentarnos el resultado de la ejecución del código de nuestra clase.
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.
Ciclo de vida de un Applet
Cuando un applet se carga en el appletviewer, comienza su ciclo de vida, que pasaría por las siguientes fases:
1.
Se crea una instancia de la clase que controla el applet. En el ejemplo de la figura anterior, sería la clase
HolaMundo.
2.
El applet se inicializa.
3.
El applet comienza a ejecutarse.
4.
El applet empieza a recibir llamadas. Primero recibe una llamada init (inicializar), seguida de un mensaje
start (empezar) y paint (pintar). Estas llamadas pueden ser recibidas asíncronamente.
42
Notas para el Curso de Java
METODOS DE APPLETVIEWER
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
inte
lec
tua
l
Are
nas
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, volveremos 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. Tambié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 comenzar 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,
imaginemos 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 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()
El método destroy() se llama cuando ya no se va a utilizar más el applet, cuando se necesita que sean liberados
todos los recursos dispuestos por el applet, por ejemplo, cuando se cierra el navegador. La clase Applet no hace nada
en este método. Las clases derivadas deberían sobrecargarlo para hacer una limpieza final. Los applet multithread
deberían utilizar destroy() para detener los threads que quedasen activos.
El appletviewer también contiene la clase Component (componente), que usa dos métodos para ayudas al applet a
escribir en el espacio gráfico que el appletviewer le proporciona para su ejecución.
paint(Graphics g)
Es la función llamada cada vez que el área de dibujo del applet necesita ser refrescada. 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 ventana, 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étodo, 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 representa la ventana del applet en la
página web.
update(Graphics g)
Esta es la función que realmente se llama cuando se necesita una actualización de la pantalla. La clase Applet
simplemente limpia el área y llama al método paint(). Esta funcionalidad es suficiente para la mayoría de los casos;
aunque, de cualquier forma, las clases derivadas pueden sustituir esta funcionalidad para sus propósitos especiales. Es
decir, en las situaciones detalladas anteriormente que dañan la zona de exposición del applet, el sistema llama al
método paint(), pero en realidad la llamada se realiza al método update(), cuyo comportamiento establecido en la clase
Component es llamar al método paint(), tras haber rellenado la zona del applet con 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 percibir 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()
43
inte
lec
tua
l
Are
nas
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
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() inserto en init(), para evitar el mismo desagradable efecto.
repaint()
Llamando a este método se podrá forzar la actualización de un applet, la llamada a update(). Pero hay que tener
cierto cuidado, porque AWT posee cierta inteligencia (combinación casi siempre nefasta), de forma que si se llama a
update() mediante repaint() con una frecuencia muy corta, AWT ignorará las llamadas a update() que estime oportuno,
pues considera a esta función como un bien escaso.
Resumen
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.
Si el fichero html no contiene un tag <APPLET> válido, el appletviewer no hará nada. El appletviewer no
muestra otras marcas html.
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 archivo html del mismo nombre que nuestro archivo de código fuente
Java.
%appletviewer HolaMundo.html
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
Esta llamada lanzaría la ejecución de HolaMundo.class en el appletviewer, abriéndose en pantalla la ventana
siguiente:
44
Notas para el Curso de Java
El menu del applet
El appletviewer tiene un único menú mostrado en la imagen siguiente y que vamos a explicar en cada una de sus
opciones, ya que lo usaremos a menudo cuando vayamos avanzando en nuestros conocimientos de Java.
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
inte
lec
tua
l
Are
nas
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 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 archivo 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.
45
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
Un applet básico en Java
Vamos a comenzar la creación del código fuente del un applet que satisfaga nuestras necesidades. Recordamos
que Java utiliza la extensión .java para designar los archivos fuente.
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 archivo fuente Java como HolaMundo.java.
inte
lec
tua
l
Are
nas
//
// Applet HolaMundo de ejemplo
//
import java.awt.Graphics;
import java.applet.Applet;
public class HolaMundo extends Applet {
public void paint( Graphics g ) {
g.drawString( "Hola Mundo!",25,25 ) ;
}
}
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.
Veamos como se descompone un applet en sus piezas/objetos:
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
/*
Sección de importaciones
*/
public class NombreDelNuevoApplet extends Applet {
/*
Aquí se declaran las variables de estado (public y private)
*/
/*
Los métodos para la interacción con los objetos se
declaran y definen aquí
*/
public void MetodoUno( parámetros ) {
/*
Aquí viene para cada método, el código Java que
desempeña la tarea.
Qué código se use depende del applet
*/
}
}
Para HolaMundo, se importan las dos clases que necesita. No hay variables de estado, 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 archivo 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 deseamos.
import java.applet.Applet;
46
Notas para el Curso de Java
. . .
public class HolaMundo extends Applet {
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:
inte
lec
tua
l
Are
nas
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.
Otros métodos básicos para dibujar son:
drawLine( int x1,int y1,int x2,int y2 )
drawRect( int x,int y,int ancho,int alto )
drawOval( int x,int y,int ancho,int alto )
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
Tanto para drawRect() como para drawOval(), las coordenadas (x,y) son la esquina superior izquierda del
rectángulo (para drawOval, el óvalo es encajado en el rectángulo que lo circunscribe).
47
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
Compilación y ejecución de un applet
Ahora que tenemos el código de nuestro applet básico y el archivo fuente Java que lo contiene, 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
inte
lec
tua
l
Are
nas
Eso es todo. El compilador javac generará un archivo HolaMundo.class que podrá ser llamado 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.
Prueba de un Applet
El 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 archivo.html
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
En nuestro caso el archivo 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.
La marca <applet> </applet> de HTML
Dado que los applets están mayormente destinados a ejecutarse en navegadores Web, había que preparar el
lenguaje HTML para soportar Java, o mejor, los applets. El esquema de marcas de HTML, y la evolución del estándar
marcado por Netscape hicieron fácil la adición de una nueva marca que permitiera, una vez añadido el correspondiente
código gestor en los navegadores, la ejecución de programas Java en ellos.
La sintaxis de las etiquetas <APPLET> y <PARAM> es la que se muestra a continuación y que iremos explicando en párrafos posteriores:
<APPLET CODE= WIDTH= HEIGTH= [CODEBASE=] [ALT=]
[NAME=] [ALIGN=] [VSPACE=] [HSPACE=]>
<PARAM NAME= VALUE= >
</APPLET>
Atributos obligatorios:
CODE : Nombre de la clase principal
WIDTH : Anchura inicial
HEIGHT : Altura inicial
Atributos opcionales:
CODEBASE : URL base del applet
ALT : Texto alternativo
NAME : Nombre de la instancia
ALIGN : Justificación del applet
VSPACE : Espaciado vertical
HSPACE : Espaciado horizontal
Los applets se incluyen en las páginas Web a través de la marca <APPLET>, que para quien conozca html
resultará muy similar a la marca <IMG>. Ambas necesitan la referencia a un archivo 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 archivo 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 archivo .class utilizando el parámetro
48
Atributos de un applet
inte
lec
tua
l
Are
nas
Notas para el Curso de Java
CODEBASE, aunque todavía se puede usar CODE para proporcionar el nombre del archivo .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), definiendo 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 espacio 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.
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
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 el fichero de clase ejecutable, que tiene la extensión .class. No se permite un URL absoluto, como ya se ha
dicho, aunque sí puede ser relativo al atributo opcional CODEBASE.
WIDTH
Indica la anchura inicial que el navegador debe reservar para el applet en pixels.
HEIGHT
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
Se emplea para utilizar el URL base del applet. En caso de no especificarse, se utilizará el mismo que tiene el
documento.
ALT
Como ya se ha dicho, funciona exactamente igual que el ALT de la marca <IMG>, es decir, muestra un texto
alternativo, en este caso al applet, en navegadores en modo texto o que entiendan la etiqueta APPLET pero no
implementen una máquina virtual Java.
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
Se emplea para alinear el applet permitiendo al texto fluir a su alrededor. Puede tomas los siguientes valores:
LEFT, RIGHT, TOP, TEXTTOP, MIDDLE, ABSMIDDLE, BASELINE, BOTTOM y ABSBOTTOM.
VSPACE
Indica el espaciado vertical entre el applet y el texto, en pixels. Sólo funciona cuando se ha indicado ALIGN =
LEFT o RIGHT.
HSPACE
Funciona igual que el anterior pero indicando espaciamiento horizontal, en pixels. Sólo funciona cuando se ha
indicado ALIGN = LEFT o RIGHT.
Es probable encontrar en algunas distribuciones otras etiquetas para la inclusión de applets, como <APP>. Esto
se debe a que estamos ante la tercera revisión de la extensión de HTML para la incrustación de applets y ha sido
adoptada como la definitiva. Por ello, cualquier otro medio corresponde a implementaciones obsoletas que han quedado descartadas.
49
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
Paso de parámetros a un applet
inte
lec
tua
l
Are
nas
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
repetirse cuantas veces se quiera, una tras otra.
Los atributos que acompañan a la marca PARAM son los siguientes:
NAME .- Nombre del parámetro que se desea pasar al applet.
VALUE .- Valor que se desea transmitir en el parámetro que se ha indicado antes.
Texto HTML .- Texto HTML que será interpretado por los navegadores que no entienden la 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 archivo HolaTal.java
import java.awt.Graphics;
import java.applet.Applet;
public class HolaTal extends Applet {
String nombre;
public void init() {
nombre = getParameter( "Nombre" );
}
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
public void paint( Graphics g ) {
g.drawString( "Hola "+nombre+"!",25,25 );
}
}
Si compilamos el ejemplo obtendremos el archivo HolaTal.class que incluiremos en nuestra página Web. Vamos
a generar el archivo HolaTal.html, en el que incluiremos nuestro applet, y que debería tener el siguiente contenido:
<HTML>
<APPLET CODE=HolaTal.class WIDTH=300 HEIGHT=100>
<PARAM NAME="Nombre" VALUE="Basia">
</APPLET>
</HTML>
Por supuesto, que puedes sustituir mi nombre por el tuyo. Este cambio no afectará al código Java, no será
necesario recompilarlo para que te salude a ti el applet.
Los parámetros no se limitan a uno solo. Se puede pasar al applet cualquier número de parámetros y siempre hay
que indicar un nombre y un valor para cada uno de ellos.
El método getParameter() es fácil de entender. El único argumento que necesita es el nombre del parámetro cuyo
valor queremos recuperar. Todos los parámetros se pasan como Strings, en caso de necesitar pasarle al applet un valor
entero, se ha de pasar como String, recuperarlo como tal y luego convertirlo al tipo que deseemos. Tanto el argumento
de NAME como el de VALUE deben ir colocados entre dobles comillas (") ya que son String.
El hecho de que las marcas <APPLET> y <PARAM> sean ignoradas por los 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 y contenido alternativo
</APPLET>
Nuestro archivo 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="Abraham">
Si no lo ves es que tu navegador no es compatible con Java
<I>Java Compatible</I>
</APPLET>
</HTML>
50
inte
lec
tua
l
Are
nas
Notas para el Curso de Java
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
Java y el M-V-C
de Smalltalk
51
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
El modelo de delegación de eventos de Java (Modelo-VistaControlador, MVC)
inte
lec
tua
l
Are
nas
Construir una ventana o applet en Java implica la interacción entre el usuario y el programa. Esto suele ser un
ejercicio complejo. Para que una ventana sea interactiva con el usuario de Java, el lenguaje depende de tres elementos
para lograrlo, el modelo, la vista y el controlador. La triada MVC fue construida en el lenguaje Smalltalk-80, pero
eventualmente se trasladó hacia otros lenguajes. Cada uno de ellos es un tipo de objeto especializado que se combina
con los otros dos para poder producir una ventana o applet interactivo. Este método de construcción de ventanas/
applets es conocido como la triada MVC. Ahora examinaremos como trabaja cada objeto de la triada MVC como es
que son creados.
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
La idea básica de la triada MVC es la separación de la parte gráfica de la aplicación/applet en dos partes: la
aplicación abstracta (o el modelo) el cual realiza los cálculos necesarios sin tomar en cuenta los canales de E/S, y la
parte de la interfaz de usuario, cuya responsabilidad es capturar todas las acciones y funciones de E/S. Esta separación
permite al diseñador de la aplicación/applet concentrarse en una parte a la vez de la aplicación/applet, con lo que el
proceso de diseño y desarrollo se simplifica.
Ahora bien, la parte de la interfaz de usuario se divide a su vez en dos partes: la vista, que maneja la parte que se
ve de la aplicación, es decir la parte gráfica que se dibuja en la pantalla y da salida a todas las operaciones de salida; y
la parte conocida como el controlador, el cual maneja los eventos de entrada por parte del usuario, por ejemplo desde el
mouse y el teclado. La vista y el controlador pueden comunicarse entre ellas sin necesidad de tener que interactuar con
el modelo. Tanto el modelo, la vista y el controlador son instancias de clases de Java.
Los controladores en Java son vistos como delegados, es decir, un objeto en Java puede tener delegados que se
ocupen de manejar sus funciones de E/S. En Java dichos delegados se conocen como auditores de eventos o
escuchadores de eventos. Existen auditores para los componentes gráficos que ofrece el paquete java.awt.* asi como
también hay auditores para el teclado y el ratón.
Las ventajas de tener auditores son las siguientes:
•Las comunicaciones desde un controlador a un modelo, causando un cambio en el estado de la aplicación de
alguna manera.
•Las comunicaciones desde un controlados hacia una vista, causando un cambio en la representación visible sin
afectar el estado del modelo.
La división de las labores entre tres objetos hacen que el modelo de ventanas del AWT de Java sea muy flexible
y extensible. También nos produce los siguientes beneficios:
•Separa el código de la interfaz de usuario de la estructura subyacente (queremos evitar darle al modelo detalles
íntimos acerca de sus vistas y que pudiera ser difícil separarlo de ellas).
•Las vistas representan algún aspecto del modelo al usuario. El mismo modelo puede tener diferentes vistas
presentadas al mismo usuario simultaneamente.
VISTA
dependencia
modelo
controlador
vista
CONTROLADOR
52
modelo
MODELO
Notas para el Curso de Java
Eventos y Auditores de Java 1.1.X
En la siguiente tabla se resumen los eventos, sus auditores y la interface del auditor de los delegados para los
componentes de IU básicos.
Interfaz del
auditor
inte
lec
tua
l
Are
nas
Evento
Método
ActionListener
public void actionPerformed(ActionEvent e)
Seleccionar/Deseleccionar
lista
ItemListener
public void itemStateChanged(ItemEvent e)
Obtener el foco
FocusListener
public void focusGained(FocusEvent e)
Perder el foco
FocusListener
public void focusLosted(FocusEvent e)
El texto cambio
TextListener
public void textValueChanged(TextEvent e)
Agregar
componente
ContainerListener
public void
componentAdded(ContainerListener e)
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
Acción
Eliminar
componente
ContainerListener
public void
componentRemoved(ContainerListener e)
Un auditor se tiene que asociar con un objeto o mejor dicho modelo. Para asociarlo se dice que se registra un
auditor al objeto.
Por ejemplo si tenemos que
Button boton = new Button(«Salir»);
para registrarle un auditor al botón hacemos
boton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// sobre-escribimos el método a nuestra conveniencia
}
});
53
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
Interfaz del auditor
Eventos
Método
ratón abajo
public void mousePressed(MouseEvent e)
MouseListener
ratón arriba
public void mouseReleased(MouseEvent e)
MouseListener
ratón entra
public void mouseEntered(MouseEvent e)
MouseListener
ratón sale
public void mouseExited(MouseEvent e)
MouseListener
el click del ratón
public void mouseClicked(MouseEvent e)
MouseMotionListener
el ratón se mueve
public void mouseMoved(MouseMotionEvent e)
MouseMotionListener
el ratón es arrastrado (drag n
drop)
public void mouseDragged(MouseMotionEvent e)
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
inte
lec
tua
l
Are
nas
MouseListener
KeyListener
tecla arriba
public void keyReleased(KeyEvent e)
KeyListener
tecla abajo
public void keyPressed(KeyEvent e)
KeyListener
la tecla se pulsa una vez
public void keyTyped(KeyEvent e)
Esta tabla contiene los eventos y auditores para el ratón y el teclado.
Registre estos auditores usando los siguientes métodos:
•addMouseListener()
•addMouseMotionListener()
•addKeyListener()
Para combinar eventos del ratón y del teclado use los métodos siguientes, todos ellos disponibles desde la Clase
InputEvent o MouseEvent que hereda de ella:
boolean
boolean
boolean
boolean
boolean
isControlDown()
isAltDown()
isShiftDown()
isAltDown()
isMetaDown()
Para investigar que ratón ha sido pulsado use el método getModifiers() de la clase InputEvent, este método
devuelve las banderas de los eventos del ratón.
54
Notas para el Curso de Java
Para los auditores de los Frames o ventanas en Java consulte la siguiente tabla:
Nombre del Evento
auditor
Método
WindowListener
public void windowOpened(WindowEvent e)
ventana movida
ComponentListener
public void
componentMoved(ComponentEvent e)
ventana activada
WindowListener
public void
windowActivated(WindowEvent e)
ventana desactivada
WindowListener
public void
windowDeactivated(WindowEvent e)
ventana minimizada
WindowListener
public void
windowIconified(WindowEvent e)
ventana maximizada
WindowListener
public void
windowDeiconified(WindowEvent e)
cerrando ventana
WindowListener
public void windowClosing(WindowEvent
e)
ventana cerrada
WindowListener
public void windowClosed(WindowEvent e)
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
inte
lec
tua
l
Are
nas
ventana abierta
Para registar los auditores a un Frame use el método addWindowListener(). Este método solo está disponible para
las clases Frame y Dialog.
55
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
Método
Función
Devuelve un objeto Color que representa el color
de fondo del componente
setBackground(Color)
Asigna el color de fondo del componente
getForeground()
Devuelve un objeto Color que representa el color
del primer plano del componente
getFont()
setFont(Font)
getSize()
getMinimumSize()
getPreferredSize()
inte
lec
tua
l
Are
nas
getBackground()
Devuelve un objeto Font que representa la fuente
del componente actual
Cambia la fuente del componente
Devuelve un objeto Dimension que representa el
ancho y el largo del componente a travez de
getSize().width y getSize().height
Devuelve un objeto Dimension que representa el
tamaño mas pequeño que un componente puede
tener, este método se puede sobre-escribir
Devuelve un objeto Dimension que representa el
tamaño preferido del componente
Cambia el tamaño del componente por medio de un
objeto Dimension. (ver validate())
contains(int, int)
Devuelve true si las coordenadas x,y están
dentro del componente
setVisible(boolean)
Dependiendo del valor booleano oculta o muestra
el compoente
boolean isVisible()
Devuelve true o false dependiendo si el
componente está visible u oculto
setEnabled(boolean)
Habilita o deshabilita el componente
dependiendo del valor booleano
boolean isEnabled()
Devuelve true o false dependiendo si el
componente está habilitado o no
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
setSize(Dimension)
Todos los componentes de AWT descienden de la clase Component, la tabla muestra todos los métodos que estan
disponibles para los componentes.
56
inte
lec
tua
l
Are
nas
Notas para el Curso de Java
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
El Abstract
Window Toolkit
de Java 1.1.x
57
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
Estructura del Abstract Window Toolkit (AWT)
inte
lec
tua
l
Are
nas
La estructura de la versión actual del AWT podemos resumirla en los puntos que exponemos a continuación:
"#Los Contenedores contienen Componentes, que son los controles básicos
"#No se usan posiciones fijas de los Componentes, sino que están situados a través de una disposición controlada por medio de plantillas de disposición (layouts)
"#El común denominador de más bajo nivel se acerca al teclado, ratón y al manejo de eventos
"#Alto nivel de abstracción respecto al entorno de ventanas en que se ejecute la aplicación (no hay áreas
cliente, ni llamadas a X windows, ni hWnds de windows, etc.)
"#La arquitectura de la aplicación es dependiente del entorno de ventanas, en vez de tener un tamaño fijo
"#Es bastante dependiente de la máquina en que se ejecuta la aplicación (no puede asumir que un diálogo
tendrá el mismo tamaño en cada máquina)
"#Carece de un formato de recursos. No se puede separar el código de lo que es propiamente interface. No hay
ningún diseñador de interfaces (todavía)
Componentes y contenedores
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
Una interface gráfica está construida en base a elementos gráficos básicos, los Componentes. 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 usuario interactuar con la aplicación y proporcionar información desde el programa al
usuario sobre el estado del programa. En el AWT, todos los Componentes de la interface de usuario son instancias de
la clase Component o uno de sus subtipos.
Los Componentes no se encuentran aislados, sino agrupados dentro de Contenedores. Los Contenedores contienen y organizan la situación de los Componentes; además, los Contenedores son en sí mismos Componentes y como
tales pueden ser situados dentro de otros Contenedores. También contienen el código necesario para el control de
eventos, cambiar la forma del cursor o modificar el icono de la aplicación. En el AWT, todos los Contenedores son
instancias de la clase Container o uno de sus subtipos.
En la imagen siguiente presentamos una interface de usuario muy simple, con la apariencia que presenta cuando
se visualiza bajo Windows '95,98 o 2000.
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 corresponde con la aplicación gráfica generada anteriormente.
58
inte
lec
tua
l
Are
nas
Notas para el Curso de Java
Tipos de componentes
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
En el árbol siguiente mostramos la relación que existe entre todas las clases que proporciona AWT para la
creación de interfaces de usuario, presentando la jerarquía de Clases e Interfaces:
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
59
(c) 2000 by José Abraham Arenas Barrios
inte
lec
tua
l
Are
nas
Notas para el Curso de Java
•MenuItem
•CheckboxMenuItem
•Menu
•Point
•Polygon
•Rectangle
•Toolkit
Interfaces:
•LayoutManager
•MenuContainer
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
En la figura siguiente reproducimos la ventana generada por el código de la aplicación ComponentesAWT.java
que muestra todos los Componentes que proporciona el AWT. Vamos a ver en detalle estos Componentes, pero aquí
podemos observar ya la estética que presentan en su conjunto. La ventana es necesaria porque el programa incluye un
menú, y los menús solamente pueden utilizarse en ventanas. El código contiene un método main() para poder ejecutarlo como una aplicación independiente.
60
Notas para el Curso de Java
Componentes
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
inte
lec
tua
l
Are
nas
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.
Los Objetos derivados de la clase Component que se incluyen en el Abstract Window Toolkit son los que
aparecen a continuación:
√
Button
√
Canvas
√
Checkbox
√
Choice
√
Container
√
Panel
√
Window
√
Dialog
√
Frame
√
Label
√
List
√
Scrollbar
√
TextComponent
√
TextArea
√
TextField
Vamos a ver un poco más en detalle los Componentes que nos proporciona el AWT para incorporar a la creación
de la interface con el usuario.
Botones (clase Button)
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.
Se pueden crear objetos Button con el operador new:
Button boton = new Button( "Botón");
La cadena utilizada en la creación del botón aparecerá en el botón cuando se visualice en pantalla. Esta cadena
también se devolverá para utilizarla como identificación del botón cuando ocurra un evento.
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 agregandole un auditor
de eventos al boton:
boton.addActionListener(new ActionListener() {
// sobreescribimos el método actionPerformed
public void actionPerformed(ActionEvent e) {
System.out.println(«el boton ha sido oprimido»);
}
});
Es deseable que cada botón cuente con su propio auditor de eventos. Solamente puede existir un auditor y solo
uno por componente.
import java.awt.*;
import java.awt.event.*;
import java.applet.Applet;
public class Botones extends Applet {
Button b1,b2,b3;
public
b1
b2
b3
void init() {
= new Button( "Botón B1" );
= new Button( "Botón B2" );
= new Button( "Botón B3" );
61
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
add( b1 );
add( b2 );
add( b3 );
inte
lec
tua
l
Are
nas
// se añaden los auditores para cada boton
b1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("boton1 oprimido");
}
});
b2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("boton2 oprimido");
}
});
b3.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("boton3 oprimido");
}
});
}
}
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
En el applet anterior, Botones.java, observamos que se imprime el texto asociado al botón que hayamos pulsado.
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 (clase Choice)
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.event.*;
import java.awt.*;
import java.applet.Applet;
public class BotonSeleccion extends Applet {
Choice Selector;
public void init() {
Selector = new Choice();
Selector.add(
Selector.add(
Selector.add(
add( Selector
"Rojo" ); // tambien puede ser addItem()
"Verde" );
"Azul" );
);
// poner el auditor al Choice
Selector.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
System.out.println("El color seleccionado es: " +
Selector.getSelectedItem());
}
});
}
}
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.
62
Notas para el Curso de Java
Botones de Marcación (clase Checkbox)
inte
lec
tua
l
Are
nas
Los botones de comprobación (Checkbox) se utilizan frecuentemente como botones de estado. Proporcionan
información del tipo Sí o No (true o false). El estado del botón se devuelve 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 pueden obtener a través de
los métodos getLabel() y getState() del objeto Checkbox.
import java.awt.*;
import java.awt.event.*;
import java.applet.Applet;
public class BotonComprobacion extends Applet {
Checkbox Relleno;
public void init() {
Relleno = new Checkbox( "Relleno");
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
add( Relleno );
// auditor para Relleno
Relleno.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
if (Relleno.getState()) // esta marcado?
System.out.println("Relleno esta marcado");
else
System.out.println("Relleno no tiene marca");
}
});
}
}
El sencillo ejemplo anterior, BotonComprobacion.java, muestra los cambios que se producen en el estado del
botón cuando la caja está o no seleccionada.
Botones de Selección(clase CheckboxGroup)
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.awt.event.*;
import java.applet.Applet;
public class BotonRadio extends Applet {
CheckboxGroup Radio;
public void init() {
Radio = new CheckboxGroup();
add( new Checkbox( "Primero",Radio,true) );
add( new Checkbox( "Segundo",Radio,false) );
add( new Checkbox( "Tercero",Radio,false) );
// ver public Checkbox getSelectedCheckbox() para Radio
}
}
En el ejemplo anterior, BotonRadio.java, observamos que siempre hay un botón activo entre los que conforman
el interfaz de comprobación que se ha implementado.
63
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
Etiquetas (clase Label)
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 siguiente:
inte
lec
tua
l
Are
nas
import java.awt.*;
import java.applet.Applet;
public class Etiqueta extends Applet {
public void init() {
Label etiq1 = new Label( "Hola Java!" );
Label etiq2 = new Label( "Otra Etiqueta" );
setLayout( new FlowLayout( FlowLayout.CENTER,10,10) );
add( etiq1 );
add( etiq2 );
}
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
}
LISTAS (clase List)
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 permite selección múltiple.
import java.awt.*;
import java.awt.event.*;
import java.applet.Applet;
public class Lista extends Applet {
List l;
public void init() {
l = new List( 4,false );
l.add(
l.add(
l.add(
l.add(
l.add(
l.add(
l.add(
l.add(
l.add(
add( l
64
"Mercurio" );
"Venus" );
"Tierra" );
"Marte" );
"Jupiter" );
"Saturno" );
"Neptuno" );
"Urano" );
"Pluton" );
);
Notas para el Curso de Java
inte
lec
tua
l
Are
nas
// agregarle el auditor
l.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
System.out.println(l.getSelectedItem());
}
});
// otro auditor
l.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println( "doble click en: " + l.getSelectedItem());
}
});
}
}
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 activará al auditor ActionListener de la misma
forma que con los eventos de selección en menús.
En el applet siguiente, ListaMult.java, se permite al usuario seleccionar varios elementos de los que constituyen
la lista. En la figura se muestra la apariencia de una selección múltiple en este applet.
import java.awt.*;
import java.awt.event.*;
import java.applet.Applet;
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
public class ListaMult extends Applet {
List lm = new List( 6,true );
public void init() {
Button boton = new Button( "Aceptar"
);
lm.add( "Mercurio" );
lm.add( "Venus" );
lm.add( "Tierra" );
lm.add( "Marte" );
lm.add( "Jupiter" );
lm.add( "Saturno" );
lm.add( "Neptuno" );
lm.add( "Urano" );
lm.add( "Pluton" );
add( lm );
add( boton );
// auditores
boton.addActionListener(new
ActionListener() {
public void actionPerformed(ActionEvent e) {
String seleccion[];
seleccion = lm.getSelectedItems();
for( int i=0; i < seleccion.length; i++ )
System.out.println( seleccion[i] );
}
});
}
}
En este caso de la selección múltiple en una lista, utilizamos un auditor de eventos externo para disparar 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.
65
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
Campos de Texto (clase TextField)
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 determinada, rellenos con texto
predefinido y rellenos con texto predefinido y una longitud determinada. 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.
inte
lec
tua
l
Are
nas
import java.awt.*;
import java.awt.event.*;
import java.applet.Applet;
public class CampoTexto extends Applet {
TextField tf1,tf2,tf3,tf4;
public void init() {
// Campo de texto vacío
tf1 = new TextField();
// Campo de texto vacío con 20
columnas
20 );
"Hola" );
en 30 columnas
"Hola",30 );
);
);
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
tf2 = new TextField(
// Texto predefinido
tf3 = new TextField(
// Texto predefinido
tf4 = new TextField(
add( tf1 ); add( tf2
add( tf3 ); add( tf4
// auditores
tf1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("Campo de texto 1 es: " + tf1.getText());
}
});
tf2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("Campo de texto 2 es: " + tf2.getText());
}
});
tf3.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("Campo de texto 3 es: " + tf3.getText());
}
});
tf4.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("Campo de texto 4 es: " + tf4.getText());
}
});
}
}
Cuando el usuario teclea un retorno de carro en un campo de texto, se genera un evento TextField, que al igual
que con los otros Componentes del AWT podemos capturar con el auditor ActionListener, tal como se demuestra en el
ejemplo.
66
Notas para el Curso de Java
Áreas de Texto (clase TextArea)
inte
lec
tua
l
Are
nas
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.awt.event.*;
import java.applet.Applet;
public class AreaTexto extends Applet {
TextArea t1,t2;
public void init() {
Button boton = new Button( "Aceptar" );
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
t1 = new TextArea();
t2 = new TextArea( "Notas para el curso de Java",5,40 );
t2.setEditable( false ); // inhabilita a t2 a recibir texto desde el teclado
add( t1 );
add( t2 );
add( boton );
// auditor para el boton
boton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
String textoT1 = t1.getText();
t2.insert(textoT1, 10); // insertar texto en la pos 10
t2.append("Hola desde Java 1.1!"); // apendizar texto
}
});
}
}
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.
67
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
Lienzo (clase Canvas)
inte
lec
tua
l
Are
nas
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 esos 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.*;
import java.awt.event.*;
import java.applet.Applet;
public class Lienzo extends Applet {
Button boton;
public void init() {
setLayout( new BorderLayout( 15,15 ) );
boton = new Button( "Test" );
MiCanvas canv = new MiCanvas( 100,100 );
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
add( "Center",canv );
add( "South",boton );
boton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("boton pulsado");
}
});
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
System.out.println( "Raton: ("+e.getX()+","+e.getY()+")" );
return true;
}
});
}
}
class MiCanvas extends Canvas {
private int ancho;
private int alto;
public MiCanvas( int anc,int alt ) {
ancho = anc; alto = alt;
setBounds( 0,0,anc,alt );
}
public void paint( Graphics g ) {
g.setColor( Color.blue );
g.fillRect( 0,0,ancho,alto );
}
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
if( x < ancho && y < alto ) {
System.out.println( "Raton en Canvas: ("+x+","+y+")" );
}
});
} }
68
Notas para el Curso de Java
Barras de desplazamiento (clase Scrollbar)
inte
lec
tua
l
Are
nas
En determinados applets que necesiten realizar el ajuste de valores lineales en pantalla, resulta útil el uso de
barras de desplazamiento (Scrollbar). Las barras de desplazamiento proporcionan una forma de trabajar con rangos de
valores o de áreas como el Componente TextArea, que proporciona dos barras de desplazamiento automáticamente.
Si queremos implementar un selector de color, como en el applet Slider.java, podemos utilizar una barra de
desplazamiento para cada uno de los colores primarios.
import java.awt.*;
import java.applet.Applet;
public class Slider extends Applet {
Scrollbar rojo,verde,azul;
public void init() {
rojo = new Scrollbar( Scrollbar.VERTICAL,0,1,0,255 );
verde = new Scrollbar( Scrollbar.VERTICAL,0,1,0,255 );
azul = new Scrollbar( Scrollbar.VERTICAL,0,1,0,255 );
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
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;
public class Ranger extends Applet {
Scrollbar rango;
public void init() {
rango = new Scrollbar( Scrollbar.HORIZONTAL,0,64,0,255 );
add( rango );
}
}
Como se puede ver, el ancho de la barra es mayor, en relación al Scrollbar. En este caso, maxValue representa el
valor máximo para el lado izquierdo de la barra. Si se quieren representar 64 colores simultáneamente, es decir [0-63] a
[192-255], maxValue debería ser 192.
Igual que otros Componentes, las barras de desplazamiento generan eventos; estos eventos se capturan por medio
de, adivino, otro auditor. 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.
69
(c) 2000 by José Abraham Arenas Barrios
inte
lec
tua
l
Are
nas
Notas para el Curso de Java
import java.awt.*;
import java.awt.event.*;
import java.applet.Applet;
public class RangoRojo extends Applet {
Scrollbar rango;
TextField valor;
Label etiqueta;
public void init() {
rango = new Scrollbar( Scrollbar.HORIZONTAL,0,1,0,255 );
valor = new TextField( "0",5 );
etiqueta = new Label( "Rojo (0-255)" );
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
setLayout( new GridLayout( 1,3 ) );
valor.setEditable( false );
add( etiqueta );
add( rango );
add( valor );
// ponerle el auditor al Scrollbar
rango.addAdjustmentListener(new AdjustmentListener() {
public void adjustmentValueChanged(AdjustmentEvent e) {
valor.setText(Integer.toString(rango.getValue()));
}
});
}
}
Ese era el código del applet que construye la ventana de la figura y actualiza el campo de texto asociado. No
implementa ninguna otra acción o evento.
70
Notas para el Curso de Java
Contenedores (clase Container y derivadas)
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
inte
lec
tua
l
Are
nas
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 clase Component la funcionalidad de adición, sustracción, recuperación, control y organización de otros componentes.
El AWT proporciona cuatro clases de Contenedores:
!
Window
!
Frame
!
Dialog
!
Panel
Además de estos Contenedores, la clase Applet también es un Contenedor, es un subtipo de la clase Panel y
puede tener Componentes.
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 programador 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 añadiendo Componentes directamente a la instancia que se crea de la clase Applet.
En el siguiente ejemplo se crea un Frame vacío. El título del Frame, que corresponderá 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.*;
public class Ejemplo1 {
public static void main( String args[] ) {
Frame f = new Frame( "Ejemplo 1" );
f.show();
}
}
En el código de ejemplo que sigue, extendemos el código anterior para que la nueva 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.*;
public class Ejemplo2 extends Panel {
public static void main( String args[] ) {
Frame f = new Frame( "Ejemplo 2" );
Ejemplo2 ej = new Ejemplo2();
f.add( "Center",ej );
f.pack();
f.show();
71
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
}
}
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.*;
inte
lec
tua
l
Are
nas
public class Ejemplo3 extends java.applet.Applet {
public static void main( String args[] ) {
Frame f = new Frame( "Ejemplo 3" );
Ejemplo3 ej = new Ejemplo3();
f.add( "Center",ej );
f.pack();
f.show();
}
}
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
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.
Añadir Componentes a un Contenedor
Para que la interface sea útil, no debe estar compuesta solamente por Contenedores, éstos deben tener Componentes en su interior. Los Componentes se añaden al Contenedor invocando al método add() del Contenedor. Este
método tiene tres formas de llamada que dependen del manejador de composición o layout manager que se vaya a
utilizar sobre el Contenedor.
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.*;
public class Ejemplo4 extends java.applet.Applet {
public void init() {
add( new Button( "Uno" ) );
add( new Button( "Dos" ) );
}
public static void main( String args[] ) {
Frame f = new Frame( "Ejemplo 4" );
Ejemplo4 ej = new Ejemplo4();
ej.init();
f.add( "Center",ej );
f.pack();
f.show();
}
}
72
Notas para el Curso de Java
Creación de aplicaciones con GUI’s usando el AWT.
inte
lec
tua
l
Are
nas
Para crear una aplicación utilizando AWT, vamos a ver en principio cómo podemos generar el interface de esa
aplicación, mostrando los distintos elementos del AWT, posteriormente volveremos hacia la implementación de la
aplicación.
Interface
•Crear el Marco de la aplicación (Frame)
•Inicializar Fuentes, Colores, Layouts y demás recursos
•Crear menús y Barras de Menús
•Crear los controles, diálogos, ventanas, etc.
Implementación
•Incorporar auditores de eventos
•Incorporar funcionalidad (threads, red, etc.)
•Incorporar un controlador de errores (excepciones)
Crear el marco de la aplicación.
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
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 necesario 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 Componentes. 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 profundizar sobre algunos aspectos de Java, necesitamos conocer otros previos, así que
proporcionaremos 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 confuso, pero imprescindible.
En el archivo AplicacionAWT.java, se encuentra el código completo de la aplicación que vamos ir construyendo
a lo largo de este repaso general por las características de que dispone el AWT.
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 class AplicacionAWT extends Frame {
static final int HOR_TAMANO = 300;
static final int VER_TAMANO = 200;
public AplicacionAWT() {
super( "Aplicación Java con AWT" );
pack();
resize( HOR_TAMANO,VER_TAMANO );
show();
}
public static void main( String args[] ) {
new AplicacionAWT();
}
}
La clase anterior es un Frame, ya que extiende esa clase y hereda todas sus características. Tiene un método, el
constructor, que no admite parámetros.
Además, se hace una llamada explícita al constructor super, es decir, al constructor de la clase padre, para pasarle
como parámetro la cadena que figurará como el título de la ventana.
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 Componentes se ajusten a sus tamaños correctos en
función del Contenedor en que se encuentren situados.
73
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
La ejecución de la aplicación mostrará la siguiente ventana en pantalla:
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
inte
lec
tua
l
Are
nas
•Los atributos fundamentales de la ventana anterior son:
•Marco de 300x200 pixels
•No tiene barra de menú
•No tiene ningún Componente
•Título "Aplicación Java con AWT"
•Color de fondo por defecto
•Layout por defecto
•Fondo de la ventana vacío
Inicializar fuentes, colores, recursos y demás...
Vamos a ir alterando los recursos de la ventana de la aplicación Java que estamos desarrollando con el AWT,
para ir incorporando y visualizando los distintos Componentes que proporciona AWT. Insertemos algunas líneas de
código en el constructor para inicializar la aplicación:
Cambiemos el font de caracteres a Times Roman de 12 puntos
setFont( new Font( "TimesRoman",Font.PLAIN,12 ) );
Fijemos los colores de la ventana para que el fondo sea Blanco y el texto resalte en Negro
setBackground( Color.white );
setForeground( Color.black );
Seleccionemos como disposición de los Componentes el BorderLayout para este Contenedor
setLayout( new BorderLayout() );
Incorporemos gráficos. Usamos métodos definidos en la clase Graphics; por ejemplo, reproduzcamos el título de
la ventana en medio con una fuente Time Roman de 24 puntos en color Azul. Es necesario utilizar new con Font ya
que en Java, todo son objetos y no podríamos utilizar un nuevo font de caracteres sin antes haberlo creado
public void paint( Graphics g ) {
g.setFont( new Font( "TimesRoman",Font.BOLD,24 ) );
g.setColor( Color.blue );
g.drawString( getTitle(),30,50 );
}
Incorporemos en la parte inferior de la ventana dos botones: Aceptar y Cancelar
Panel p = new Panel();
p.add( new Button( "Aceptar" ) );
p.add( new Button( "Cancelar" ) );
add( "South",p );
Los Componentes se incorporan al Contenedor a través de los dos métodos add() que hay definidos:
add( Component c );
add( String s,Component c );
74
Notas para el Curso de Java
Los Componentes también se podían haber insertado en el Frame, organizándolos en una cierta forma, teniendo
en cuenta que su manejador de composición es un BorderLayout. Por ejemplo:
add( "South",new Button( "Aceptar ) );
add( "South",new Button( "Cancelar" ) );
inte
lec
tua
l
Are
nas
Hemos utilizado un Panel y no el segundo método, porque es más útil el organizar los Componentes en pequeñas
secciones. Así, con nuestro código podemos considerar al Panel como una entidad separada del Frame, lo cual permitiría cambiar el fondo, layout, fuente, etc. del Panel sin necesidad de tocar el Frame.
Si ejecutamos de nuevo la aplicación con los cambios que hemos introducido, aparecerá ante nosotros la ventana
que se muestra a continuación.
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
Si intentásemos en esta aplicación cerrar la ventana, no sucede nada. Cuando se intenta cerrar la ventana, el
sistema envía un evento que no se está tratando. Incorporemos pues un controlador de eventos y empecemos tratando
de capturar al evento WindowClosing, generado cuando se intenta cerrar la ventana:
addWindowListener(new AppWindowAdapter() {
public void windowClosing(WindowEvent e) {
anApplet.stop();
anApplet.destroy();
System.exit(0);
}
});
Si ahora ejecutamos de nuevo la aplicación y cerramos la ventana... Efectivamente se cierra, tal como se suponía.
75
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
Crear barras de menús, menús y opciones de menús
inte
lec
tua
l
Are
nas
En la actual versión del AWT que se proporciona con el JDK, sólo se permite crear menús a través de código, ya
que Java todavía no dispone de un formato de recursos y tampoco hay un diseñador como pueden ser AppStudio,
Delphi o X-Designer; aunque terminará habiendo uno, con seguridad.
No hay ningún método para diseñar una buena interface, todo depende del programador. Los menús son el centro
de la aplicación. La diferencia entre una aplicación útil y otra que 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.
La interface MenuContainer solamente se puede implementar sobre un Frame. Un applet que desee tener un
menú, debe crear un Frame en primer lugar. El código de la función que vamos a ver, crea una barra de menús y se
llama desde el constructor del Frame. La función es private porque no queremos que se pueda llamar desde ninguna
otra clase.
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 auditor ActionListener con el método actionPerformed.
/* Demuestra el uso de menus */
import java.awt.*;
import java.awt.event.*;
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
public class Menus extends Frame {
// constructor
Menus(String sTitle) {
super(sTitle);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
setVisible(false);
}
});
InicializaMenus(this);
setSize(300,300);
}
private void InicializaMenus(Frame window) {
MenuBar mbBarra = new MenuBar();
Menu mArchivo = new Menu("Archivo");
MenuItem miNuevo = new MenuItem( "Nuevo", new MenuShortcut('n'));
MenuItem miAbrir = new MenuItem("Abrir", new MenuShortcut('b'));
MenuItem miGuardar = new MenuItem("Guardar", new MenuShortcut('g'));
MenuItem miGComo = new MenuItem("Guardar como", new MenuShortcut('o'));
MenuItem miImprimir = new MenuItem("Imprimir", new MenuShortcut('i'));
MenuItem miSep = new MenuItem("-");
MenuItem miSalir = new MenuItem("Salir", new MenuShortcut('s'));
mArchivo.add(miNuevo);
mArchivo.add(miAbrir);
mArchivo.add(miGuardar);
mArchivo.add(miGComo);
mArchivo.add(miSep);
mArchivo.add(miSalir);
mbBarra.add(mArchivo);
Menu mAyuda = new Menu("Ayuda");
MenuItem miAyuda = new MenuItem("Ayuda!", new MenuShortcut('a'));
MenuItem miAcerca = new MenuItem("Acerca", new MenuShortcut('c'));
mAyuda.add(miAyuda);
76
Notas para el Curso de Java
mAyuda.add(miAcerca);
mbBarra.add(mAyuda);
window.setMenuBar( mbBarra );
inte
lec
tua
l
Are
nas
// agregar auditores
miNuevo.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("MenuItem Nuevo seleccionado");
}
});
miAbrir.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("MenuItem Abrir seleccionado");
}
});
miGuardar.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("MenuItem Guardar seleccionado");
}
});
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
miGComo.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("MenuItem Guardar Como seleccionado");
}
});
miImprimir.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("MenuItem Imprimir seleccionado");
}
});
miSalir.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("MenuItem Salir seleccionado");
System.exit(0);
}
});
miAyuda.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("MenuItem Ayuda! seleccionado");
}
});
miAcerca.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("MenuItem Acerca seleccionado");
}
});
}
public static void main(String args[]) {
Menus mnWin = new Menus("Aplicación Java con AWT");
mnWin.setLocation(100,100);
mnWin.setVisible(true);
}
} // class Menus
77
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
Ventanas y diálogos
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.
inte
lec
tua
l
Are
nas
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. Para poder mostrar un diálogo se necesita tener de un Frame que
contenga al diálogo, de otra manera no se podrá ni siquiera mostarlo.
El código Java que se expone a continuación, implementa el diálogo Acerca de para la aplicación. Esta clase se
crea oculta y necesitaremos llamar al método setVisible(true) de la propia clase para hacerla visible.
La ventana que aparece en pantalla generada por la clase anterior es la que muestra la figura.
Las aplicaciones independientes deberían heredar tomando como padre la ventana principal de esa aplicación.
Así pueden implementar la interface MenuContainer y proporcionar menús.
No hay razón aparente para que sea una subclase de la clase Frame, pero si se quiere proporcionar funcionalidad
extra, sí debería serlo, en vez de serlo de su padre: Window. Esto es así porque Frame implementa la interface
MenuContainer, con lo cual tiene la posibilidad de proporcionar menús y cambiar el cursor, el icono de la aplicación,
etc.
Un ejemplo más complicado de aplicación gráfica basada en el AWT es el convertidor de decimal a binario/octal/
hexadecimal/base36, Convertidor.java, cuya presentación en pantalla es la que muestra la figura siguiente.
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
import java.awt.*;
import java.awt.event.*;
class AboutDialog extends Dialog {
static int HOR_TAMANO = 300;
static int VER_TAMANO = 150;
Button btAceptar;
// constructor
public AboutDialog(Frame parent) {
super( parent,"Acerca de...",true );
setResizable( false ); // no se puede cambiar de tamaño
setBackground(Color.lightGray);
setLayout(new BorderLayout());
Panel p = new Panel();
btAceptar = new Button("Aceptar");
p.add(btAceptar);
add("South",p);
pack();
setSize(HOR_TAMANO, VER_TAMANO);
repaint();
// auditor del boton
btAceptar.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
setVisible(false);
}
});
}
public void paint( Graphics g ) {
g.setColor(Color.red);
// para la lista de fonts locales
// String[] fontsList = this.getToolkit().getFontList();
g.setFont(new Font("TimesRoman", Font.BOLD | Font.ITALIC, 18));
78
Notas para el Curso de Java
g.drawString("Aplicación Java con AWT", HOR_TAMANO/4,VER_TAMANO/3 );
g.drawString("Versión 1.1.X", HOR_TAMANO/3+15,VER_TAMANO/3+20 );
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
inte
lec
tua
l
Are
nas
}
} // class AboutDialog
79
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
Paneles (clase Panel)
inte
lec
tua
l
Are
nas
La clase Panel es el más simple de los Contenedores de Componentes gráficos. En realidad, 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:
import java.awt.*;
import java.awt.event.*;
public class Paneles extends Frame {
BarraHerram bh;
BarraEstado be;
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
// constructor
Paneles(String sTitle) {
super(sTitle);
bh = new BarraHerram();
be = new BarraEstado();
add("North", bh);
add("South", be);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
setVisible(false);
}
});
be.verEstado("hola!");
setSize(300,300);
}
public static void main(String args[]) {
Paneles panWin = new Paneles("Aplicación Java con AWT");
panWin.setLocation(100,100);
panWin.setVisible(true);
}
} // Paneles
class BarraHerram extends Panel {
public BarraHerram() {
setLayout(new FlowLayout());
add(new Button("Abrir"));
add(new Button("Guardar"));
80
Notas para el Curso de Java
add(new Button("Cerrar"));
}
} // class BarraHerram
inte
lec
tua
l
Are
nas
Choice c = new Choice();
c.add("Times Roman");
c.add("Helvetica");
c.add("System");
add(c);
add(new Button("Ayuda"));
class BarraEstado extends Panel {
Label texto;
Label mas_texto;
public BarraEstado() {
setLayout(new FlowLayout());
add(texto = new Label("La barra de estado ha sido creada"));
add(mas_texto = new Label("Información adicional"));
}
}
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
public void verEstado(String informacion) {
texto.setText(informacion);
}
Al final, la apariencia de la aplicación en pantalla es la que presenta la figura anterior.
81
Notas para el Curso de Java
(c) 2000 by José Abraham Arenas Barrios
Administradores de composición (Layouts)
inte
lec
tua
l
Are
nas
Los layout managers o administradores 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 al
momento de colocarlos sobre un Contenedor. Java dispone de varios, en la actual versión, tal como se muestra en la
imagen:
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
¿Por qué Java proporciona estos esquemas predefinidos de disposición de componentes? La razón es simple:
imaginemos que deseamos agrupar objetos de distinto tamaño en celdas de una rejilla virtual: si confiados en nuestro
conocimiento de un sistema gráfico determinado, codificamos a mano tal disposición, deberemos preveer el
redimensionamiento del applet, su repintado cuando sea cubierto por otra ventana, etc., además de todas las cuestiones
relacionadas con un posible cambio de plataforma (uno nunca sabe a donde van a ir a parar los propios hijos, o los
applets). 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 ocupará, de forma transparente para
nosotros, de todas esas cuitas de formatos. Bien, pues estos gestores son instancias de las distintas clases derivadas de
Layout Manager y que se utilizan en el applet que genera la figura siguiente, donde se muestran los diferentes tipos de
layouts que proporciona el AWT.
En el applet que genera la figura siguiente, se utilizan los diferentes tipos de layouts que proporciona el AWT.
El ejemplo AwtGui.java, ilustra el uso de paneles, listas, barras de desplazamiento, botones, selectores, campos
82
Notas para el Curso de Java
de texto, áreas de texto y varios tipos de layouts.
En el tratamiento de los Layouts se utiliza un método de validación, de forma que los Componentes 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.
inte
lec
tua
l
Are
nas
import java.awt.*;
import java.applet.Applet;
// Clase para pintar casi todos los componentes que ofrece el AWT
// de Java y poder visualizar su apariencia en la pantalla
public class AwtGui extends Applet {
TextArea edicion;
miCanvas dibujo;
Label edicionLab,lapizLab;
List colores;
Button imprimir,borrar;
Choice figuras;
Checkbox relleno;
Scrollbar lapizBar;
TextField lapizTex;
Panel panelIzq, panelDch, panelBot, panelDib;
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
public void init() {
// Creamos los paneles con sus layout managers
panelIzq = new Panel();
panelIzq.setLayout( new BorderLayout() );
panelDch = new Panel();
panelDch.setLayout( new BorderLayout() );
panelBot = new Panel();
panelBot.setLayout( new GridLayout( 4,0 ) );
panelDib = new Panel();
panelDib.setLayout( new BorderLayout() );
panelDib.setBounds( 1,1,200,20 );
// Construimos el lado izquierdo de la ventana
// Creamos la lista de colores
colores = new List( 6,false );
colores.add( "Rojo" );
colores.add( "Naranja" );
colores.add( "Amarillo" );
colores.add( "Verde" );
colores.add( "Azul" );
colores.add( "Morado" );
colores.add( "Negro" );
colores.add( "Blanco" );
// Añadimos la lista de colores al panel izquierdo
panelIzq.add("West", colores );
// Creamos un nuevo Canvas
dibujo = new miCanvas();
dibujo.setBounds( 0,0,100,100 );
// Añadimos el canvas al panel izquierdo
panelIzq.add( "Center",dibujo );
// Creamos los botones
borrar = new Button( "Borrar" );
imprimir = new Button( "Imprimir" );
83
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
figuras = new Choice();
figuras.add( "Cuadrado" );
figuras.add( "Círculo" );
figuras.add( "Triángulo" );
inte
lec
tua
l
Are
nas
relleno = new Checkbox( "Relleno" );
// Añadimos los botones a su propio panel
panelBot.add( borrar );
panelBot.add( imprimir );
panelBot.add( figuras );
panelBot.add( relleno );
// Añadimos el panel de botones al lado derecho
panelIzq.add( "East",panelBot );
// Creamos el area del lápiz
lapizLab = new Label( "Lápiz" );
lapizBar = new Scrollbar( Scrollbar.HORIZONTAL,1,1,1,10 );
lapizBar.setBounds( 1,1,100,5 );
lapizTex = new TextField( "1",8 );
// Añadimos las partes anteriores a su propio panel
panelDib.add( "North",lapizLab );
panelDib.add( "Center",lapizBar );
panelDib.add( "East",lapizTex );
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
// Añadimos el panel a la parte inferior
panelIzq.add( "South",panelDib );
// Construimos el lado derecho de la ventana
edicionLab = new Label( "Editor" );
edicion = new TextArea( "Aqui se puede escribir",8,30 );
// Añadimos la etiqueta y el área de texto al lado derecho
panelDch.add( "North",edicionLab );
panelDch.add( "South",edicion );
// Incorporamos los dos paneles al applet que utiliza
// FlowLayout por defecto
add( panelIzq );
add( panelDch );
}
}
// Creamos una clase para poder pintar una zona de dibujo y que
// se muestre el canvas. Pintamos un rectangulo alrededor suyo
class miCanvas extends Canvas {
public void paint( Graphics g ) {
int w = getSize().width;
int h = getSize().height;
g.drawRect( 0,0,w-1,h-1 );
g.drawString( "Canvas", ( w-g.getFontMetrics().stringWidth( "Canvas" ) )/2,10 );
}
}
84
Notas para el Curso de Java
FlowLayout
Es el más simple y el que se utiliza por defecto en todos los Paneles si no se fuerza el uso de alguno de los otros.
Los Componentes añadidos a un Panel con FlowLayout se encadenan en forma de lista. La cadena es horizontal, de
izquierda a derecha, y se puede seleccionar el espaciado entre cada Componente.
Por ejemplo, podemos poner un grupo de botones con la composición por defecto que proporciona FlowLayout:
inte
lec
tua
l
Are
nas
import java.awt.*;
import java.applet.Applet;
public class AwtFlow extends Applet {
Button boton1,boton2,boton3;
public void init() {
boton1 = new Button( "Aceptar" );
boton2 = new Button( "Abrir" );
boton3 = new Button( "Cerrar" );
add( boton1 );
add( boton2 );
add( boton3 );
}
}
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
Este código, AwtFlow.java, construye tres botones con un pequeño espacio de separación entre ellos.
85
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
BorderLayout
inte
lec
tua
l
Are
nas
La composición BorderLayout (de borde) proporciona un esquema más complejo de colocació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.
Con BorderLayout se podrían representar botones de dirección:
import java.awt.*;
import java.applet.Applet;
public class AwtBord extends Applet {
Button botonN,botonS,botonE,botonO,botonC;
public void init() {
setLayout( new BorderLayout() );
botonN
botonS
botonE
botonO
botonC
new
new
new
new
new
Button(
Button(
Button(
Button(
Button(
"Norte" );
"Sur" );
"Este" );
"Oeste" );
"Centro" );
"North",botonN );
"South",botonS );
"East",botonE );
"West",botonO );
"Center",botonC );
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
add(
add(
add(
add(
add(
}
=
=
=
=
=
}
86
Notas para el Curso de Java
GridLayout
La composición GridLayout proporciona gran flexibilidad para situar Componentes. El layout 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 necesarias para albergar los botones:
inte
lec
tua
l
Are
nas
import java.awt.*;
import java.applet.Applet;
public class AwtGrid extends Applet {
Button boton1,boton2,boton3,boton4,boton5,boton6;
public void init() {
setLayout( new GridLayout( 3,2 ) );
boton1
boton2
boton3
boton4
boton5
boton6
new
new
new
new
new
new
boton1
boton2
boton3
boton4
boton5
boton6
Button(
Button(
Button(
Button(
Button(
Button(
"1"
"2"
"3"
"4"
"5"
"6"
);
);
);
);
);
);
);
);
);
);
);
);
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
add(
add(
add(
add(
add(
add(
}
=
=
=
=
=
=
}
87
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
GridBagLayout
Es igual que la composición de GridLayout, con la diferencia que los Componentes no necesitan tener el mismo
tamaño. Es quizá el layout más sofisticado y versátil de los que actualmente soporta AWT.
En la figura siguiente vemos la imagen generada por un panel que usa el GridBagLayout para soportar diez
botones en su interior:
inte
lec
tua
l
Are
nas
import java.awt.*;
import java.applet.Applet;
public class AwtGBag extends Applet {
public void init() {
GridBagLayout gridbag = new GridBagLayout();
GridBagConstraints gbc = new GridBagConstraints();
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
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.gridheight = 2;
gbc.weighty = 1.0;
Button boton7 = new Button( "Botón 7" );
gridbag.setConstraints( boton7,gbc );
add( boton7 );
gbc.weighty = 0.0;
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.gridheight = 1;
Button boton8 = new Button( "Botón 8" );
88
Notas para el Curso de Java
gridbag.setConstraints( boton8,gbc );
add( boton8 );
Button boton9 = new Button( "Botón 9" );
gridbag.setConstraints( boton9,gbc );
add( boton9 );
}
inte
lec
tua
l
Are
nas
}
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
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 deberían crear métodos de conveniencia para hacer más fácil el posicionamiento de
los Componentes. 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;
public class AwtGBagConv extends Applet {
GridBagLayout gridbag = new GridBagLayout();
void addComponente( Component comp,int gridx,int gridy,
int gridw,int gridh ) {
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = gridx;
gbc.gridy = gridy;
gbc.gridwidth = gridw;
gbc.gridheight = gridh;
gridbag.setConstraints( comp,gbc );
add( comp );
}
public void init() {
setLayout( gridbag
addComponente( new
addComponente( new
addComponente( new
addComponente( new
addComponente( new
}
}
);
Label( "Nombre:" ),0,0,1,1 );
TextField( 12 ),1,0,2,1 );
TextArea( 5,20 ),0,1,2,2 );
Checkbox( "Sí?" ),2,1,1,1 );
List(),2,2,1,1 );
89
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
CardLayout
inte
lec
tua
l
Are
nas
Este es el tipo de composición que se utiliza cuando se necesita una zona de la ventana que permita colocar
distintos Componentes en esa misma zona. Este layout suele ir asociado con botones de lista (Choice), de tal modo que
cada selección determina el panel (grupo de componentes) que se presentarán.
En la figura siguiente mostramos el efecto de la selección sobre la apriencia de la ventana que contiene el panel
con la composición CardLayout:
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
import java.awt.*;
import java.awt.event.*;
import java.applet.Applet;
public class AwtCard extends Applet {
Panel card;
Choice c;
final static String PanelBoton = "Panel con Botones";
final static String PanelTexto = "Panel con Campo de Texto";
public void init() {
setLayout( new BorderLayout() );
Panel ac = new Panel();
c = new Choice();
c.add( PanelBoton );
c.add( PanelTexto );
// el auditor
c.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
CardLayout clLayout = (CardLayout)card.getLayout();
if (c.getSelectedIndex() == 0)
clLayout.first(card);
else
clLayout.last(card);
}
});
ac.add(c);
add("North", ac);
card = new Panel();
card.setLayout( new CardLayout() );
Panel p1 = new Panel();
90
Notas para el Curso de Java
p1.add( new Button( "Botón 1" ) );
p1.add( new Button( "Botón 2" ) );
p1.add( new Button( "Botón 3" ) );
Panel p2 = new Panel();
p2.add( new TextField( "Texto",20 ) );
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
}
}
inte
lec
tua
l
Are
nas
card.add( PanelBoton,p1 );
card.add( PanelTexto,p2 );
add( "Center",card );
91
(c) 2000 by José Abraham Arenas Barrios
inte
lec
tua
l
Are
nas
Notas para el Curso de Java
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
Gráficos del
AWT
en Java 1.1.x
92
Notas para el Curso de Java
Objetos Gráficos
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
inte
lec
tua
l
Are
nas
En páginas anteriores ya se ha mostrado cómo escribir applets, cómo lanzarlos y los fundamentos 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
importantes 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, doblebuffering, 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 argumento 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 tiempo 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
La pantalla en Java se incrementa de izquierda a derecha y de arriba hacia abajo, tal como muestra la figura:
Los pixels de la pantalla son pues: posición 0 + ancho de la pantalla - 1.
En los textos, el punto de inserción se encuentra en la línea base de la primera letra.
93
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
Métodos para Dibujos
Vamos a presentar métodos para dibujar varias figuras geométricas. Como estos métodos funcionan solamente
cuando son invocados por una instancia válida de la clase Graphics, por ejemplo podríamos declarar Graphics g, su
ámbito de aplicación se restringe a los componentes que se utilicen en los métodos paint() y update(). Normalmente los
métodos de dibujo de primitivas gráficas funcionan por pares: un método pinta la figura normal y el otro pinta la figura
rellena.
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
inte
lec
tua
l
Are
nas
drawLine( x1,y1,x2,y2 )
// dibuja una linea
drawRect( x,y,ancho,alto )
// dibuja un rectangulo
fillRect( x,y,ancho,alto )
// dibuja y rellena un rectangulo
clearRect( x,y,ancho.alto )
// limpia un area rectangular
drawRoundRect(x,y,ancho,alto,anchoArco,altoArco)// rect.con esquinas redondeadas
fillRoundRect( x,y,ancho,alto,anchoArco,altoArco )
draw3DRect( x,y,ancho,alto,boolean elevado )
fill3DRect( x,y,ancho,alto,boolean elevado )
drawOval( x,y,ancho,alto )
fillOval( x,y,ancho,alto )
drawArc( x,y,ancho,alto,anguloInicio,anguloArco )
fillArc( x,y,ancho,alto,anguloInicio,anguloArco )
drawPolygon( int[] puntosX,int[] puntosY[],numPuntos )
fillPolygon( int[] puntosX,int[] puntosY[],numPuntos )
drawString( string s,x,y )
drawChars( char data[],offset,longitud,x,y )
drawBytes( byte data[],offset,longitud,x,y )
copyArea( xSrc,ySrc,ancho,alto,xDest,yDest )
Lí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.
En el código de la aplicación Cuadrados.java, podemos observar el uso del método de dibujar líneas. Es el más
simple de todos los métodos gráficos, que pinta una línea recta de un pixel de ancho entre el punto inicial que se indica
y el final, calculado en función del ancho y alto de la región donde se pinta. En el siguiente applet, FigLinea.java,
podemos ver el método drawLine() en funcionamiento:
import java.awt.*;
import java.awt.event.*;
public class Cuadrados extends Frame {
float xA,yA,xB,yB,xC,yC,xD,yD;
float xA1,yA1,xB1,yB1,xC1,yC1,xD1,yD1;
float p,q,r;
int xcentro,ycentro;
static final int HOR_TAMANO = 300;
static final int VER_TAMANO = 300;
public Cuadrados() {
super("Cuadrados");
q = (float)0.05;
p = 1 - q;
r = (float)0.95 * ( HOR_TAMANO/2 );
94
Notas para el Curso de Java
xcentro = HOR_TAMANO / 2;
ycentro = VER_TAMANO / 2;
setSize( HOR_TAMANO,VER_TAMANO );
setVisible(true);
inte
lec
tua
l
Are
nas
// añadimos un auditor para el raton
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
repaint();
}
});
// auditor para el frame
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
setVisible(false);
System.exit(0);
}
});
}
void
xD =
xC =
yB =
yD =
paint(Graphics g) {
xcentro - r;
xcentro + r;
ycentro - r;
ycentro + r;
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
public
xA =
xB =
yA =
yC =
for (int i=0; i < 20; i++ ) {
g.drawLine( (int)xA,(int)yA,(int)xB,(int)yB
g.drawLine( (int)xB,(int)yB,(int)xC,(int)yC
g.drawLine( (int)xC,(int)yC,(int)xD,(int)yD
g.drawLine( (int)xD,(int)yD,(int)xA,(int)yA
xA1 = p*xA + q*xB;
yA1 = p*yA + q*yB;
xB1 = p*xB + q*xC;
yB1 = p*yB + q*yC;
xC1 = p*xC + q*xD;
yC1 = p*yC + q*yD;
xD1 = p*xD + q*xA;
yD1 = p*yD + q*yA;
xA = xA1;
yA = yA1;
xB = xB1;
yB = yB1;
xC = xC1;
yC = yC1;
);
);
);
);
xD = xD1;
yD = yD1;
}
}
public static void main( String args[] ) {
new Cuadrados();
}
}
95
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
Rectángulos
Los rectángulos, al igual que los óvalos, se definen por un punto de inicio de coordenadas x e y y el ancho y el
alto de la caja que circunscribirá a la figura.
Para dibujar un rectángulo en pantalla, basta con llamar al método drawRect() indicando 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:
inte
lec
tua
l
Are
nas
import java.awt.*;
import java.applet.Applet;
public class Rect1 extends Applet {
int ancho,alto;
public void init() {
Dimension d = getSize();
ancho = d.width;
alto = d.height;
repaint();
}
// devuelve el tamaño del applet
public void paint( Graphics g ) {
g.drawRect( 0,0,ancho-1,alto-1 );
}
}
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
En este caso hemos tenido que utilizar ancho-1 y alto-1, para que fuese visible todo el rectángulo y no solamente
la mitad, por causa de la referencia de coordenadas que utiliza Java con respecto a la pantalla.
El método drawRect() pinta un rectángulo abierto, si queremos un rectángulo relleno, tendremos que invocar al
método fillRect(), que tiene idéntica sintaxis. El código siguiente, Rect2.java, pinta un rectángulo relleno en el centro
del applet que lo contiene:
import java.awt.*;
import java.applet.Applet;
public class Rect2 extends Applet {
int apAncho;
int apAlto;
int x,y,ancho,alto;
public void init() {
Dimension d = getSize();
apAncho = d.width;
apAlto = d.height;
ancho = apAncho/3;
alto = apAlto/3;
x = (apAncho - ancho)/2;
y = (apAlto - alto)/2;
repaint();
}
public void paint( Graphics g ) {
g.drawRect( 0,0,apAncho-1,apAlto-1 );
g.fillRect( x,y,ancho-1,alto-1 );
}
}
Si queremos pintar un montón de rectángulos en posiciones y de tamaños aleatorios, al estilo de Mondrian,
también podemos hacerlo. Para ello utilizaremos el método Math.random() de la clase Math. Este método devuelve un
doble entre 0.0 y 1.0. El método aleatorio() sería:
private int aleatorio( int rango ) {
96
Notas para el Curso de Java
double retornoMath;
retornoMath = Math.random();
return( (int)( retornoMath * rango ) );
}
inte
lec
tua
l
Are
nas
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 realizarse 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;
public class Rect3 extends Applet {
int apAncho,apAlto;
int x,y,ancho,alto;
Color color;
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
public void init() {
Dimension d = getSize();
apAncho = d.width;
apAlto = d.height;
x = aleatorio( apAncho );
y = aleatorio( apAlto );
ancho = aleatorio( apAncho - x );
alto = aleatorio( apAlto - y );
color = new Color(
aleatorio(255),aleatorio(255),aleatorio(255) );
repaint();
}
public void paint( Graphics g ) {
g.setColor( color );
g.drawRect( 0,0,apAncho-1,apAlto-1 );
g.fillRect( x,y,ancho-1,alto-1 );
}
private int aleatorio( int rango ) {
double retornoMath;
retornoMath = Math.random();
return( (int)( retornoMath * rango ) );
}
}
Si se carga repetidas veces el applet, aparecerán diferentes rectángulos, con distintos colores 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;
public class Rect4 extends Applet {
97
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
int apAncho,apAlto;
int x,y,ancho,alto;
Color color;
int numRect = 100;
inte
lec
tua
l
Are
nas
public void init() {
Dimension d = getSize();
apAncho = d.width;
apAlto = d.height;
String s = getParameter( "Numero" );
if( s != null )
numRect = Integer.valueOf( s ).intValue();
repaint();
}
public void paint( Graphics g ) {
g.setColor( Color.black );
g.drawRect( 0,0,apAncho-1,apAlto-1 );
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
for( int i=0; i < numRect; i++ ) {
x = aleatorio( apAncho );
y = aleatorio( apAlto );
ancho = aleatorio( apAncho - x );
alto = aleatorio( apAlto - y );
color = new Color( aleatorio(255),aleatorio(255),
aleatorio(255) );
g.setColor( color );
g.fillRect( x,y,ancho-1,alto-1 );
}
}
private int aleatorio( int rango ) {
double retornoMath;
retornoMath = Math.random();
return( (int)( retornoMath * rango ) );
}
}
98
Notas para el Curso de Java
Círculos y Elipses
inte
lec
tua
l
Are
nas
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.
En el ejemplo siguiente, Ojos.java, podemos ver la réplica de la aplicacién conocida por los usuarios de Motif, en
la que los ojos siguen los movimientos del ratón cuando se encuentra dentro del campo del applet.
Cada uno de los métodos que nos permiten representar en pantalla elipses y arcos, requieren 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().
import java.awt.*;
import java.awt.event.*;
import java.applet.Applet;
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
public class Ojos extends Applet implements MouseListener, MouseMotionListener {
int Mx = -1;
int My = -1;
int OjoR1;
int OjoR2;
int Ancho;
int Alto;
int OjoIzq;
int OjoDch;
int OjoPY;
Color Pupila = Color.black;
Color Iris = Color.green.brighter();
Color Orbita = Color.white;
Image Imagen;
Graphics OjoCG;
public void init() {
setBackground( Color.darkGray );
Dimension d = getSize();
// Fijamos las variables que nos van a posicionar los
// ojos sobre el applet
OjoIzq = d.width >> 2;
OjoDch = OjoIzq * 3;
OjoPY = d.height >> 2;
OjoR2 = d.width >> 4;
OjoR1 = d.width >> 5;
Ancho = ( d.width >> 3 ) + OjoR1;
Alto = Ancho >> 1;
// registro de auditores de eventos
addMouseListener(this);
addMouseMotionListener(this);
}
public void update( Graphics g ) {
paint( g );
}
// Funcion auxiliar, para que no se desmanden los valores
99
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
// y no se los salgan los ojos de las orbitas
int swap(int i, int c) {
if ( i > c )
i = c;
else if(i<-c)
i = -c;
return( i );
}
inte
lec
tua
l
Are
nas
// Pintamos el ojo sobre el applet
void pintaOjo(Graphics g,int x ) {
// Fijamos los desplazamientos, las nuevas posiciones de
// referencia, en funcion de la posicion del cursor del
// raton, determinada por Mx y My
int dx = x-Mx;
int dy = OjoPY-My;
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
// Pintamos el ojo solamente bordeado, es decir, cerrado
if ( dx < Ancho && dx > -Ancho && dy < Alto && dy > -Alto ) {
g.setColor( getBackground() );
g.fillOval( x-Ancho,OjoPY-Alto,Ancho << 1,Alto << 1 );
g.setColor( getBackground().brighter() );
g.drawOval( x-Ancho,OjoPY-Alto,Ancho << 1,Alto << 1 );
}
else {
// Pintamos el hueco del ojo, por el que se movera el iris
g.setColor( Orbita );
g.fillOval( x-Ancho,OjoPY-Alto,Ancho << 1,Alto << 1 );
int y = OjoPY;
dx = swap( dx >> 3,OjoR1 << 1 );
dy = swap( dy >> 5,OjoR1 >> 1 );
if (Mx >= 0 && My >= 0) {
x -= dx;
y -= dy;
}
// Pintamos
g.setColor(
g.fillOval(
if (Mx >= 0
x -= ( dx
y -= dy;
}
el iris, sobre el que se movera la pupila
Iris );
x-OjoR2,y-OjoR2,OjoR2 << 1,OjoR2 << 1 );
&& My >= 0) {
>> 1 );
// Pintamos la pupila dentro del iris
g.setColor( Pupila );
g.fillOval( x-OjoR1,y-OjoR1,OjoR1 << 1,OjoR1 << 1 );
}
}
public void paint( Graphics g ) {
Dimension d = getSize();
// La primera vez que se llama a este metodo, todavia no
// hay nada, asi que creamos el soporte de los ojos
if ( Imagen == null || OjoCG == null ) {
Imagen = createImage( d.width,d.height >> 1 );
OjoCG = Imagen.getGraphics();
}
100
Notas para el Curso de Java
// Pintamos los ojos
OjoCG.setColor( getBackground() );
OjoCG.fillRect( 0,0,d.width,d.height );
pintaOjo( OjoCG,OjoIzq );
pintaOjo( OjoCG,OjoDch );
g.drawImage( Imagen,0,0,this );
}
inte
lec
tua
l
Are
nas
// auditores del mouse
// Cuando movemos el cursos dentro del applet, recibimos las
// coordenadas y repintamos el ojo
public void mouseMoved(MouseEvent e) {
Mx = e.getX();
My = e.getY();
repaint();
}
public void mouseDragged(MouseEvent e) {}
public void mouseClicked(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
// Si nos llega el evento de que el raton se ha salido del applet
// ponemos los todo en el centro
public void mouseExited(MouseEvent e) {
Mx = My = -1;
repaint();
}
public void mouseReleased(MouseEvent e) { }
public void mousePressed(MouseEvent e) {}
} // class
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
coordenadas 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.
import java.awt.Graphics;
import java.awt.Polygon;
public class Poligonos extends java.applet.Applet {
public void paint(Graphics g) {
int pX = {10, 30, 23, 78, 56, 47};
int pY = {54, 32, 45, 10, 78, 90};
int lon = pX.length;
Polygon poly = new Polygon(pX, pY, lon);
g.setColor(Color.black);
g.drawPolygon(poly);
g.setColor(Color.green);
g.fillPolygon(poly);
}
}
101
(c) 2000 by José Abraham Arenas Barrios
inte
lec
tua
l
Are
nas
Notas para el Curso de Java
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
Subprocesos
(Threads)
de Java
102
Notas para el Curso de Java
Threads en Java.
Flujo de programas.
inte
lec
tua
l
Are
nas
Programas de flujo único
Un programa de flujo único o mono-hilvanado (single-thread) utiliza un único flujo de control (thread) para
controlar su ejecución. Muchos programas no necesitan la potencia o utilidad de múltiples flujos de control. Sin
necesidad de especificar explícitamente que se quiere un único flujo de control, muchos de los applets y aplicaciones
son de flujo único.
Por ejemplo, en nuestra aplicación estándar de saludo:
public class HolaMundo {
static public void main( String args[] ) {
System.out.println( "Hola Mundo!" );
}
}
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
Aquí, cuando se llama a main(), la aplicación imprime el mensaje y termina. Esto ocurre dentro de un único
thread.
Programas de flujo múltiple
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 plantearse 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.
Si se ha utilizado un navegador con soporte Java, ya se habrá visto el uso de múltiples threads en Java. Habrá
observado que dos applet se pueden ejecutar al mismo tiempo, o que puede desplazar la página del navegador mientras
el applet continúa ejecutándose. Esto no significa que el applet utilice múltiples threads, sino que el navegador es
multithreaded.
Las aplicaciones (y applets) multithreaded utilizan muchos contextos de ejecución para 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 entrada en tiempo real.
Vamos a modificar nuestro programa de saludo creando tres threads individuales, que imprimen cada uno de
ellos su propio mensaje de saludo, MultiHola.java:
// Definimos unos sencillos threads. Se detendrán un rato
// antes de imprimir sus nombres y retardos
class TestTh extends Thread {
private String nombre;
private int retardo;
// Constructor para almacenar nuestro nombre
// y el retardo
public TestTh( String s,int d ) {
nombre = s;
retardo = d;
}
// El metodo run() es similar al main(), pero para
// threads. Cuando run() termina el thread muere
public void run() {
// Retasamos la ejecución el tiempo especificado
try {
sleep( retardo );
} catch( InterruptedException e ) {
;
}
103
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
// Ahora imprimimos el nombre
System.out.println( "Hola Mundo! "+nombre+" "+retardo );
}
}
public class MultiHola {
public static void main( String args[] ) {
TestTh t1,t2,t3;
Creamos los threads
= new TestTh( "Thread 1",(int)(Math.random()*2000) );
= new TestTh( "Thread 2",(int)(Math.random()*2000) );
= new TestTh( "Thread 3",(int)(Math.random()*2000) );
inte
lec
tua
l
Are
nas
//
t1
t2
t3
// Arrancamos los threads
t1.start();
t2.start();
t3.start();
}
}
CREACION Y CONTROL DE THREADS
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
Creación de un Thread
Hay dos modos de conseguir threads en Java. Una es implementando la interface Runnable, la otra es extender la
clase Thread.
La implementación de la interface Runnable es la forma habitual de crear threads. Las interfaces proporcionan al
programador una forma de agrupar el trabajo de infraestructura de una clase. Se utilizan para diseñar los requerimientos comunes al conjunto de clases a implementar. La interface define el trabajo y la clase, o clases, que implementan la
interface realizan ese trabajo. Los diferentes grupos de clases que implementen la interface tendrán que seguir las
mismas reglas de funcionamiento.
Hay una cuantas diferencias entre interface y clase. Primero, una interface solamente puede 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 sobrecarga 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 oportunidad de extender la clase MiThread, si fuese necesario. La mayoría de las clases creadas que necesiten
104
Notas para el Curso de Java
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:
inte
lec
tua
l
Are
nas
package java.lang;
public interface Runnable {
public abstract void run() ;
}
Y esto es todo lo que hay sobre la interface Runnable. Como se ve, una interface sólo proporciona un diseño para
las clases que vayan a ser implementadas. En el caso de Runnable, fuerza a la definición del método run(), por lo tanto,
la mayor parte del trabajo se hace en la clase Thread. Un vistazo un poco más profundo a la definición de la clase
Thread nos da idea de lo que realmente está pasando:
public class Thread implements Runnable {
...
public void run() {
if( tarea != null )
tarea.run() ;
}
}
...
}
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
De este trocito de código se desprende que la clase Thread también implemente la interface Runnable. tarea.run()
se asegura de que la clase con que trabaja (la clase que va a ejecutarse como un thread) no sea nula y ejecuta el método
run() de esa clase. Cuando esto suceda, el método run() de la clase hará que corra como un thread.
Arranque de un Thread
Las aplicaciones ejecutan main() tras arrancar. Esta es la razón de que main() sea el 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 nuestro 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 controlaremos 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 (pasada a través
del constructor):
sleep( retardo );
El método sleep() simplemente le dice al thread que duerma durante los milisegundos especificados. Se debería
utilizar sleep() cuando se pretenda retrasar la ejecución del thread. sleep() no consume recursos del sistema mientras el
thread duerme. De esta forma otros threads pueden seguir funcionando. Una vez hecho el retardo, se imprime el
mensaje "Hola Mundo!" con el nombre del thread y el retardo.
Suspensión de un Thread
Puede resultar útil suspender la ejecución de un thread sin marcar un límite de tiempo. Si, por ejemplo, está
construyendo un applet con un thread de animación, querrá permitir al usuario la opción de detener la animación hasta
que quiera continuar. No se trata de terminar 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 indefinidamente y para volver a
activarlo de nuevo necesitamos realizar una invocación al método resume():
105
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
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();
t1.isAlive();
inte
lec
tua
l
Are
nas
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.
En nuestro ejemplo, no necesitamos detener explícitamente el thread. Simplemente se le deja terminar. Los
programas más complejos necesitarán un control sobre cada uno de los threads que lancen, el método stop() puede
utilizarse en esas situaciones.
Si se necesita, se puede comprobar si un thread está vivo o no; considerando vivo un thread que ha comenzado y
no ha sido detenido.
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.
ARRANCAR Y PARAR THREADS
Ahora que ya hemos visto por encima como se arrancan, paran y manipulan threads, vamos a mostrar un ejemplo
un poco más gráfico, se trata de un contador, cuyo código (App1Thread.java) es el siguiente:
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
public class App1Thread extends Applet implements Runnable {
Thread t;
int contador;
public void init() {
contador = 0;
t = new Thread( this );
// auditor del mouse
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
t.stop();
}
});
t.start();
}
public void run() {
while( true ) {
contador++;
repaint();
try {
t.sleep( 10 );
} catch( InterruptedException e ) {
;
};
}
}
public void paint( Graphics g ) {
g.drawString( Integer.toString( contador ),10,10 );
System.out.println( "Contador = "+contador );
}
public void stop() {
106
Notas para el Curso de Java
t.stop();
}
}
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
inte
lec
tua
l
Are
nas
Este applet arranca un contador en 0 y lo incrementa, presentando su salida tanto en la pantalla gráfica como en
la consola. Una primera ojeada al código puede dar la impresión de que el programa empezará a contar y presentará
cada número, pero no es así. Una revisión más profunda del flujo de ejecución del applet, nos revelará su verdadera
identidad.
En este caso, la clase App1Thread está forzada a implementar Runnable sobre la clase Applet que extiende.
Como en todos los applets, el método init() es el primero que se ejecuta. En init(), la variable contador se inicializa a
cero y se crea una nueva instancia de la clase Thread. Pasándole this al constructor de Thread, el nuevo thread ya
conocerá al objeto que va a correr. En este caso this es una referencia a App1Thread. Después de que hayamos creado
el thread, necesitamos arrancarlo. La llamada a start(), llamará a su vez al método run() de nuestra clase, es decir, a
App1Thread.run(). La llamada a start() retornará con éxito y el thread comenzará a ejecutarse en ese instante. Observar
que el método run() es un bucle infinito. Es infinito porque una vez que se sale de él, la ejecución del thread se detiene.
En este método se incrementará la variable contador, se duerme 10 milisegundos y envía una petición de refresco del
nuevo valor al applet.
Es muy importante dormirse en algún lugar del thread, porque sino, el thread consumirá todo el tiempo de la
CPU para su proceso y no permitirá que entren otros métodos de 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 presentarán los números
consecutivos o no, porque el incremento de la variable contador es independiente 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 convertirá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.
SUSPENDER Y REANUDAR THREADS
Una vez que se para un thread, ya no se puede rearrancar con el comando start(), debido a que stop() concluirá la
ejecución del thread. Por ello, en ver de parar el thread, lo que podemos hacer es dormirlo, llamando al método sleep().
El thread estará suspendido un cierto tiempo y luego reanudará su ejecución cuando el límite fijado se alcance. Pero
esto no es útil cuando se necesite que el thread reanude su ejecución ante la presencia de ciertos eventos. En estos
casos, el método suspend() permite que cese la ejecución del thread y el método resume() permite que un método
suspendido reanude su ejecución. En la siguiente versión de nuestra clase contador, App2Thread.java, modificamos el
applet para que utilice los métodos suspend() y resume():
public class App2Thread extends Applet implements Runnable {
Thread t;
int contador;
boolean suspendido;
...
public void mousePressed(MouseEvent e) {
if( suspendido )
t.resume();
else
t.suspend();
suspendido = !suspendido;
}
...
Para controlar el estado del applet, hemos introducido la variable suspendido. Diferenciar los distintos estados de
ejecución del applet es importante porque algunos métodos pueden generar excepciones si se llaman desde un estado
erróneo. Por ejemplo, si el applet ha sido arrancado y se detiene con stop(), si se intenta ejecutar el método start(), se
generará una excepción IllegalThreadStateException.
107
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
ESTADOS DE UN THREAD
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
inte
lec
tua
l
Are
nas
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 llamada a cualquier otro método carece de sentido y lo único que provocará será la
generación de una excepción de tipo IllegalThreadStateException.
Ejecutable
Ahora veamos las dos línea de código que se presentan a continuación:
Thread MiThread = new MiClaseThread();
MiThread.start();
La llamada al método start() creará los recursos del sistema necesarios para que el thread puede ejecutarse, lo
incorpora a la lista de procesos disponibles para ejecución del sistema y llama al método run() del thread. En este
momento nos encontramos en el estado "Ejecutable" del diagrama. Y este estado es Ejecutable y no En Ejecución,
porque cuando el thread está aquí no esta corriendo. Muchos ordenadores tienen solamente un procesador lo que hace
imposible que todos los threads estén corriendo al mismo tiempo. Java implementa un tipo de scheduling o lista de
procesos, que permite que el procesador sea compartido entre todos los procesos o threads que se encuentran en la lista.
Sin embargo, para nuestros propósitos, y en la mayoría de los casos, se puede considerar que este estado es realmente
un estado "En Ejecución", porque la impresión que produce ante nosotros es que todos los procesos se ejecutan al
mismo tiempo.
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á Parado.
Por ejemplo, en el trozo de código siguiente:
Thread MiThread = new MiClaseThread();
MiThread.start();
108
Notas para el Curso de Java
try {
MiThread.sleep( 10000 );
} catch( InterruptedException e ) {
;
}
la línea de código que llama al método sleep():
MiThread.sleep( 10000 );
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
inte
lec
tua
l
Are
nas
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 ejemplo, si el thread ha sido puesto a dormir, una vez
transcurridos los milisegundos que se especifiquen, él solo se despierta y vuelve a estar en estado Ejecutable. Llamar al
método resume() mientras esté el thread durmiendo no serviría para nada.
Los métodos de recuperación del estado Ejecutable, en función de la forma de llegar al estado Parado del thread,
son los siguientes:
Si un thread está dormido, pasado el lapso de tiempo
Si un thread está suspendido, luego de una llamada al método resume()
Si un thread está bloqueado en una entrada/salida, una vez que el comando E/S concluya su ejecución
Si un thread está esperando por una condición, cada vez que la variable que controla esa condición varíe debe
llamarse a notify() o notifyAll()
Muerto
Un thread se puede morir de dos formas: por causas naturales o porque lo maten (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 complete el bucle y run()
concluya.
También se puede matar en cualquier momento un thread, invocando a su método stop(). En el trozo de código
siguiente:
Thread MiThread = new MiClaseThread();
MiThread.start();
try {
MiThread.sleep( 10000 );
} catch( InterruptedException e ) { ; }
MiThread.stop();
se crea y arranca el thread MiThread, lo dormimos durante 10 segundos y en el momento de despertarse, la
llamada a su método stop(), lo mata.
El método stop() envía un objeto ThreadDeath al thread que quiere detener. Así, cuando 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 ejemplo, cuando se minimiza la ventana del navegador
o cuando se cambia de página.
El método isAlive()
La interface de programación de la clase Thread incluye el método isAlive(), que 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".
109
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
PRIORIDADES, DEMONIOS...
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
inte
lec
tua
l
Are
nas
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 variables 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()).
Diferencia de threads con fork()
fork() en Unix crea un proceso hijo que tiene su propia copia de datos y código del padre. Esto funciona correctamente si estamos sobrados de memoria y disponemos de una CPU poderosa, y siempre que mantengamos el número de
procesos hijos dentro de un límite manejable, porque se hace un uso intensivo de los recursos del sistema. Los applets
Java no pueden lanzar ningún proceso en el cliente, porque eso sería una fuente de inseguridad y no está permitido. Las
aplicaciones y los applets deben utilizar threads.
La multi-tarea pre-emptiva tiene sus problemas. Un thread puede interrumpir a otro en cualquier momento, de ahí
lo de pre-emptive. Imaginarse lo que pasaría si un thread está escribiendo 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 bloqueo 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 incrementar la respuesta del
ordenador ante el usuario cuando se encuentra realizando complicados cálculos y no puede atender a la entrada de
usuario. Estos cálculos se pueden realizar en segundo plano, o realizar varios en primer plano (música y animaciones)
sin que se dé apariencia de pérdida de rendimiento.
110
Notas para el Curso de Java
COMUNICACION ENTRE THREADS
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
inte
lec
tua
l
Are
nas
Otra clave para el éxito y la ventaja de la utilización de múltiples threads en una aplicación, o aplicación
multithreaded, es que pueden comunicarse entre sí. Se pueden diseñar threads para utilizar objetos comunes, que cada
thread puede manipular independientemente de los otros threads.
El ejemplo clásico de comunicación de threads es un modelo productor/consumidor. Un thread produce una
salida, que otro thread usa (consume), sea lo que sea esa salida. Vamos entonces a crear un productor, que será un
thread que irá sacando caracteres por su salida; crearemos también un consumidor que ira recogiendo los caracteres
que vaya sacando el productor y un monitor que controlará el proceso de sincronización entre los threads. Funcionará
como una tubería, insertando el productor caracteres en un extremos y leyéndolos el consumidor en el otro, con el
monitor siendo la propia tubería.
Productor
El productor extenderá la clase Thread, y su código es el siguiente:
class Productor extends Thread {
private Tuberia tuberia;
private String alfabeto = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public Productor( Tuberia t ) {
// Mantiene una copia propia del objeto compartido
tuberia = t;
}
public void run() {
char c;
// Mete 10 letras en la tubería
for( int i=0; i < 10; i++ )
{
c = alfabeto.charAt( (int)(Math.random()*26 ) );
tuberia.lanzar( c );
// Imprime un registro con lo añadido
System.out.println( "Lanzado "+c+" a la tuberia." );
// Espera un poco antes de añadir más letras
try {
sleep( (int)(Math.random() * 100 ) );
} catch( InterruptedException e ) {
;
}
}
}
}
Notar que creamos una instancia de la clase Tuberia, y que se utiliza el método tuberia.lanzar() para que se vaya
construyendo la tubería, en principio de 10 caracteres.
Consumidor
Veamos ahora el código del consumidor, que también extenderá la clase Thread:
111
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
class Consumidor extends Thread {
private Tuberia tuberia;
public Consumidor( Tuberia t ) {
// Mantiene una copia propia del objeto compartido
tuberia = t;
}
inte
lec
tua
l
Are
nas
public void run() {
char c;
// Consume 10 letras de la tubería
for( int i=0; i < 10; i++ ) {
c = tuberia.recoger();
// Imprime las letras retiradas
System.out.println( "Recogido el caracter "+c );
// Espera un poco antes de coger más letras
try {
sleep( (int)(Math.random() * 2000 ) );
} catch( InterruptedException e ) {
;
}
}
}
}
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
En este caso, como en el del productor, contamos con un método en la clase Tuberia, tuberia.recoger(), para
manejar la información.
Monitor
Una vez vistos el productor de la información y el consumidor, nos queda por ver qué es 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 importantes 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;
// Método para retirar letras del buffer
public synchronized char recoger() {
// No se puede consumir si el buffer está vacío
while( estaVacia == true )
{
try {
wait(); // Se sale cuando estaVacia cambia a false
} catch( InterruptedException e ) {
;
}
}
// Decrementa la cuenta, ya que va a consumir una letra
siguiente--;
// Comprueba si se retiró la última letra
if( siguiente == 0 )
estaVacia = true;
// El buffer no puede estar lleno, porque acabamos de consumir
estaLlena = false;
notify();
112
Notas para el Curso de Java
// Devuelve la letra al thread consumidor
return( buffer[siguiente] );
}
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
inte
lec
tua
l
Are
nas
// Método para añadir letras al buffer
public synchronized void lanzar( char c ) {
// Espera hasta que haya sitio para otra letra
while( estaLlena == true )
{
try {
wait(); // Se sale cuando estaLlena cambia a false
} catch( InterruptedException e ) {
;
}
}
// Añade una letra en el primer lugar disponible
buffer[siguiente] = c;
// Cambia al siguiente lugar disponible
siguiente++;
// Comprueba si el buffer está lleno
if( siguiente == 6 )
estaLlena = true;
estaVacia = false;
notify();
}
}
En la clase Tuberia vemos dos características importantes: los miembros dato (buffer[]) son privados, y los
métodos de acceso (lanzar() y recoger()) son sincronizados.
Aquí vemos que la variable estaVacia es un semáforo, como los de toda la vida. La 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 problemas; por ejemplo, si el consumidor intenta retirar datos de un buffer
vacío, obtendrá excepciones innecesarias, o se bloqueará el proceso.
Los métodos sincronizados de acceso impiden que los productores y consumidores corrompan 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.
Ahora que ya tenemos un productor, un consumidor y un objeto compartido, necesitamos una aplicación que
arranque los threads y que consiga que todos hablen con el mismo objeto que están compartiendo. Esto es lo que hace
el siguiente trozo de código, del fuente TubTest.java:
class TubTest {
public static void main( String args[] ) {
Tuberia t = new Tuberia();
Productor p = new Productor( t );
Consumidor c = new Consumidor( t );
p.start();
c.start();
}
113
Notas para el Curso de Java
(c) 2000 by José Abraham Arenas Barrios
}
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
inte
lec
tua
l
Are
nas
Compilando y ejecutando esta aplicación, podremos observar nuestro modelo el pleno funcionamiento.
Monitorización del Productor
Los programas productor/consumidor a menudo emplean monitorización remota, que permite al consumidor
observar el thread del productor interaccionando con un usuario o con otra parte del sistema. Por ejemplo, en una red,
un grupo de threads productores podrían trabajar cada uno en una workstation. Los productores imprimirían documentos, almacenando una entrada en un registro (log). Un consumidor (o múltiples consumidores) podría procesar el
registro y realizar durante la noche un informe de la actividad de impresión del día anterior.
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).
Peer, es un observador general del sistema.
114
inte
lec
tua
l
Are
nas
Notas para el Curso de Java
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
Programación de
Comunicaciones
en Redes en Java
115
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
Comunicaciones en Java
COMUNICACIONES EN UNIX
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
inte
lec
tua
l
Are
nas
El sistema de Entrada/Salida de Unix sigue el paradigma que normalmente se designa como Abrir-Leer-EscribirCerrar. 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 conseguir
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.
Cuando se incorporan las características a Unix de comunicación entre procesos (IPC) y el manejo de redes, la
idea fue implementar la interface con IPC similar a la que se estaba utilizando para la entrada/salida de ficheros, es
decir, siguiendo el paradigma del párrafo anterior. En Unix, un proceso tiene un conjunto de descriptores de entrada/
salida desde donde Leer y por donde Escribir. Estos descriptores pueden estar referidos a ficheros, dispositivos, o
canales de comunicaciones (sockets). El ciclo de vida de un descriptor, aplicado a un canal de comunicación (socket),
está determinado por tres fases (siguiendo el paradigma):
•Creación, apertura del socket
•Lectura y Escritura, recepción y envío de datospor el socket
•Destrucción, cierre del socket
La interface IPC en Unix-BSD está implementada sobre los protocolos de red TCP y UDP. Los destinatarios de
los mensajes se especifican como direcciones de socket; cada dirección de socket es un identificador de comunicación
que consiste en una dirección Internet y un número de puerto.
Las operaciones IPC se basan en pares de sockets. Se intercambian información transmitiendo datos a través de
mensajes que circulan entre un socket en un proceso y otro socket en otro proceso. Cuando los mensajes son enviados,
se encolan en el socket hasta que el protocolo de red los haya transmitido. Cuando llegan, los mensajes son encolados
en el socket de recepción hasta que el proceso que tiene que recibirlos haga las llamadas necesarias para recoger esos
datos.
SOCKETS
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.
El tipo de sockets describe la forma en la que se transfiere información a través de ese socket.
Sockets Stream (TCP, Transport Control Protocol)
Son un servicio orientado a conexión donde los datos se transfieren sin encuadrarlos en registros o bloques. Si se
rompe la conexión entre los procesos, éstos serán informados.
El protocolo de comunicaciones con streams es un protocolo orientado a conexión, ya que para establecer una
comunicación utilizando el protocolo TCP, hay que establecer en primer lugar una conexión entre un par de sockets.
Mientras uno de los sockets atiende peticiones de conexión (servidor), el otro solicita una conexión (cliente). Una vez
que los dos sockets estén conectados, se pueden utilizar para transmitir datos en ambas direcciones.
Sockets Datagrama (UDP, User Datagram Protocol)
Son un servicio de transporte sin conexión. Son más eficientes que TCP, pero no está garantizada la fiabilidad.
Los datos se envían y reciben en paquetes, cuya entrega no está garantizada. Los paquetes pueden ser duplicados,
perdidos o llegar en un orden diferente al que se envió.
El protocolo de comunicaciones con datagramas es un protocolo sin conexión, es decir, cada vez que se envíen
datagramas es necesario enviar el descriptor del socket local y la dirección del socket que debe recibir el datagrama.
Como se puede ver, hay que enviar datos adicionales cada vez que se realice una comunicación.
Sockets Raw
Son sockets que dan acceso directo a la capa de software de red subyacente o a protocolos de más bajo nivel. Se
utilizan sobre todo para la depuración del código de los protocolos.
Diferencias entre Sockets Stream y Datagrama
Ahora se nos presenta un problema, ¿qué protocolo, o tipo de sockets, debemos usar - UDP o TCP? La decisión
depende de la aplicación cliente/servidor que estemos escribiendo. Vamos a ver algunas diferencias entre los protoco116
Notas para el Curso de Java
inte
lec
tua
l
Are
nas
los para ayudar en la decisión.
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 establecer 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.
En UDP hay un límite de tamaño de los datagramas, establecido en 64 kilobytes, que se pueden enviar a una
localización determinada, mientras que TCP no tiene límite; una vez que se ha establecido la conexión, el par de
sockets funciona como los streams: todos los datos se leen inmediatamente, en el mismo orden en que se van recibiendo.
UDP es un protocolo desordenado, no garantiza que los datagramas que se hayan enviado sean recibidos en el
mismo orden por el socket de recepción. Al contrario, TCP es un protocolo 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/servidor en sistemas distribuidos montados sobre redes de área local.
USO DE SOCKETS
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
Podemos pensar que un Servidor Internet es un conjunto de sockets que proporciona capacidades 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 sistema se indican en el fichero /etc/services, y
algunos ejemplos son:
daytime
ftp
telnet
smtp
http
13/udp
21/tcp
23/tcp
25/tcp
80/tcp
telnet
mail
La primera columna indica el nombre del servicio. La segunda columna indica el puerto y el protocolo que está
asociado al servicio. La tercera columna es un alias del servicio; por ejemplo, el servicio smtp, también conocido como
mail, es la implementación del servicio de correo electrónico.
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.
117
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
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.mx
inte
lec
tua
l
Are
nas
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.mx 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.mx a 205.216.146.71, lo que nos permite teclear www.yahoo.com.mx, en lugar de tener
que recordar su dirección IP.
Si queremos obtener la dirección IP real de la red en que estamos corriendo, podemos realizar llamadas a los
métodos getLocalHost() y getAddress(). Primero, getLocalHost() nos devuelve un objeto iNetAddress, que si usamos
con getAddress() generará un array con los cuatro bytes de la dirección IP, por ejemplo:
InetAddress direccion = InetAddress.getLocalHost();
byte direccionIp[] = direccion.getAddress();
Si la dirección de la máquina en que estamos corriendo es 150.150.112.145, entonces:
direccionIp[0]
direccionIp[1]
direccionIp[2]
direccionIp[3]
=
=
=
=
150
150
112
145
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
Una cosa interesante en este punto es que una red puede mapear muchas direcciones IP. Esto puede ser necesario
para un Servidor Web, como Yahoo, que tiene que soportar grandes cantidades de tráfico y necesita más de una
dirección IP para poder atender a todo ese tráfico. El nombre interno para la dirección 205.216.146.71, por ejemplo, es
www7.yahoo.com. El DNS puede trasladar una lista de direcciones IP asignadas a Yahoo en www.yahoo.com. Esto es
una cualidad útil, pero por ahora abre un agujero en cuestión de seguridad.
Ya conocemos la dirección IP, nos falta el número del puerto. Si no se indica nada, 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.mx: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 suficientemente difíciles de recordar como para añadirle encima el número del
puerto.
Si necesitamos otro protocolo, como:
ftp://ftp.microsoft.com
el puerto se derivará de ese protocolo. Así el puerto FTP de Microsoft es el 21, según su fichero services. La
primera parte, antes de los dos puntos, de la URL, indica el protocolo que se quiere utilizar en la conexión con el
servidor. El protocolo http (HyperText Transmission Protocol), es el utilizado para manipular documentos Web. Y si
no se especifica ningún documento, muchos servidores están configurados para devolver un documento de nombre
index.html.
Con todo esto, Java permite los siguientes cuatro constructores para la clase URL:
public URL( String spec ) throws MalformedURLException;
public URL( String protocol,String host,int port,String file ) throws
MalformedURLException;
public URL( String protocol,String host,String file ) throws MalformedURLException;
public URL( URL context,String spec ) throws MalformedURLException;
Así que podríamos especificar todos los componenetes del URL como en:
URL( "http","www.yahoo.com.mx","80","index.html" );
o dejar que los sistemas utilicen todos los valores por defecto que tienen definidos, como en:
URL( "http://www.yahoo.com.mx" );
y en los dos casos obtendríamos la visualización de la página principal de Yahoo en nuestro navegador.
118
Notas para el Curso de Java
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.
Dominio Unix
Dominio Internet
inte
lec
tua
l
Are
nas
Bajo Unix, hay dos dominios, uno para comunicaciones internas al sistema y otro para comunicaciones entre
sistemas.
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.
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 establecidas 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
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
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.
119
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
MODELO DE COMUNICACIONES CON JAVA
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
inte
lec
tua
l
Are
nas
En Java, crear una conexión socket TCP/IP se realiza directamente con el paquete java.net. A continuación
mostramos un diagrama de lo que ocurre en el lado del cliente y del servidor:
El modelo de sockets más simple es:
1
El servidor establece un puerto y espera durante un cierto tiempo (timeout segundos), a que el cliente
establezca la conexión. Cuando el cliente solicite una conexión, el servidor abrirá la conexión socket con el método
accept().
2
El cliente establece una conexión con la máquina host a través del puerto que se designe en puerto#
3
El cliente y el servidor se comunican con manejadores InputStream y OutputStream
Hay una cuestión al respecto de los sockets, que viene impuesta por la implementación del sistema de seguridad
de Java. Actualmente, los applets sólo pueden establecer conexiones con el nodo desde el cual se transfirió su código.
Esto está implementado en el JDK y en el intérprete de Java de Netscape y de Internet Explorer. Esto reduce en gran
manera la flexibilidad de las fuentes de datos disponibles para los applets. El problema si se permite que un applet se
conecte a cualquier máquina de la red, es que entonces se podrían utilizar los applets para inundar la red desde un
computadora con un cliente Netscape del que no se sospecha y sin ninguna posibilidad de rastreo.
Una solución alternativa es ejecutar un servlet, es decir un applet que corre del lado del servidor y acepta comunicación desde applets normales.
120
Notas para el Curso de Java
APERTURA DE SOCKETS
Si estamos programando un cliente, el socket se abre de la forma:
Socket miCliente;
miCliente = new Socket( "maquina",numeroPuerto );
inte
lec
tua
l
Are
nas
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 como
email, ftp o http. Para las aplicaciones que se desarrollen, asegurarse de seleccionar un puerto por encima del 1023.
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 );
}
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
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 clientes potenciales y poder aceptar esas conexiones:
Socket socketServicio = null;
try {
socketServicio = miServicio.accept();
} catch( IOException e ) {
System.out.println( e );
}
CIERRE DE SOCKETS
Siempre deberemos cerrar los canales de entrada y salida que se hayan abierto durante la ejecución de la aplicación. En la parte del cliente:
try {
salida.close();
entrada.close();
miCliente.close();
} catch( IOException e ) {
System.out.println( e );
}
Y en la parte del servidor:
try {
salida.close();
entrada.close();
socketServicio.close();
miServicio.close();
} catch( IOException e ) {
System.out.println( e );
}
121
Notas para el Curso de Java
(c) 2000 by José Abraham Arenas Barrios
CREACION DE STREAMS
Creación de Streams de Entrada
En la parte cliente de la aplicación, se puede utilizar la clase DataInputStream para crear un stream de entrada
que esté listo a recibir todas las respuestas que el servidor le envíe.
inte
lec
tua
l
Are
nas
DataInputStream entrada;
try {
entrada = new DataInputStream( miCliente.getInputStream() );
} catch( IOException e ) {
System.out.println( e );
}
La clase DataInputStream permite la lectura de líneas de texto y tipos de datos primitivos de Java de un modo
altamente portable; dispone de métodos para leer todos esos tipos como: read(), readChar(), readInt(), readDouble() y
readLine(). Deberemos utilizar la función que creamos necesaria dependiendo del tipo de dato que esperemos recibir
del servidor.
En el lado del servidor, también usaremos DataInputStream, pero en este caso para recibir las entradas que se
produzcan de los clientes que se hayan conectado:
DataInputStream entrada;
try {
entrada =
new DataInputStream( socketServicio.getInputStream() );
} catch( IOException e ) {
System.out.println( e );
}
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
Creación de Streams de Salida
En el lado del cliente, podemos crear un stream de salida para enviar información al socket del servidor utilizando las clases PrintStream o DataOutputStream:
PrintStream salida;
try {
salida = new PrintStream( miCliente.getOutputStream() );
} catch( IOException e ) {
System.out.println( e );
}
La clase PrintStream tiene métodos para la representación textual de todos los datos primitivos de Java. Sus
métodos write y println() tienen una especial importancia en este aspecto. No obstante, para el envío de información al
servidor también podemos utilizar DataOutputStream:
DataOutputStream salida;
try {
salida = new DataOutputStream( miCliente.getOutputStream() );
} catch( IOException e ) {
System.out.println( e );
}
La clase DataOutputStream permite escribir cualquiera de los tipos primitivos de Java, muchos de sus métodos
escriben un tipo de dato primitivo en el stream de salida. De todos esos métodos, el más útil quizás sea writeBytes().
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.
122
Notas para el Curso de Java
Cliente/Servidor TCP-IP
Este ejemplo demuestra como escribir clases en Java para implementar un cliente y un servidor en Java. La idea
es sencilla, se pone un servidor con un archivo y es capaz de recibir peticiones de múltiples clientes. Una vez que un
cliente se conecta con el servidor, este último le envia un archivo de datos. El cliente recibe el archivo de datos y se
desconecta del servidor.
import java.net.*;
import java.io.*;
inte
lec
tua
l
Are
nas
/*
Este programa implementa un servidor de archivos multitareas para que pueda
atender a multiples clientes
Autor: José Abraham Arenas Barrios
Fecha: Mayo 2000.
*/
public class ThreadedFileHandler extends Thread {
Socket inSocket;
int count;
byte buf[] = new byte[1024];
int len;
String fileName;
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
// constructor
ThreadedFileHandler(Socket i, int c) {
inSocket = i;
count = c;
fileName = "planet.jpg";
}
public void run() {
try {
//FileInputStream istream = new FileInputStream("planet.jpg");
PrintStream out = new PrintStream(inSocket.getOutputStream());
File inputFile = new File(fileName);
FileInputStream fis = new FileInputStream(inputFile);
while ((len = fis.read(buf)) != -1) {
out.write(buf, 0, len);
}
//out.print(istream);
// cerrar el archivo y el socket de entrada
fis.close();
inSocket.close();
}
catch(Exception e) {
System.out.println("Exception: " + e);
}
}
} // class ThreadedFileHandler
123
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
/*
Este programa utiliza instancias de ThreadedFileHandler para poder llevar a cabo el
servicio
Autor: José Abraham Arenas Barrios
Fecha: Mayo 2000.
*/
import java.net.*;
inte
lec
tua
l
Are
nas
public class ThreadServer {
public static void main(String args[]) {
int i=0;
try {
ServerSocket s = new ServerSocket(1032);
System.out.println("Servidor en ejecución...esperando por clientes");
System.out.println("nombre del Host : " +
(InetAddress.getLocalHost()).getHostName());
System.out.println("escuchando peticiones en el puerto #: " +
s.getLocalPort());
System.out.println(s.toString());
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
for (;;) {
Socket inSocket = s.accept();
System.out.println("*********************************");
// se imprime cierta informacion del socket cliente
System.out.println("Puerto del cliente: " + inSocket.getLocalPort());
System.out.println("Host del cliente : " +
(inSocket.getInetAddress()).getHostAddress());
System.out.println("*********************************");
new ThreadedFileHandler(inSocket, i).start();
i++;
System.out.println("Número de clientes servidos: " + i);
}
}
catch(Exception e) {
System.out.println("Excepción: " + e);
}
}
} // class ThreadServer
124
Notas para el Curso de Java
/* Este programa implementa un cliente
Autor: José Abraham Arenas Barrios, Fecha: Mayo 2000. */
import java.net.*;
import java.io.*;
Socket clientSocket;
DataInputStream dis;
File outputFile;
FileOutputStream fos;
String fileName;
inte
lec
tua
l
Are
nas
public class MMClient {
byte buf[] = new byte[1024];
int len=0;
long countBytes = 0;
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
// constructor
public MMClient() {
// seleccionar el archivo a leer del servidor
fileName = "planet-get.jpg";
// conectarse con el servidor
try {
clientSocket = new Socket(InetAddress.getLocalHost(), 1032);
PrintStream ps = new PrintStream(clientSocket.getOutputStream());
// disponer de un medio de comunicacion con el server
dis = new DataInputStream(clientSocket.getInputStream());
}
catch (Exception e) {
System.err.println("Excepcion: " + e.getMessage());
}
}
public void showClientSocketInfo() {
if (clientSocket != null) {
// se imprime cierta informacion del socket
System.out.println("Puerto del cliente : " + clientSocket.getLocalPort());
try {
System.out.println("Host del cliente
: " +
(InetAddress.getLocalHost()).getHostName());
}
catch(UnknownHostException e) {
System.err.println("Excepcion: " + e.getMessage());
}
}
}
public synchronized void transferFile() {
try {
showClientSocketInfo();
// disponer de un archivo para escribir los datos del flujo del socket
outputFile = new File(fileName);
fos = new FileOutputStream(outputFile);
// leer datos que provienen del socket y escribirlos al archivo
System.out.println("Cliente aceptado, recibiendo archivo: " + fileName);
while ((len = dis.read(buf)) != -1) {
fos.write(buf, 0, len);
fos.flush();
countBytes += len; // cuenta bytes transferidos
}
125
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
// cerrar el archivo y el socket
fos.close();
clientSocket.close();
System.out.println("¡transferencia exitosa!");
System.out.println("Se recibieron " + countBytes + " bytes del servidor.");
}
inte
lec
tua
l
Are
nas
}
catch(Exception e) {
System.err.println("Excepción: " + e.getMessage());
}
public static void main(String args[]) {
MMClient cliente = new MMClient();
System.out.println("Cliente ejecutandose...");
cliente.transferFile();
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
}
} // class MMClient
126
Notas para el Curso de Java
Contenido
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
inte
lec
tua
l
Are
nas
Programación Orientada a Objetos 2
¿Qué es la programación orientada a objetos?
3
¿Qué es un Objeto? 3
¿Qué es una clase? 3
Componentes de una clase 3
¿Qué es la encapsulación? 4
¿Qué es la herencia? 4
¿Qué es el polimorfismo? 4
Mitos y realidades de la P.O.O.
4
Algunos campos de la aplicación de la P.O.O.
4
Metodología de la P.O.O. 5
Introducción a 6
Java 6
Origen de Java7
Caracteristicas del Lenguaje Java 9
Java para aplicaciones Empresariales
14
Desarrollo rápido de aplicaciones (RAD) 14
Aplicaciones efectivas y eficientes 14
Portabilidad para programador y programa15
Costos de desarrollo 15
Mantenimiento y soporte
15
Aprendizaje 16
Resumen
17
Elementos del lenguaje Java 18
Programando en Java 19
Comentarios 19
Identificadores19
Palabras clave 19
Palabras Reservadas 19
Literales
20
Operadores 21
CONTROL DE FLUJO
22
Sentencias de Bifurcación 22
Sentencias de ciclo o repetición condicional e incondicional
Excepciones 22
Control General del Flujo 23
Clases 24
Tipos de Clases
24
Variables y Métodos de Instancia 25
Ambito de una variable
25
Métodos y Constructores
25
Finalizadores 25
Alcance y ámbito de Objetos y Reciclaje de Memoria 26
Herencia
26
Control de acceso
27
Variables y Métodos Estáticos
27
Clases Abstractas
29
this Y super 29
Interfaces
30
Paquetes (packages) 31
Import 31
Paquetes de Java
31
22
127
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
inte
lec
tua
l
Are
nas
(c) 2000 by José Abraham Arenas Barrios
Notas para el Curso de Java
Métodos Nativos
31
Referencias 32
APUNTADORES Y REFERENCIAS C++32
Referencias en Java 33
Referencias y arreglos
35
Referencias y Listas 36
Apuntadores de C/C++ vs. Referencias en Java 37
Una aplicación mínima en Java
38
Compilación y Ejecución de una aplicación Java 39
Los Applets 40
de Java 40
El visor de Applets de SUN (appletviewer)41
Arquitectura interna del appletviewer
42
Ciclo de vida de un Applet 42
METODOS DE APPLETVIEWER 43
El menu del applet 45
Un applet básico en Java
46
Componentes básicos de un Applet 46
Compilación y ejecución de un applet
48
La marca <applet> </applet> de HTML 48
Atributos de un applet
49
Paso de parámetros a un applet
50
Java y el M-V-C
51
de Smalltalk 51
El modelo de delegación de eventos de Java (Modelo-Vista-Controlador, MVC)
52
Eventos y Auditores de Java 1.1.X 53
El Abstract Window Toolkit 57
de Java 1.1.x 57
Estructura del Abstract Window Toolkit (AWT) 58
Componentes y contenedores
58
Tipos de componentes
59
Componentes 61
Botones (clase Button)
61
Botones de Lista (clase Choice)
62
Botones de Marcación (clase Checkbox) 63
Botones de Selección(clase CheckboxGroup)
63
Etiquetas (clase Label)
64
LISTAS (clase List) 64
Campos de Texto (clase TextField) 66
Áreas de Texto (clase TextArea) 67
Lienzo (clase Canvas)
68
Barras de desplazamiento (clase Scrollbar) 69
Contenedores (clase Container y derivadas)
71
Creación de aplicaciones con GUI’s usando el AWT.
73
Crear el marco de la aplicación.
73
Inicializar fuentes, colores, recursos y demás... 74
Crear barras de menús, menús y opciones de menús
76
Ventanas y diálogos 78
Paneles (clase Panel) 80
Administradores de composición (Layouts)
82
FlowLayout 85
BorderLayout 86
GridLayout 87
GridBagLayout
88
CardLayout 90
128
Notas para el Curso de Java
Pr
opi
eda
d
Jos
e A de
bra
Bar ham
rio
s
inte
lec
tua
l
Are
nas
Gráficos del AWT 92
en Java 1.1.x 92
Objetos Gráficos
93
Líneas 94
Métodos para Dibujos
94
Rectángulos 96
Círculos y Elipses
99
Polígonos
101
Subprocesos (Threads)
102
de Java 102
Threads en Java.
103
Flujo de programas. 103
CREACION Y CONTROL DE THREADS
104
ARRANCAR Y PARAR THREADS
106
SUSPENDER Y REANUDAR THREADS107
ESTADOS DE UN THREAD
108
PRIORIDADES, DEMONIOS... 110
COMUNICACION ENTRE THREADS 111
Comunicaciones en Java
116
COMUNICACIONES EN UNIX 116
SOCKETS
116
USO DE SOCKETS 117
LA CLASE URL
118
DOMINIOS DE COMUNICACIONES 119
Dominio Unix 119
Dominio Internet
119
File Tranfer Protocol, FTP 119
Simple Network Management Protocol, SNMP 119
MODELO DE COMUNICACIONES CON JAVA
APERTURA DE SOCKETS 121
CIERRE DE SOCKETS
121
CREACION DE STREAMS 122
Cliente/Servidor TCP-IP
123
120
129
Descargar