Programación en Java

Anuncio
Desarrollo de software
Se tratan diferentes
aspectos de Java, desde las
aplicaciones hasta los
applets a través de
numerosos ejemplos.
Como herramientas de
desarrollo se utilizan
Microsoft Visual J++ 6 y
JBuilder. La versión tratada
del lenguaje es la estándar
de Sun Microsystems.
Se pretende adiestrar sobre:
•
•
•
•
•
•
•
Programación Orientada
a Objetos.
Construcción de
aplicaciones Java.
Creación de applets Java.
Tratamiento de eventos y
construcción de
interfaces de usuario.
Utilización de Visual J++
6.
Programación multihilo.
Acceso a ficheros y
conexiones a través de
Internet.
El único requisito es conocer
algún lenguaje de
programación
PROGRAMACIÓN EN JAVA
ÁNGEL ESTEBAN
ADVERTENCIA LEGAL
Todos los derechos de esta obra están reservados a Grupo EIDOS Consultoría y Documentación
Informática, S.L.
El editor prohíbe cualquier tipo de fijación, reproducción, transformación, distribución, ya sea mediante
venta y/o alquiler y/o préstamo y/o cualquier otra forma de cesión de uso, y/o comunicación pública de la
misma, total o parcialmente, por cualquier sistema o en cualquier soporte, ya sea por fotocopia, medio
mecánico o electrónico, incluido el tratamiento informático de la misma, en cualquier lugar del universo.
El almacenamiento o archivo de esta obra en un ordenador diferente al inicial está expresamente
prohibido, así como cualquier otra forma de descarga (downloading), transmisión o puesta a disposición
(aún en sistema streaming).
La vulneración de cualesquiera de estos derechos podrá ser considerada como una actividad penal
tipificada en los artículos 270 y siguientes del Código Penal.
La protección de esta obra se extiende al universo, de acuerdo con las leyes y convenios internacionales.
Esta obra está destinada exclusivamente para el uso particular del usuario, quedando expresamente
prohibido su uso profesional en empresas, centros docentes o cualquier otro, incluyendo a sus empleados
de cualquier tipo, colaboradores y/o alumnos.
Si Vd. desea autorización para el uso profesional, puede obtenerla enviando un e-mail fmarin@eidos.es o
al fax (34)-91-5017824.
Si piensa o tiene alguna duda sobre la legalidad de la autorización de la obra, o que la misma ha llegado
hasta Vd. vulnerando lo anterior, le agradeceremos que nos lo comunique al e-mail fmarin@eidos.es o al
fax (34)-91-5017824). Esta comunicación será absolutamente confidencial.
Colabore contra el fraude. Si usted piensa que esta obra le ha sido de utilidad, pero no se han abonado los
derechos correspondientes, no podremos hacer más obras como ésta.
© Ángel Esteban, 2000
© Grupo EIDOS Consultaría y Documentación Informática, S.L., 2000
ISBN 84-88457-18-9
Programación en Java
Ángel Esteban
Responsable editorial
Paco Marín (fmarin@eidos.es)
Autoedición
Magdalena Marín (mmarin@eidos.es)
Ángel Esteban (aesteban@eidos.es)
Grupo EIDOS
C/ Téllez 30 Oficina 2
28007-Madrid (España)
Tel: 91 5013234 Fax: 91 (34) 5017824
www.grupoeidos.com/www.eidos.es
www.LaLibreriaDigital.com
Coordinación de la edición
Antonio Quirós (aquiros@eidos.es)
Índice
ÍNDICE................................................................................................................................................... 5
INTRODUCCIÓN A LA POO ........................................................................................................... 11
¿QUÉ ES LA POO?.............................................................................................................................. 11
OBJETOS ............................................................................................................................................. 12
MENSAJES .......................................................................................................................................... 13
CLASES............................................................................................................................................... 14
HERENCIA .......................................................................................................................................... 14
MÉTODOS ........................................................................................................................................... 15
POLIMORFISMO .................................................................................................................................. 16
SOBRECARGA ..................................................................................................................................... 16
LA LEY DE DEMETER ......................................................................................................................... 16
MODELO DE OBJETOS ......................................................................................................................... 17
RELACIONES ENTRE CLASES .............................................................................................................. 17
VENTAJAS E INCONVENIENTES DE LA POO ....................................................................................... 18
UN EJEMPLO SENCILLO ...................................................................................................................... 19
INTRODUCCIÓN AL LENGUAJE JAVA ...................................................................................... 21
INTRODUCCIÓN .................................................................................................................................. 21
BREVE HISTORIA DEL LENGUAJE ....................................................................................................... 22
DESCRIPCIÓN DEL LENGUAJE ............................................................................................................. 22
PROGRAMAS EN JAVA: APPLETS Y APLICACIONES ............................................................................ 25
SIMILITUDES Y DIFERENCIAS ENTRE JAVA Y C++ ............................................................................. 26
VERSIONES DEL LENGUAJE ................................................................................................................ 26
ENTORNOS DE DESARROLLO .............................................................................................................. 27
CARACTERÍSTICAS DE LA PLATAFORMA JAVA 2 ............................................................................... 29
SINTAXIS DEL LENGUAJE JAVA................................................................................................. 33
INTRODUCCIÓN .................................................................................................................................. 33
IDENTIFICADORES .............................................................................................................................. 33
PALABRAS CLAVE .............................................................................................................................. 35
LITERALES ......................................................................................................................................... 36
Enteros........................................................................................................................................... 36
Coma flotante................................................................................................................................. 37
Booleanos ...................................................................................................................................... 37
Caracteres...................................................................................................................................... 37
Cadenas ......................................................................................................................................... 38
El literal null.................................................................................................................................. 38
OPERADORES ..................................................................................................................................... 38
SEPARADORES.................................................................................................................................... 39
COMENTARIOS ................................................................................................................................... 39
TIPOS DE DATOS EN JAVA .................................................................................................................. 40
Enteros........................................................................................................................................... 40
Coma flotante................................................................................................................................. 41
Booleanos ...................................................................................................................................... 41
Carácter......................................................................................................................................... 41
Cadenas ......................................................................................................................................... 41
Arrays ............................................................................................................................................ 42
EMPAQUETAR TIPOS PRIMITIVOS ....................................................................................................... 43
CONVERSIÓN DE TIPOS DE DATOS ...................................................................................................... 44
BLOQUES Y ÁMBITOS ......................................................................................................................... 45
Bloques .......................................................................................................................................... 45
Ámbitos .......................................................................................................................................... 46
EXPRESIONES ..................................................................................................................................... 46
CLASIFICACIÓN DE OPERADORES ...................................................................................................... 48
Aritméticos..................................................................................................................................... 48
Operadores sobre enteros........................................................................................................... 48
Operadores sobre reales............................................................................................................. 50
Booleanos ...................................................................................................................................... 50
Relacionales................................................................................................................................... 51
Cadena........................................................................................................................................... 51
Asignación ..................................................................................................................................... 52
PRECEDENCIA DE LOS OPERADORES .................................................................................................. 52
CONTROL DE FLUJO............................................................................................................................ 53
If-else ............................................................................................................................................. 53
Switch............................................................................................................................................. 54
For ................................................................................................................................................. 55
While.............................................................................................................................................. 56
Do-while: ....................................................................................................................................... 57
Break, continue y etiquetas............................................................................................................ 57
Return ............................................................................................................................................ 59
Try, catch, finally y throws: ........................................................................................................... 59
POO EN JAVA: OBJETOS................................................................................................................ 61
INTRODUCCIÓN .................................................................................................................................. 61
OBJETOS ............................................................................................................................................. 61
CREACIÓN DE OBJETOS ...................................................................................................................... 62
Declaración ................................................................................................................................... 62
Instanciación.................................................................................................................................. 63
Inicialización ................................................................................................................................. 64
UTILIZACIÓN DE OBJETOS .................................................................................................................. 64
DESTRUCCIÓN DE OBJETOS ................................................................................................................ 66
POO EN JAVA: CLASES................................................................................................................... 69
CLASES............................................................................................................................................... 69
DECLARACIÓN DE LA CLASE .............................................................................................................. 70
Superclase de la clase.................................................................................................................... 70
Interfaces de la clase ..................................................................................................................... 70
Modificadores de la clase.............................................................................................................. 71
CUERPO DE LA CLASE. DECLARACIÓN DE ATRIBUTOS ...................................................................... 72
Modificadores................................................................................................................................ 72
Tipo................................................................................................................................................ 74
Nombre variable ............................................................................................................................ 75
CUERPO DE LA CLASE. IMPLEMENTACIÓN DE MÉTODOS ................................................................... 75
Declaración del método................................................................................................................. 75
Declaración de variables............................................................................................................... 80
Implementación del método........................................................................................................... 80
HERENCIA .......................................................................................................................................... 82
Sustituir la implementación de un método..................................................................................... 84
Ampliar la implementación de un método ..................................................................................... 85
Métodos que una clase heredada no puede redefinir .................................................................... 85
Métodos que una clase heredada tiene que redefinir .................................................................... 86
CLASES ABSTRACTAS......................................................................................................................... 86
POO EN JAVA: OTROS CONCEPTOS .......................................................................................... 89
INTRODUCCIÓN .................................................................................................................................. 89
INTERFACES ....................................................................................................................................... 89
Declaración del interfaz ................................................................................................................ 90
Cuerpo del interfaz ........................................................................................................................ 90
EXCEPCIONES..................................................................................................................................... 91
PAQUETES .......................................................................................................................................... 94
PRINCIPALES PAQUETES DEL LENGUAJE JAVA................................................................................... 98
LA CLASE OBJECT ............................................................................................................................ 100
POO EN JAVA: UN EJEMPLO. RESUMEN DE CONCEPTOS................................................ 103
INTRODUCCIÓN ................................................................................................................................ 103
DESCRIPCIÓN DEL PROBLEMA.......................................................................................................... 103
UTILIZANDO EL JDK 1.3.................................................................................................................. 104
CREANDO LAS CLASES ..................................................................................................................... 105
APLICACIONES JAVA ................................................................................................................... 111
INTRODUCCIÓN ................................................................................................................................ 111
INTRODUCCIÓN A VISUAL J++ 6...................................................................................................... 112
INTRODUCCIÓN A JBUILDER 3.5 ...................................................................................................... 117
EL MÉTODO MAIN().......................................................................................................................... 122
LA CLASE SYSTEM ........................................................................................................................... 125
LA CLASE RUNTIME ......................................................................................................................... 128
ALGUNAS CONSIDERACIONES SOBRE VISUAL J++ 6 ....................................................................... 130
CONSIDERACIONES SOBRE JBUILDER .............................................................................................. 135
INTERFACES DE USUARIO EN JAVA: COMPONENTES AWT ........................................... 145
INTRODUCCIÓN ................................................................................................................................ 145
EL AWT (ABSTRACT WINDOW TOOLKIT) ...................................................................................... 145
UTILIZANDO LOS COMPONENTES DEL AWT.................................................................................... 148
Frame........................................................................................................................................... 148
Cursor.......................................................................................................................................... 149
7
MenuBar ...................................................................................................................................... 150
Menu ............................................................................................................................................ 150
MenuItem ..................................................................................................................................... 150
CheckboxMenuItem ..................................................................................................................... 150
Dialog .......................................................................................................................................... 152
FileDialog.................................................................................................................................... 153
Container ..................................................................................................................................... 153
Button........................................................................................................................................... 154
Label ............................................................................................................................................ 156
List, Choice.................................................................................................................................. 157
TextField, TextArea ..................................................................................................................... 158
Checkbox, CheckboxGroup ......................................................................................................... 160
ScrollPane.................................................................................................................................... 162
INTERFACES DE USUARIO EN JAVA: GESTORES DE DISEÑO Y EVENTOS................. 165
INTRODUCCIÓN ................................................................................................................................ 165
GESTORES DE DISEÑO ...................................................................................................................... 165
FlowLayout .................................................................................................................................. 167
BorderLayout............................................................................................................................... 167
CardLayout .................................................................................................................................. 168
GridLayout................................................................................................................................... 170
GridBagLayout ............................................................................................................................ 171
TRATAMIENTO DE EVENTOS EN JAVA .............................................................................................. 173
INTERFACES DE USUARIO EN JAVA: COMPONENTES SWING / CONTENEDORES... 189
INTRODUCCIÓN ................................................................................................................................ 189
JFC Y SWING ................................................................................................................................... 190
COMPONENTES SWING FRENTE A COMPONENTES AWT.................................................................. 191
CONTENEDORES DE ALTO NIVEL...................................................................................................... 192
JFrame......................................................................................................................................... 197
JDialog, JOptionPane ................................................................................................................. 199
JApplet ......................................................................................................................................... 208
CONTENEDORES INTERMEDIOS ........................................................................................................ 208
JPanel .......................................................................................................................................... 209
JTabbedPane ............................................................................................................................... 212
JToolBar ...................................................................................................................................... 216
JLayeredPane .............................................................................................................................. 217
INTERFACES DE USUARIO EN JAVA: COMPONENTES ATÓMICOS DE SWING ......... 223
INTRODUCCIÓN ................................................................................................................................ 223
COMPONENTES ATÓMICOS ............................................................................................................... 223
COMPONENTES PARA OBTENER INFORMACIÓN ............................................................................... 225
JButton......................................................................................................................................... 225
JCheckbox.................................................................................................................................... 227
JRadioButton ............................................................................................................................... 229
JComboBox.................................................................................................................................. 231
JMenu .......................................................................................................................................... 233
JSlider.......................................................................................................................................... 238
COMPONENTES PARA MOSTRAR INFORMACIÓN ............................................................................... 242
JLabel .......................................................................................................................................... 242
JToolTip....................................................................................................................................... 245
JProgressBar ............................................................................................................................... 245
COMPONENTES QUE MUESTRAN INFORMACIÓN ESTRUCTURADA.................................................... 247
JColorChooser............................................................................................................................. 247
JFileChooser................................................................................................................................ 252
INTERFACES DE USUARIO EN JAVA: OTRAS CARACTERÍSTICAS DE SWING........... 257
INTRODUCCIÓN ................................................................................................................................ 257
EL GESTOR DE DISEÑO BOXLAYOUT ............................................................................................... 257
ESTABLECIENDO EL LOOK & FEEL .................................................................................................. 262
APPLETS DE JAVA: INTRODUCCIÓN A LOS APPLETS....................................................... 269
CONCEPTOS PREVIOS ....................................................................................................................... 269
Internet......................................................................................................................................... 269
URLs y direcciones IP ................................................................................................................. 270
Clientes y Servidores Web ........................................................................................................... 271
HTML........................................................................................................................................... 272
HTTP ........................................................................................................................................... 272
INTRODUCCIÓN A LOS APPLETS DE JAVA ......................................................................................... 272
EL CICLO DE VIDA DE LOS APPLETS ................................................................................................. 282
SEGURIDAD EN LOS APPLETS ........................................................................................................... 285
TRUSTED/UNTRUSTED APPLETS ...................................................................................................... 289
DIFERENCIAS ENTRE APPLETS Y APLICACIONES .............................................................................. 289
LA ETIQUETA APPLET ....................................................................................................................... 290
Codebase ..................................................................................................................................... 291
Code............................................................................................................................................. 291
Alt................................................................................................................................................. 291
Name............................................................................................................................................ 291
Width, Height............................................................................................................................... 291
Align............................................................................................................................................. 291
Vspace, Hspace............................................................................................................................ 292
<PARAM>................................................................................................................................... 292
Etiquetas HTML alternativas ...................................................................................................... 292
APPLETS DE JAVA: UTILIZANDO LOS APPLETS ................................................................. 293
INTERACCIÓN DE LOS APPLETS CON EL NAVEGADOR WEB .............................................................. 293
EVENTOS, GRÁFICOS Y FUENTES EN LOS APPLETS ........................................................................... 303
COMPRESIÓN DE APPLETS ................................................................................................................ 322
COMPONENTES SWING Y APPLETS ................................................................................................... 325
LA CLASE JAPPLET .......................................................................................................................... 327
ASPECTOS AVANZADOS DE JAVA: PROCESOS.................................................................... 331
INTRODUCCIÓN ................................................................................................................................ 331
PROCESOS Y MULTIPROCESO ........................................................................................................... 331
HOLA MUNDO CON HILOS ................................................................................................................ 332
PARALELISMO .................................................................................................................................. 333
UTILIZANDO PROCESOS. EL INTERFAZ RUNNABLE .......................................................................... 337
COORDINANDO LOS PROCESOS ........................................................................................................ 344
Sin derecho preferente................................................................................................................. 344
Con derecho preferente ............................................................................................................... 344
ASPECTOS AVANZADOS DE JAVA: CANALES Y SOCKETS............................................... 345
INTRODUCCIÓN A LOS CANALES ...................................................................................................... 345
CANALES ESTÁNDAR DE ENTRADA/SALIDA ..................................................................................... 346
CANALES DE JAVA.IO ....................................................................................................................... 348
CANALES DE TRANSMISIÓN (DATA SINK STREAMS) .......................................................................... 350
CANALES DE PROCESO (PROCESSING STREAMS)............................................................................... 352
Canales de Filtrado ..................................................................................................................... 353
Canales de buffer......................................................................................................................... 353
Canal de Concatenación.............................................................................................................. 354
Canales de conversión de tipos ................................................................................................... 354
9
Serialización de Objetos .............................................................................................................. 356
Canal Contador de Líneas........................................................................................................... 357
Canales de impresión .................................................................................................................. 358
Canales de vuelta atrás ............................................................................................................... 359
APLICACIONES CLIENTE/SERVIDOR EN JAVA ................................................................................... 359
Introducción a la POO
¿Qué es la POO?
Las siglas POO se corresponden con Programación Orientada a Objetos, aunque muchas veces las
podemos encontrar escritas en inglés OOP (Object Oriented Programming).
En este primer capítulo de este curso vamos a tratar de explicar de forma sencilla los principales
conceptos y términos que se utilizan dentro de este tipo de programación, es decir, dentro de la
Programación Orientada a Objetos. No vamos a entrar en sesudas divagaciones filosóficas respecto a
la POO, sino que vamos a definir lo más claramente posible cada uno de los elementos clave que
aparecen en la POO para después poder aplicarlos al lenguaje que nos ocupa, es decir, al lenguaje
Java.
Este tema es muy necesario debido a que el lenguaje Java, como veremos en el siguiente capítulo, es
un lenguaje que se basa en la Programación Orientada a Objetos, por lo tanto para conocer el lenguaje
Java, es necesario conocer la Programación Orientada a Objetos.
La Programación Orientada a Objetos trata de utilizar una visión real del mundo dentro de nuestros
programas. La visión que se tiene del mundo dentro de la POO es que se encuentra formado por
objetos.
Para comprender bien la POO debemos olvidar un poco la Programación Estructurada, que si nos
fijamos bien es algo artificial, la POO es una forma de abordar los problemas más natural. Aquí
natural significa más en contacto con el mundo real que nos rodea, de esta forma si queremos resolver
un problema determinado, debemos identificar cada una de las partes del problema con objetos
presentes en el mundo real.
Programación en Java
© Grupo EIDOS
En esta definición de POO ya estamos haciendo referencia al elemento clave de la misma: el objeto. El
objeto va a ser la modelización de los objetos que nos encontramos en el mundo real, estos objetos los
vamos a utilizar en nuestros programas para dar la solución al problema que nos ocupe en cada caso.
Objetos
Como ya hemos adelantado un objeto es la pieza básica de la POO, es una representación o
modelización de un objeto real perteneciente a nuestro mundo, por ejemplo, podemos tener un objeto
perro que represente a un perro dentro de nuestra realidad, o bien un objeto factura, cliente o pedido.
Los objetos en la vida real tienen todos, dos características: estado y comportamiento. El estado de un
objeto viene definido por una serie de parámetros que lo definen y que lo diferencian de objetos del
mismo tipo. En el caso de tener un objeto perro, su estado estaría definido por su raza, color de pelo,
tamaño, etc. Y el comportamiento viene definido por las acciones que pueden realizar los objetos, por
ejemplo, en el caso del perro su comportamiento sería: saltar, correr, ladrar, etc. El comportamiento
permite distinguir a objetos de distinto tipo, así por ejemplo el objeto perro tendrá un comportamiento
distinto a un objeto gato.
Si tomamos un ejemplo que tiene que ver más con el mundo de la informática se pueden ver más
claros estos dos conceptos. Si tenemos un objeto pantalla que representa la pantalla de nuestro
ordenador, el estado de la misma estaría definido por los siguientes parámetros: encendida o apagada,
tamaño, resolución, número de colores, etc.; y su comportamiento podría ser: imprimir, encender,
apagar, etc.
Los parámetros o variables que definen el estado de un objeto se denominan atributos o variables
miembro y las acciones que pueden realizar los objetos se denominan métodos o funciones miembro, y
para indicar variables miembro y funciones miembro se utiliza el término general miembro.
Si lo comparamos con la programación estructurada podríamos hacer la siguiente aproximación: los
atributos o variables miembro serían variables y los métodos o funciones miembro procedimientos y
funciones.
A partir de ahora y a lo largo de todo el presente curso vamos a utilizar únicamente la nomenclatura de
atributos y métodos.
Los atributos de un objeto deben encontrarse ocultos al resto de los objetos, es decir, no se va a poder
acceder directamente a los atributos de un objeto para modificar su estado o consultarlo. Para acceder
a los atributos de un objeto se deben utilizar métodos. Es decir, los métodos exponen toda la
funcionalidad del objeto, mientras que los detalles del estado interno del objeto permanecen ocultos.
Incluso algunos métodos también pueden permanecer ocultos.
El hecho de ocultar la implementación interna de un objeto, es decir, como está construido y de que se
compone se denomina encapsulación. La encapsulación es uno de los beneficios y particularidades del
paradigma de la Programación Orientada a Objetos.
Normalmente un objeto ofrece una parte pública que será utilizada por otros objetos para interactuar
entre sí, pero también permanece una parte oculta para encapsular los detalles de la implementación
del objeto.
Ya se ha dicho que un objeto está compuesto de atributos y métodos. Como la caja negra de un avión,
el objeto recubre la información que almacena y solamente podemos obtener la información e
indicarle que realiza acciones por medio de lo que comúnmente se denomina interfaz del objeto, que
estará constituido por los métodos públicos.
12
© Grupo EIDOS
1. Introducción a la POO
Los datos y la implementación queda oculta a los demás objetos que interaccionan en el programa, lo
que favorece enormemente la protección de los datos y las estructuras internas contra las
modificaciones externas al objeto. De este modo es mucho más sencillo localizar errores en los
programas puesto que cada objeto está altamente especializado, y sólo se encarga de su tarea. Como se
puede observar, esto consigue una mayor modularidad, que facilita además el diseño en equipo de
programas y la reutilización de clases (componentes) creados por otros desarrolladores.
Hemos indicado que la encapsulación es un beneficio que nos aporta la POO, es un beneficio porque
permite abstraernos de la utilización de los objetos, es decir, a mí me interesa realizar una determinada
tarea con un objeto (por ejemplo imprimir una pantalla o rellenar una factura), pero a mí no me
interesa como realiza este proceso internamente el objeto que estoy utilizando. En este momento entra
en juego otro concepto importante de la POO y que es la abstracción.
La abstracción indica la capacidad de ignorar determinados aspectos de la realidad con el fin de
facilitar la realización de una tarea. Nos permite ignorar aquellos aspectos de la realidad que no
intervienen en el problema que deseamos abordar, y también nos permite ignorar los aspectos de
implementación de los objetos en los pasos iniciales, con lo cual sólo necesitamos conocer qué es lo
que hace un objeto, y no cómo lo hace, para definir un objeto y establecer las relaciones de éste con
otros objetos.
Un objeto lo podríamos representar como dos circunferencias, una interna que permanece oculta al
mundo exterior y que contendría todos los detalles de la implementación del objeto, y otra
circunferencia concéntrica externa, que representa lo que el objeto muestra al mundo exterior y le
permite utilizar para interactuar con él. En la Figura 1 se puede ver dicha representación.
La encapsulación ofrecida a través de objetos tienen varios beneficios, entre los que destacan la
modularidad y la ocultación de la información.
Mediante la modularidad podemos escribir código de manera independiente de cómo se encuentren
construidos los diferentes objetos que vamos a utilizar. Y ocultando la información se permite realizar
cambios en el código interno de los objetos sin que afecte a otros objetos que los utilicen o dependan
de ellos. No es necesario entender la implementación interna de un objeto para poder utilizarlo.
Figura 1
Mensajes
Los mensajes son la forma que tienen de comunicarse distintos objetos entre sí. Un objeto por sí sólo
no es demasiado útil, sino que se suele utilizar dentro de una aplicación o programa que utiliza otros
objetos.
El comportamiento de un objeto está reflejado en los mensajes a los que dicho objeto puede responder.
Representan las acciones que un determinado objeto puede realizar.
13
Programación en Java
© Grupo EIDOS
Un mensaje enviado a un objeto representa la invocación de un determinado método sobre dicho
objeto, es decir, la ejecución de una operación sobre el objeto. Es la manera en la que un objeto utiliza
a otro, el modo en el que dos objetos se comunican, ya que la ejecución de ese método retornará el
estado del objeto invocado o lo modificará.
Los mensajes se utilizan para que distintos objetos puedan interactuar entre sí y den lugar a una
funcionalidad más compleja que la que ofrecen por separado. Un objeto lanzará o enviará un mensaje
a otro objeto si necesita utilizar un método del segundo objeto. De esta forma si el objeto A quiere
utilizar un método del objeto B, le enviará un mensaje al objeto A.
Para enviar un mensaje se necesitan tres elementos: el objeto al que se le va a enviar el mensaje, el
nombre del método que se debe ejecutar y los parámetros necesarios para el método en cuestión.
Clases
Una clase es un molde o prototipo que define un tipo de objeto determinado. Una clase define los
atributos y métodos que va a poseer un objeto. Mediante las clases podremos crear o instanciar objetos
de un mismo tipo, estos objetos se distinguirán unos de otros a través de su estado, es decir, el valor de
sus atributos.
La clase la vamos a utilizar para definir la estructura de un objeto, es decir, estado (atributos) y
comportamiento (métodos). La clase es un concepto abstracto que generalmente no se va a utilizar
directamente en nuestros programas o aplicaciones. Lo que vamos a utilizar van a ser objetos
concretos que son instancias de una clase determinada.
La clase es algo genérico y abstracto, es similar a una idea. Cuando decimos piensa en un coche todos
tenemos en mente la idea general de un coche, con puertas, ruedas, un volante, etc., sin embargo
cuando decimos "ese coche que está aparcado ahí fuera", ya se trata de un coche determinado, con una
matrícula, de un color, con un determinado número de puertas, y que podemos tocar y utilizar si es
necesario. Sin embargo como ya hemos dicho la clase es la idea que define al objeto concreto.
Un ejemplo que se suele utilizar para diferenciar y relacionar clases y objetos es el ejemplo del molde
de galletas. El molde para hacer galletas sería una clase, y las galletas que hacemos a partir de ese
molde ya son objetos concretos creados a partir de las características definidas por el molde.
Una vez implementada una clase podremos realizar instancias de la misma para crear objetos que
pertenezcan a esa clase.
Las clases ofrecen el beneficio de la reutilización, utilizaremos la misma clase para crear distintos
objetos. Y luego veremos que una vez que tenemos una clase podremos aprovecharla heredando de
ella para complicarla o especializarla para una labor concreta.
Si comparamos las clases y objetos de la POO con la programación estructurada tradicional, se puede
decir que las clases son los tipos de datos y los objetos las variables de esos tipos de datos. De esta
forma si tenemos el tipo entero, en la POO diríamos que es la clase entero, y si tenemos una variable
de tipo entero, en la POO diríamos que tenemos un objeto de la clase entero.
Herencia
La herencia es un mecanismo mediante el cual podemos reutilizar clases ya definidas. Es decir, si
tenemos una clase botón que define un tipo de objeto que se corresponde con un botón que tiene un
14
© Grupo EIDOS
1. Introducción a la POO
texto, que se puede pulsar, etc., si queremos definir una nueva clase llamada botón de color, no
tenemos que rescribir todo el código y crear una clase completamente nueva, sino lo que haremos será
heredar de la clase botón, utilizar lo que nos ofrezca esta clase y añadirle lo que sea necesario para la
nueva funcionalidad deseada.
La herencia dentro de la POO es un mecanismo fundamental que se puede definir también como una
transmisión de las características de padres a hijos. Entendiendo aquí características como métodos y
atributos de una clase. La clase hija puede añadir atributos, métodos y redefinir los métodos de la clase
padre.
Podemos ver la herencia como una sucesiva especialización de las clases. La clase de la que se hereda
se suele denominar clase padre o superclase, y la clase que hereda se denomina clase hija o subclase.
El mecanismo de herencia es muy potente, puesto que nos permite agregar funcionalidades nuevas a
una clase ya existente, reutilizando todo el código que ya se tenga disponible de la clase padre, es
decir, se heredarán sus atributos y métodos, como ya habíamos indicado con anterioridad. En las
clases hijas podemos redefinir el comportamiento de la clase padre.
Para verificar que la herencia entre dos clases es correcta y coherente, debemos hacernos la pregunta
de "¿es un?" o "¿es un tipo de?". Por ejemplo, en el caso que comentábamos del botón, tenemos una
clase BotonColor que hereda de la clase Boton. Esta herencia contesta perfectamente a la pregunta
definida antes: ¿un botón de color es un botón?, evidentemente sí.
Mediante el mecanismo de herencia podemos definir superclases denominadas clases abstractas que
definen comportamientos genéricos. De esta clase pueden heredar otras clases que ya implementan de
forma más concreta estos comportamientos. De esta forma podremos crear jerarquías de clases.
Así por ejemplo podemos tener una clase bicicleta, que será más o menos genérica. De esta clase
pueden heredar la clase bicicleta de montaña, bicicleta de carreras y tandem, que ya ofrecen una clase
más especializada que la clase padre.
Los métodos que se heredan de la clase padre no tienen porqué utilizarse sin realizar ningún cambio,
se puede llevar a cabo lo que se denomina la sobrescritura de métodos. Podemos heredar un método de
la clase padre, pero en la clase hija le podemos dar una implementación diferente para que se adecue a
la nueva clase.
La herencia puede ser simple si la clase hija hereda de una única clase padre o múltiple si hereda de
varias clases padre, más adelante veremos que el tipo de herencia que soporta Java es una herencia
simple. Algunas veces la herencia múltiple puede llegar a ser confusa.
Métodos
Como ya hemos dicho anteriormente los métodos son las acciones que se pueden realizar con los
objetos. También se podría definir un método como la implementación de un mensaje, al fin y al cabo,
un mensaje es la llamada o invocación de un método de un objeto.
Existen dos métodos especiales dentro de la POO que se denominan constructor y destructor.
El método constructor se ejecuta automáticamente cada vez que se crea un objeto de la clase en
cuestión, sobre el objeto que acaba de crearse, inmediatamente después de haberse asignado memoria
a dicho objeto. Algunos lenguajes proporcionan un constructor por defecto, pero generalmente lo
correcto es que lo defina el diseñador de la clase y que en él se lleven a cabo las inicializaciones y
todas aquellas operaciones que se necesiten para poder usar el objeto.
15
Programación en Java
© Grupo EIDOS
El método destructor se invoca automáticamente inmediatamente antes de liberar la memoria del
objeto en cuestión, o lo que es lo mismo, antes de que se salga del ámbito de la declaración del objeto,
por lo que se ha de emplear para que la destrucción del objeto se efectúe correctamente y contendrá
operaciones tales como liberación de memoria asignada dinámicamente dependiente del objeto,
grabación de todos o parte de los atributos del objeto en un fichero o base de datos y operaciones
similares.
Polimorfismo
El término polimorfismo expresa la posibilidad de que el mismo mensaje, enviado a objetos distintos,
ejecute métodos distintos. Esto significa que podemos definir dentro de dos clases distintas dos
operaciones con el mismo nombre y aspecto externo, pero con distintas implementaciones para cada
clase.
En el momento de emplear estas operaciones, el lenguaje es capaz de ejecutar el código
correspondiente dependiendo de la clase del objeto sobre el que se ejecuta la operación. Esto permite
definir un interfaz común, un aspecto externo idéntico, para una serie de clases.
De esta forma podemos definir un método suma() para la clase Enteros y otro método suma() para la
clase Matrices. El mensaje será el mismo, pero la implementación de los métodos será distinta ya que
no es lo mismo sumar enteros que matrices.
Sobrecarga
La sobrecarga de métodos se produce cuando una clase tiene métodos con el mismo nombre pero que
difieren o bien en el número o en el tipo de los parámetros que reciben dichos métodos.
Un ejemplo de sobrecarga los podemos tener en el método imprimir(), mediante este método vamos a
imprimir en pantalla y realizar un salto de línea. Este método acepta como parámetro una variable de
tipo entero o una variable de tipo cadena de caracteres, incluso si no indicamos ningún parámetro
realizaría un salto de línea.
La ley de Demeter
En lo que respecta a la implementación de métodos de una clase, existe una ley que nos da una serie
de pautas para realizar una Programación Orientada a Objetos correcta, esta ley es la Ley de Demeter.
La Ley de Demeter, enunciada por Karl Lieberherr, determina el acceso o visibilidad de los objetos en
la implantación de un método, y se rige por el siguiente principio: en la implantación de un método se
puede tener acceso a los siguientes objetos:
•
Atributos de su clase.
•
Los parámetros que se pasan al método.
•
Objetos temporales creados dentro del método.
Existen dos formas de la Ley de Demeter, la flexible y la estricta, la flexible permite acceder a los
atributos de la clase padre o superclase y la estricta indica que este acceso se debe lograr mediante la
utilización de métodos de acceso de la clase padre.
16
© Grupo EIDOS
1. Introducción a la POO
Modelo de objetos
El modelo de objetos es un conjunto de principios que se deben dar para que se puedan modelar
objetos computacionales (objetos dentro de nuestro código y programas) a partir de objetos de la
realidad de manera que éstos reflejen los posibles comportamientos presentes en la realidad y
compongan un modelo computacional válido.
Es decir, definido e identificado un problema se trata de realizar una representación lógica de los
objetos que forman parte del problema en el mundo real, de esta forma podremos utilizar en nuestro
programa estos objetos para dar la solución a través de nuestro código.
El modelo de objetos que definen un problema de la realidad se basa en los siguientes principios:
abstracción, encapsulación, herencia y polimorfismo. Todos estos conceptos básicos de la POO ya han
sido discutidos en este tema en mayor o menor medida.
Por lo tanto antes de empezar a construir un programa para una tarea específica, es necesario construir
y diseñar su modelo de objetos. Existen diversas técnicas de modelización, pero el objetivo de este
curso no es el de explicarlas, así que no entraremos en más detalles.
Relaciones entre clases
Una clase por sí sola no ofrece una funcionalidad demasiado interesante, como ya veremos a lo largo
del curso, el lenguaje Java se encuentra formado por un gran número de clases que forman parte de
una compleja y completa jerarquía. Cada una de las clases está especializada en una función o tarea
específica, es decir, se da una gran modularidad, cada clase tiene su cometido.
Por lo tanto para ofrecer una mayor funcionalidad y realizar tareas más complejas es necesario que
exista una relación entre distintas clases. Un ejemplo podría ser las piezas del motor de un coche. Las
piezas por si solas no realizan ninguna tarea de importancia, cuando realmente se saca provecho de
ellas es cuando se relacionan entre sí y se construye con ellas un motor. A su vez este motor puede ser
incorporado dentro de un coche, que estará compuesto a su vez de más objetos o piezas. Como vemos
la correspondencia entre los objetos del mundo real y los objetos del mundo computacional es bastante
obvia en algunos casos.
Básicamente una clase se puede relacionar con otra de tres formas diferentes. Las relaciones que
podemos distinguir son:
•
Relación de composición: una clase puede estar compuesta de otras clases. Esto se consigue
implementando los atributos de la clase como objetos de otra clase. Una clase hace uso de otra
a través de sus atributos, una clase se encuentra formada por varias clases. Por ejemplo la
clase Coche tiene atributos que son de la clase Puerta, Rueda, Motor, etc.
•
Relación de uso: una clase se relaciona con otra a través de los mensajes que le envía. Esto se
consigue pasándose una instancia de la clase como uno de los parámetros del método
invocado por el mensaje. Es decir, si tenemos la clase Pantalla con el método dibujar() y
queremos dibujar un objeto de la clase Rectangulo, deberíamos realizar lo siguiente
objPantalla.dibujar(Rectangulo), es decir, el parámetro que se le pasa al método dibujar() de la
clase Pantalla es un objeto de la clase Rectangulo.
•
Relación de herencia: este tipo de relación entre clases ya la conocemos y consiste en que una
clase hija hereda de una clase padre o superclase pudiendo utilizar así toda la funcionalidad
ofrecida por la clase padre y añadir nuevas funcionalidades. En esta forma de relación se
17
Programación en Java
© Grupo EIDOS
consigue la reutilización del código y una progresiva especialización a través de una jerarquía
de clases.
Ventajas e inconvenientes de la POO
En este apartado vamos a comentar que ventajas nos ofrece el paradigma de la POO, también
mostraremos algunas de sus desventajas. Como ventajas o aportaciones podemos destacar las
siguientes:
•
Facilita la reutilización del software. A través de la herencia se nos permite utilizar en un
objeto las operaciones implementadas para otros sin esfuerzo adicional. Por otro lado la
encapsulación nos facilita el uso de objetos que no hemos definido ni implementado. Se pude
considerar que la encapsulación y el polimorfismo son las herramientas más potentes del
paradigma de la POO.
•
Facilita la construcción de programas portables. Es posible diseñar una capa de objetos que se
comunique con la máquina o el sistema operativo, y sobre ésta los objetos que dan
funcionalidad a la aplicación. Una migración a otra arquitectura sólo requerirá cambios en
dicha capa, y la encapsulación nos garantiza que los cambios se van a limitar a esos objetos.
Java refuerza más esta característica de la portabilidad, ya que, como veremos en el próximo
capítulo, el lenguaje Java es independiente de la plataforma en la que se ejecuta.
•
Facilita el mantenimiento. El encapsulamiento nos garantiza que las modificaciones realizadas
en un objeto tendrán un efecto limitado. Si un elemento de datos se accede directamente y en
un momento dado cambia de tipo o formato, habrá que localizar todos los puntos en los que se
accede a dicho elemento y modificarlos en consecuencia. Si este elemento de datos está
encapsulado en un objeto y siempre es accedido mediante las operaciones disponibles, un
cambio como el indicado se limitará al objeto, del que sólo habrá que cambiar el elemento de
datos y las operaciones del objeto que lo utilizan. Lo recomendable es desconocer la
implementación interna del objeto, nosotros necesitaremos utilizar un objeto que ofrece una
serie de funcionalidades, pero no nos interesa de que forma nos ofrece las mismas.
•
Provoca que las tareas de análisis, diseño e implementación sean más intuitivas, ya que se
manejan objetos, concepto con el que todos estamos familiarizados, y estructuradas, ya que
podemos asignar las tareas de diseño e implementación en el ámbito de objetos. Se debe
recordar que los objetos en nuestros diseños van a representar objetos presentes en el mundo
real.
No todos son beneficios, la POO ofrece también una serie de desventajas, aunque más que desventajas
o inconvenientes podemos decir que presenta una serie de dificultades, las cuales se comentan a
continuación:
18
•
Curvas de aprendizaje largas, debido principalmente a que implica un cambio mentalidad, y
no sólo el aprender un nuevo lenguaje.
•
Dificultad en determinar las características de un objeto. Debido a que un objeto no se define
aisladamente, sino que depende de las relaciones con otros objetos, el establecimiento de
nuevas relaciones puede implicar un cambio en la definición del objeto y viceversa. Por todo
ello es conveniente iterar, esto es, repetir el proceso de definición de objetos y de
identificación de relaciones, tantas veces como sean necesarias para alcanzar un modelo
estable. En muchos casos resulta complicado llegar a un modelo de objetos definitivo con el
que se pueda abordar el problema planteado de forma completa.
© Grupo EIDOS
•
1. Introducción a la POO
Jerarquías de herencia complejas: en muchos casos se tiende a utilizar de forma desmedida el
mecanismo de herencia, ofreciendo en algunos casos unas jerarquías de herencia difíciles de
seguir y abordar.
Un ejemplo sencillo
No hace falta decir que el tema que nos ocupa es un tema eminentemente teórico, pero aun así vamos a
incluir un breve y sencillo ejemplo con el que se pretende mostrar de forma sencilla como se puede
construir un modelo de objetos, identificando los objetos que intervienen en el problema a resolver,
definiendo los atributos de la clase, los métodos, relaciones entre las diferentes clases, etc.
En este caso vamos a abandonar los típicos ejemplos de la clase Coche y similares y vamos a mostrar
un ejemplo más lúdico. En nuestro ejemplo vamos a tener la clase VideoJuego, es decir, vamos a crear
el juego de matamarcianos conocido por todos.
No vamos a profundizar demasiado en todos los atributos y métodos de las clases, sino que vamos a
mostrar únicamente los más relevantes.
En el primer paso vamos a identificar las clases necesarias para abordar nuestro problema, en este
caso, la creación de un juego de matamarcianos. Primero tengamos en cuenta las especificaciones:
•
En el juego tenemos dos tipos de enemigos: marcianos y venusianos.
•
El protagonista del juego es un terrícola.
•
El terrícola dispone de un arma para defenderse de los enemigos, un lanza cohetes.
Como se puede observar no vamos a tener demasiadas complicaciones a la hora definir el modelo de
objetos de nuestro programa.
Se pueden distinguir seis clases: VideoJuego, Enemigo, Marciano, Venusiano, Terricola y
LanzaCohetes.
La clase VideoJuego sería la clase principal y contendría al resto de las clases. Por lo tanto entre la
clase VideoJuego y el resto de las clases existe una relación de composición. Esta clase tendría como
atributos posibles: Enemigos, Heroes y Armas; y como métodos: comenzarPartida(),
interrumpirPartida(), reanudarPartida() y finalizarPartida(), la finalidad de cada uno de ellos está
bastante clara.
La clase Enemigo en realidad va a ser una clase abstracta, ya que no vamos a instanciar objetos de esta
clase. La clase Enemigo va a ser la clase padre de la clase Marciano y Venusiano, y lo que se consigue
con esta clase es agrupar todo el comportamiento que es común a los enemigos que aparecen en el
juego. Esta clase tiene los siguientes atributos: Color, NumeroOjos y NumeroPiernas. Estos atributos
serán comunes a las clases Marciano y Venusiano. Existe una relación de herencia entre la clase
Enemigo y las clases Marciano y Venusiano.
La clase Enemigo podría tener los siguientes métodos: mover(), atacar() y disparar(). Estos métodos
también serán heredados por la clase hijas mencionadas. Las clases hijas tienen dos opciones, si la
implementación de los métodos anteriores que realiza la clase padre es la adecuada, los heredarán y
utilizarán sin más, pero si las clases hijas quieren realizar modificaciones o ampliaciones sobre los
métodos heredados deberán implementarlos y por lo tanto sobrescribirlos aportando un
comportamiento diferente.
19
Programación en Java
© Grupo EIDOS
Las clases Marciano y Venusiano pueden además de sobrescribir los métodos heredados de la clase
Enemigo, aportar nuevos métodos y nuevos atributos. La clase Marciano añade el atributo Visible, es
decir, nuestro amigo tiene la capacidad de hacerse invisible, y la clase Venusiano añade el atributo
NumeroCabezas.
La clase Terricola representa al héroe de nuestro juego y tiene los atributos: NumeroVidas y
Municion, ambos requieren poca explicación. Esta clase implementa el método disparar(), que recibe
como parámetro un objeto de la clase LanzaCohetes, la relación entre ambas clases es una relación de
uso.
La clase LanzaCohetes que representa el arma que va a utilizar la clase Terricola y tienen el atributo
NumeroCohetes, y posee el método lanzarCohete().
Podríamos entrar en más detalles e ir afinando más en la identificación de objetos, podríamos definir
también la clase Cohete, que representaría a los cohetes lanzados por una instancia de la clase
LanzaCohetes. Pero no vamos a entrar en más detalles y vamos a dejar de definir más clases,
quedándonos con las vistas hasta ahora.
En la Figura 2 se trata de mostrar un esquema de todas las clases que implementan el problema
propuesto y la relación existente entre cada una de ellas. Cada clase se representa por una
circunferencia y las relaciones existentes mediante flechas.
Figura 2
20
Introducción al lenguaje Java
Introducción
En este capítulo vamos a comentar las características principales del lenguaje Java, también
comentaremos algunos conceptos interesantes que aporta el lenguaje.
Como su nombre indica este capítulo es introductorio, por lo tanto algunos de los conceptos los
explicaremos de forma breve y en los sucesivos capítulos profundizaremos en los temas más
interesantes.
Por lo tanto en este capítulo todavía no vamos a ver ni una sola línea de código en Java, antes debemos
tener claro una serie de puntos acerca del lenguaje.
También comentaremos tres herramientas de desarrollo que podemos utilizar para realizar nuestros
desarrollos en Java, se trata de las siguientes:
•
JDK (Java Development Kit): herramienta ofrecida por Sun MicroSystems, implementa la
versión última y oficial de Java.
•
Microsoft Visual J++ 6.0: incluida dentro de Visual Studio 6, ofrece la versión 1.1 de Java.
•
Borland JBuilder 3.5: potente entorno de desarrollo que implementa la versión de Java
perteneciente a Java 2.
Que el lector no tema por las distintas versiones de Java, en el apartado correspondiente se aclaran las
distintas versiones existentes.
Programación en Java
© Grupo EIDOS
Breve historia del lenguaje
Los orígenes de Java se remontan al año 1990, cuando un equipo de la compañía Sun Microsystems
investigaba, bajo la dirección del ingeniero James Gosling, en el diseño y elaboración de software para
pequeños dispositivos electrónicos de consumo.
En un primer momento se pensó en la utilización de lenguajes de programación como C o C++, pero
para poder compilar un programa en estos lenguajes es preciso adaptarlo a las características de la
plataforma en la que debe funcionar, esta situación constituía un gran inconveniente para las
compañías dedicadas a la construcción de dispositivos electrónicos, pues cada pocas semanas aparecen
en el mercado versiones más potentes y baratas de los chips utilizados, y por lo tanto, el software que
se había diseñado para un chip determinado debía modificarse y adaptarse para explotar las
características de los chips de reciente aparición.
Se hace patente la necesidad de introducir un nuevo lenguaje de programación, que permita desarrollar
programas independientes del tipo de plataforma. Los dispositivos que se pretenden fabricar son
calculadoras, relojes, equipos de música, cafeteras, etc.…, que no tienen una gran capacidad
computacional, por lo que el nuevo lenguaje debe ser capaz de generar programas pequeños y rápidos,
además de ser fiables y robustos.
La primera versión de este nuevo lenguaje se denominó Oak (roble), pero más tarde Sun descubrió que
este nombre estaba ya registrado, y lo tuvieron que cambiar, el nuevo nombre fue Java (una de las
versiones sobre el significado del nombre es que Java es un término popularmente empleado en
California para designar café de buena calidad).
A comienzos de 1993 aparecieron nuevas herramientas gráficas para facilitar la comunicación y
navegación por Internet, se concibió la idea de aplicar técnicas de documentos con enlaces de tipo
hipertextual para facilitar la navegación por Internet, y se desarrolló el primer browser (navegador
Web o visualizador) de lo que se comenzó a denominar World Wide Web. Esta herramienta era
denominada Mosaic.
El equipo de James Gosling se planteó como objetivo la utilización de Java como lenguaje en el que
escribir aplicaciones que pudiesen funcionar a través de Internet. Como resultado de su trabajo se
desarrolló un nuevo navegador completamente escrito en Java, llamado HotJava. Este navegador
permitía la integración de pequeñas aplicaciones en el interior de las páginas Web. El desarrollo de
HotJava hizo patente que las características de Java se adaptan perfectamente a las peculiaridades de
Internet.
A partir de su primera y sencilla versión Java ha ido creciendo progresiva y espectacularmente para
pasar a ofrecer un potente y complejo lenguaje con el que se pueden abarcar una gran cantidad de
campos.
Descripción del lenguaje
En este apartado vamos a resaltar las características principales del lenguaje Java y comentaremos una
serie de términos y conceptos que se hacen indispensables para comprender lo que nos ofrece el
lenguaje de programación que nos ocupa.
Java es un lenguaje de programación orientado a objetos de propósito general, de ahí la necesidad de
estudiar el capítulo anterior dedicado íntegramente a la Programación Orientada a Objetos. Según
indican desde Sun Microsystems, Java es un lenguaje creado para realizar una programación en
Internet rápida y fácil, rápida y fácil si ya poseemos conocimientos previos de C++ y de programación
22
© Grupo EIDOS
2. Introducción al lenguaje Java
orientada a objetos. Por lo tanto si el lector ya ha programado en C++ la curva de aprendizaje del
lenguaje Java se suavizará considerablemente.
Aunque no se debe considerar a Java como una herramienta exclusiva y únicamente para la
programación en Internet, ya que su uso, lejos de restringirse a este campo, puede y debe extenderse a
problemas y situaciones de todo tipo, por lo tanto para evitar confusiones el lenguaje Java se podría
definir mejor de la siguiente forma: Java es un lenguaje de programación orientado a objetos, de
propósito general que presenta características especiales que lo hacen idóneo para su uso en Internet.
Una de estas características son los applets. Un applet es un programa dinámico e interactivo que se
puede ejecutar dentro de una página Web que se carga en un navegador Web, y otra característica para
la utilización de Java en Internet son los servlets. Un servlets es una aplicación Java que se ejecuta
sobre un servidor y que atiende una serie de peticiones realizadas desde un cliente que será un
navegador Web. A diferencia de los applets los servlets no presentan interfaz gráfico.
Java toma prestadas características y sintaxis de diferentes lenguajes de programación. La sintaxis
básica de Java está sacada del lenguaje C/C++ aunque al contrario de estos lenguajes Java es un
lenguaje fuertemente tipado.
De Smalltalk Java toma conceptos como el recolector de basura (garbage collector, concepto que se
explicará más adelante) y un sólido modelo de orientación a objetos, como iremos comprobando a lo
largo del presente curso. De Objective-C, Java toma el concepto de interfaz, en el capítulo
correspondiente veremos como define e implementa Java los interfaces.
El mecanismo de herencia que posee Java, se denomina herencia simple, esto quiere decir que cada
clase Java sólo puede tener una superclase o clase padre. En otros lenguajes de programación como
C++, las clases pueden heredar de diferentes superclases, esto se denomina herencia múltiple, la cual
no emplea Java. Pero Java posee un mecanismo que le permite simular la herencia múltiple, este
mecanismo se consigue a través de los interfaces.
Como ya adelantábamos, el concepto de interfaz lo toma Java del lenguaje Objective-C. Un interfaz es
una colección de nombres de métodos sin definiciones reales que indican que una clase tiene un
conjunto de comportamientos, además de los que la clase hereda de sus superclases. Por lo tanto un
interfaz es una lista de métodos sin ninguna implementación, la palabra reservada implements es
utilizada en la declaración de una clase para indicar que implementa los métodos de un interfaz
determinado. Más tarde, en el curso, retomaremos el concepto de interfaz.
Al ser un lenguaje de Programación Orientada a Objetos, la unidad básica dentro de la programación
en Java va a ser la clase y el objeto. Java está compuesto por un gran número de clases que se agrupan
y clasifican en paquetes, en el capítulo dedicado a la POO con Java veremos como se estructura la
jerarquía de clases que presenta el lenguaje.
Pero a pesar de estar compuesto de clases, en Java nos encontramos también con una serie de tipos de
datos predefinidos similares a C o C++, estos tipos, denominados tipos primitivos son: int, byte, short,
char, long, boolean, float y double. Por lo tanto en Java podremos definir variables de estos tipos de
datos, en este caso se empleará la denominación variable en lugar de objeto, ya que los tipos
primitivos no son clases. En el próximo capítulo volveremos a retomar los tipos primitivos.
Java presenta una completa y compleja jerarquía de clases, esto le hace un lenguaje muy potente, ya
que para cada tarea a realizar existe una clase determinada que se encuentra especializada para realizar
una función específica.
Se puede considerar que por un lado tenemos el lenguaje Java, que es con el que escribimos nuestras
clases y compilamos, y por otro se encuentra la Máquina Virtual de Java, JVM (Java Virtual
23
Programación en Java
© Grupo EIDOS
Machine). Para garantizar que los programas son independientes de la plataforma (capacidad del
programa de trasladarse con facilidad de un sistema computacional a otro) hay una sola arquitectura a
la que todos los programas Java son compilados, es decir, cuando se compila un programa Java en una
plataforma Windows/Intel, se obtiene la misma salida compilada que en un sistema Macintosh o Unix.
El compilador compila no a una plataforma determinada, sino a una plataforma abstracta llamada
Máquina Virtual de Java.
La especificación de la Máquina Virtual de Java define la JVM como: una máquina imaginaria que se
implementa emulando por software una máquina real. El código para la Máquina Virtual de Java se
almacena en ficheros .class, cada uno de los cuales contiene al menos el código de una clase pública,
en el capítulo dedicado a la POO con Java comentaremos en detalle la visibilidad de las clases
(públicas o privadas).
Por lo tanto, cuando se escribe una aplicación Java o un applet Java (después veremos estos dos tipos
de programas que se pueden construir en Java), se está escribiendo un programa diseñado para
ejecutarse en la Máquina Virtual de Java. La Máquina Virtual de Java requiere un código binario
especial para ejecutar los programas Java, este código no debe tener instrucciones relacionadas
específicamente con la plataforma.
Los archivos binarios Java, que se obtienen al compilar el código fuente, son independientes de la
plataforma y pueden ejecutarse en múltiples plataformas sin necesidad de volver a compilar el fuente.
Los archivos binarios Java se encuentran en una forma especial llamada bytecode, que son un conjunto
de instrucciones muy parecidas al código máquina, pero que no son específicas para ningún
procesador.
El compilador Java toma el programa Java y en lugar de generar código máquina específico para los
archivos fuente, genera un bytecode. Para ejecutar un programa Java, se debe ejecutar un programa
llamado intérprete de bytecode, el cual a su vez ejecuta el programa Java deseado. En el caso de los
applets el intérprete se encuentra integrado en el navegador Web con capacidad para Java y es
ejecutado automáticamente. El concepto de independencia de la plataforma es ilustrado en la Figura 3.
Figura 3
24
© Grupo EIDOS
2. Introducción al lenguaje Java
La desventaja de utilizar los bytecodes es la velocidad, estos programas tienen una menor velocidad de
ejecución, ya que previamente a ejecutarse sobre el sistema, deben ser procesados por el intérprete.
Aunque según van apareciendo nuevas versiones del lenguaje Java la velocidad de ejecución se va
mejorando, se alcanzará la máxima velocidad y eficacia cuando aparezcan los chips Java. Se trata de
una versión hardware del intérprete Java presente en los programas navegadores con capacidad para
este lenguaje, es decir, es una implementación hardware de la Máquina Virtual de Java. Estos chips
serán los encargados de traducir el código Java sobre la marcha, por lo tanto no será necesaria la etapa
intermedia de interpretación de los bytecodes, como se veía en la figura, con lo que se aumentará de
forma considerable la velocidad de ejecución. En un principio Sun MicroSystems tiene pensado
comercializar estos chips en una especie de tarjeta, que se insertará en un slot del ordenador.
Una polémica entorno a Java se produjo cuando Microsoft lanzó su versión del lenguaje, a través de su
herramienta de desarrollo Visual J++. Desde esta herramienta corremos el peligro de generar código
Java que sí sea dependiente de la plataforma, en este caso de la plataforma Windows, y si estamos
utilizando el lenguaje Java es porque nos interesa su característica de independencia de la plataforma,
por lo tanto hay que ser cuidadosos en este aspecto.
Java incluye características de seguridad para reforzar su empleo en Internet. Un problema de
seguridad potencial tiene que ver con los applets de Java, que son código ejecutable que opera en la
máquina local del usuario que se conecta a la página Web en la que se encuentran los applets. Java
emplea verificación de código y acceso limitado al sistema de archivos para garantizar que el código
no dañe nada en la máquina local. En el tema y apartados correspondientes comentaremos las
restricciones de seguridad que presentan los applets.
Debido a que en Java no existen punteros, la asignación y liberación de recursos tiene algunas
particularidades. El recolector de basura (garbage collector) es otro de los aciertos del lenguaje Java,
se trata de un hilo de ejecución (thread) que se encarga de rastrear en tiempo de ejecución cada objeto
que se ha creado, advierte cuándo desaparece la última referencia a él y libera el objeto por el
programador. Es decir, en Java la asignación de memoria es dinámica y automática, cuando se crea un
nuevo objeto se destina la cantidad de memoria necesaria, y una vez que se ha dejado de utilizar ese
objeto el recolector de basura lo localiza y se apropia de la memoria que empleaba el objeto. Por lo
tanto no es necesario realizar ninguna liberación explícita de memoria.
Programas en Java: applets y aplicaciones
Los programas Java comprenden dos grupos principales: applets y aplicaciones. Un applet
(denominado por algunos autores miniaplicación) es un programa dinámico e interactivo que se
ejecuta dentro de una página Web, desplegada por un navegador Web con capacidad para Java como
puede ser Navigator de Netscape o el Internet Explorer de Microsoft, por lo tanto los applets para
ejecutarse dependen de un navegador Web habilitado para Java. Los applets es uno de los principales
motivos que han hecho al lenguaje Java tan popular, sin embargo no son tan potentes como las
aplicaciones.
Las aplicaciones Java son programas más generales que los applets. No requieren un navegador para
ejecutarse sólo necesitan de un intérprete de Java para la plataforma en la que se ejecutarán, y pueden
emplearse para realizar todo tipo de aplicaciones posibles. Una de las primeras aplicaciones Java que
se realizaron fue el navegador Web HotJava. Una aplicación Java consiste en una o más clases, lo
único que se necesita para ejecutar una aplicación es tener una clase que funcione como punto de
arranque para el resto del programa.
25
Programación en Java
© Grupo EIDOS
Para ejecutar una aplicación Java en un sistema es necesario ejecutar primero la Máquina Virtual, es
decir, el intérprete de Java, que permitirá la ejecución de la aplicación, ya que traduce los bytecodes en
código nativo. Este paso intermedio a la ejecución de la aplicación Java puede ser molesto, por ello
algunos constructores de software han anunciado que pronto incorporarán la Máquina Virtual de Java
a sus sistemas operativos, de esta forma las aplicaciones Java se ejecutarán de una manera muy similar
a las aplicaciones de otros lenguajes (C, C++, Pascal, etc.…), todas las llamadas a la Máquina Virtual
serán transparentes al usuario y serán manejadas por el sistema operativo. Esto es similar a lo que
ocurre cuando un navegador carga un applet y lo ejecuta en la Máquina Virtual de forma automática.
Similitudes y diferencias entre Java y C++
Se debe indicar que existen pocas diferencias entre Java y C++, al menos en cuanto al aspecto del
código fuente al menos. Las diferencias, gratas en algunos puntos e ingratas en otros, comenzamos a
avistarlas cuando profundizamos en el lenguaje. Podremos rentabilizar la inversión de tiempo y
esfuerzo invertidos en aprender C++ y controlar la programación orientada a objetos. Todos estos
conocimientos serán aprovechables por el programador en Java, sin embargo no es cierto que para
conocer Java se debe estudiar previamente C/C++, Java es un lenguaje por sí mismo.
Las cadenas de Java no son punteros a cadenas de caracteres como en C/C++, ya que Java no soporta
punteros. Este modo de concebir las cadenas libera al programador de la engorrosa manipulación de
cadenas de C/C++. Para manejar cadenas en una aplicación Java disponemos del tipo de dato String,
que no es más que una clase definida en el sistema.
Los archivos .class funcionan en cualquier máquina, ya que el intérprete encargado de ejecutarlos está
codificado de forma nativa en el sistema receptor de la aplicación. Para lograr lo anteriormente
expuesto Java es interpretado, por lo que la velocidad de los ejecutables (que realmente no lo son) es
menor que la que lograríamos con el mismo código en C++ (de 10 a 20 veces mayor). Esto no debe
preocuparnos, ya que diferentes tecnologías consiguen disminuir esta diferencia llegado, en algunos
casos, a igualar la velocidad de C++. De entre estas tecnologías destaca la compilación Just-in-time,
que permite la conversión en tiempo de ejecución a instrucciones nativas de la máquina en la que se
estén ejecutando los bytecodes.
Dispondremos de una inmensa jerarquía de clases diseñadas para la creación de aplicaciones en la
Web. Jerarquía probada y en constante crecimiento. Sun Microsystems facilita gratuitamente una
copia del JDK del producto (acorde al sistema operativo bajo el que funcionará), con todos los
elementos necesarios para crear aplicaciones con Java. La distribución del intérprete no es necesaria si
estamos distribuyendo una miniaplicación (applet) enlazada a una página Web, ya que el navegador
correspondiente sería capaz de realizar una compilación Just-in-Time del applet. En caso de no ser así
y sí necesitar el intérprete, éste sería de libre distribución.
Otra diferencia notable es que Java no soporta la herencia múltiple, es decir, no puede heredar de
varias clases, sin embargo C++ si soporta heredar de varias clases padre. Se puede decir que Java
ofrece una Programación Orientada a Objetos simplificada en este aspecto.
Versiones del lenguaje
La última versión del lenguaje Java es lo que se denomina Plataforma Java 2 (Java 2 Platform) que se
corresponde con las dos últimas versiones de la herramienta de desarrollo de Sun Microsystems, es
decir, con el JDK (Java Development Kit) 1.2 y el JDK 1.3, este último de muy reciente aparición.
Anteriormente había una correspondencia entre la versión del lenguaje Java y la versión de la
herramienta JDK, así la versión 1.1 de Java se correspondía con la versión 1.1 del JDK.
26
© Grupo EIDOS
2. Introducción al lenguaje Java
Ahora vamos a comentar las correspondencias entre las versiones de Java y las distintas herramientas
de desarrollo de las que vamos a tratar en este curso. Aunque desde este momento se debe aclarar que
este curso no está enfocado a la utilización de ninguna herramienta de desarrollo de terceros como
Borland JBuilder o Microsoft Visual J++, sino que se trata la versión estándar del lenguaje, es decir, la
que ofrece Sun con su JDK, y en este caso se trata del JDK 1.3, aunque todo lo que vamos a ver es
aplicable también al JDK 1.2.
La herramienta de desarrollo de Java de la compañía Microsoft, Visual J++ 6.0, soporta la versión 1.1
del lenguaje. Esta herramienta puede interesarnos si lo que vamos a utilizar es la versión 1.1 de Java,
esto no es ninguna contradicción, ya que la propia Sun sigue ofreciendo una versión del JDK para la
versión 1.1 de Java, se trata del JDK 1.1.8. La versión 1.1 de Java sigue estando vigente debido a la
rápida evolución que está sufriendo el lenguaje.
Algunos de los ejemplos ofrecidos en el curso no funcionarán dentro de Visual J++ ya que se
corresponderán con nuevas características de Java, de todas formas esta situación se le indicará al
lector cuando se produzca.
La herramienta de desarrollo Borland JBuilder 3.5, de la compañía Inprise, implementa la Plataforma
Java 2, equiparándose a la herramienta de Sun JDK 1.2. Si el lector quiere utilizar la última versión de
Java es recomendable que utilice JBuilder 3.5. Además en diversos estudios comparativos entra
distintas herramientas de desarrollo del lenguaje Java, Borland JBulider ha sido casi siempre la mejor
considerada.
El lenguaje Java todavía no ha tenido una versión definitiva ni estable, cosa que supone un grave
inconveniente para los sufridos desarrolladores, que tenemos que estar actualizándonos
continuamente. Según se ha comunicado desde Sun Microsystems, la versión Java 2 Platform es la
versión definitiva del lenguaje, esperemos que esto sea cierto, ya que desde mediados/finales del año
1996 se han sucedido un gran número de versiones, y el seguimiento y aprendizaje del lenguaje resulta
verdaderamente agotador y desconcertante.
Entornos de desarrollo
Como ya hemos dicho a lo largo de este capítulo vamos a comentar en el curso tres herramientas de
desarrollo distintas para utilizar en la realización de nuestros programas en Java, aunque siempre
utilizando el estándar del lenguaje Java.
La herramienta de desarrollo oficial de Java es el JDK (Java Development Kit). La herramienta JDK la
podemos obtener de forma gratuita desde el sitio Web que posee Sun Microsystems dedicado por
completo al lenguaje Java, http://java.sun.com.
Las versiones actuales de este producto son la 1.1.8, la 1.2 y la 1.3, correspondiéndose la primera con
la versión 1.1 de Java y las dos últimas con la última versión denominada Java 2 Platform. Debido a
que muchos fabricantes todavía no soportan la versión nueva de Java y también debido a que la nueva
versión es bastante reciente, coexisten las dos versiones del lenguaje.
Sun Microsystems no nos lo pone fácil con las nomenclaturas de versiones y de productos, y si
acudimos a su sitio Web para descargar el JDK, vemos que no aparece la versión 1.2 ni la 1.3. Esto es
debido a que el JDK se encuentra incluido en lo que se denomina Java 2 SDK (Software Development
Kit). De esta forma si deseamos la versión 1.2 del JDK veremos descargar el producto Java 2 SDK
Standard Edition v 1.2, y si lo que deseamos es tener el JDK 1.3 debemos conseguir el Java 2 SDK
Standard Edition v 1.3. A los efectos del presente curso las dos versiones del Java 2 SDK son válidas.
27
Programación en Java
© Grupo EIDOS
El código que generemos en los ejemplos y las características del lenguaje que vamos a utilizar a lo
largo de todo el curso se corresponden todas con el JDK de Sun Microsystems, es decir, vamos a
utilizar Java estándar. Por lo tanto el único software completamente necesario para seguir el curso
satisfactoriamente es el Java 2 SDK Standard Edition.
El entorno de desarrollo ofrecido por Sun es bastante pobre, ya que se basa en modo comando, es
decir, no ofrece un entorno gráfico, el código lo escribiremos con un editor de texto y luego lo
podremos compilar, ejecutar, depurar, etc., con una serie de programas incluidos en el JDK, pero
siempre desde la línea de comandos.
Pero para mostrar el panorama actual de herramientas de desarrollo de Java se ha decido incluir dos
entornos de desarrollo más. Además estos entornos son más amables de utilizar que el ofrecido por
Sun, ofreciendo cada uno de ellos un interfaz gráfico que permite que el desarrollo de aplicaciones
Java sea más sencillo y rápido.
Una de estas herramientas de desarrollo que vamos a comentar en este curso es, como ya hemos
adelantado, Microsoft Visual J++ 6.0. Esta herramienta forma parte de la suite de herramientas de
Microsoft Visual Studio 6.0.
Microsoft Visual J++ 6.0 ofrece un entorno de desarrollo amigable. Sin embargo, como ya habíamos
comentado anteriormente, el JDK ofrece un entorno de desarrollo bastante árido, el editor que se
propone es el Edit de MS-DOS o cualquier otro editor de texto que no genere un formato para el texto.
El entorno de compilación y depuración es bastante pobre y además se basa completamente en la línea
de comandos de MS-DOS.
El mayor inconveniente que presenta Visual J++ 6.0 es que en algunos casos puede generar código
Java que no pertenece al estándar del lenguaje. Pero en este curso se va a tratar el lenguaje Java
estándar no la implementación particular de Microsoft. Se podría decir que vamos a ver el lenguaje
Java 100% de Sun.
Aunque no utilicemos la implementación que realiza Microsoft del lenguaje, nos podemos beneficiar
del entorno de desarrollo que ofrece Microsoft, que como ya hemos dicho es mucho más fácil de
utilizar y más amigable que el ofrecido por Sun.
A lo largo del curso comentaremos en detalle Visual J++ 6.0 y cómo debemos utilizarlo para generar
código Java completamente estándar.
Pero el mayor inconveniente que presenta Visual J++ 6.0 es que recoge únicamente hasta l versión 1.1
del lenguaje Java, esto en algunos casos puede ser suficiente, pero veremos que en otros no, sobre todo
si queremos utilizar las nuevas características de Java 2, como pueden ser la utilización de
componentes avanzados (Swing) para construir interfaces de usuario o el acceso a datos mejorado que
ofrece a través de JDBC 2.0.
Otro punto en contra de Visual J++ es que ofrece un entorno gráfico, pero no es todo lo gráfico que se
podría desear, es decir, no permite construir interfaces de usuario al modo de otras herramientas como
pueden ser Visual Basic o Delphi, dónde podemos arrastran controles o elementos del interfaz de
usuario y situarlos en el lugar deseado, sino que todo lo debemos hacer a través de programación sin
que Visual J++ genera una única línea de código fuente.
Podemos decir que el segundo entorno de desarrollo utilizado en este curso Borland JBuilder 3.5
soluciona las dos deficiencias que plantea Visual J++. Por un lado ofrece la versión del lenguaje
correspondiente a la Java 2 Platform(Plataforma Java 2) y un entorno de desarrollo que permite la
creación visual de interfaces de usuario con generación de código Java incluida.
28
© Grupo EIDOS
2. Introducción al lenguaje Java
De todas formas en los sucesivos capítulos veremos con más detenimiento cada uno de estos entornos
de desarrollo.
Características de la plataforma Java 2
Este apartado muestra de forma general las características que incluye el lenguaje Java en su última
versión y que han ido evolucionando desde la versión 1.0. El contenido de este apartado es a modo
informativo, el lector no tiene porque comprender los términos que se van a utilizar en este apartado.
Este apartado puede ser útil a lectores que ya conozcan algunas de las versiones anteriores del lenguaje
y también nos sirve para observar las nuevas características que ha ido implementando el lenguaje.
•
Internacionalización: permite el desarrollo de applets localizables, es decir, un mecanismo de
localización sensible a la hora y fecha locales. Se incluye también la utilización de caracteres
Unicode. Unicode tiene la capacidad de representar unos 65.000 caracteres, un número
bastante amplio para incluir los caracteres de la mayoría de los lenguajes hablados hoy en día.
•
Seguridad y firma de applets: el API (aquí entendemos como API un conjunto de clases más o
menos complejo que cumplen una funcionalidad común) de seguridad de Java está diseñado
para permitir a los desarrolladores incorporar funcionalidades de seguridad a sus aplicaciones.
Contiene APIs para firma digital y tratamiento de mensajes. Existen interfaces para la gestión
de claves, tratamiento de certificados y control de accesos. También posee APIs específicos
para el mantenimiento de certificados X.509 v3 y para otros formatos de certificado. Además
se ofrecen herramientas para firmar ficheros JAR (Java Archive).
•
Ampliaciones del AWT: el AWT (Abstract Window Toolkit) es un API encargado de
construir el GUI (Graphical User Interface, interfaz de usuario gráfico). El AWT de la versión
1.0 fue diseñado para construir sencillos interfaces de usuario por lo tanto se hizo necesario el
ampliar el AWT en la versión 1.1. Estas ampliaciones tratan de resolver las principales
deficiencias del AWT , además, representan un comienzo en la creación de una infraestructura
más rica para el desarrollo de complicados interfaces de usuario, esto incluye: APIs para la
impresión, componentes scroll, menúes popup, portapapeles (copiar/pegar), cursores para cada
componente, un modelo de eventos nuevo basado en la delegación de eventos (Delegation
Event Model), ampliaciones en el tratamiento de imágenes y gráficos, y un tratamiento de
fuentes más flexible de cara a la característica de internacionalización.
•
JavaBeans: inicialmente se puede definir un JavaBean como un componente software
reutilizable, que puede ser manipulado visualmente en una herramienta de desarrollo. Consta
de una colección de una o más clases Java que suelen encontrarse en un único fichero JAR
(Java Archive). Un JavaBean sirve como un objeto independiente y reusable. El API de los
JavaBeans define un modelo de componentes software para Java. Este modelo, desarrollado
de forma coordinada entre las compañías Sun, Borland Inprise y otras, es una especificación
de como codificar estos componentes para que puedan ser utilizados en diferentes entornos de
programación. Un ejemplo de la utilización de los JavaBeans la ofrece la herramienta de
desarrollo de Java JBuilder, de la compañía Borland Inprise (también ofrece una herramienta
llamada BeansExpress que permite una fácil construcción de JavaBeans). Esta herramienta
posee un gran número de JavaBeans como pueden ser los diferentes componentes para la
construcción interfaces de usuario (botones, listas, paneles, ventanas, botones de selección,
barras de menú, etc.…). Al igual que estos componentes son utilizados por el entorno
JBuilder, se pueden incluir dentro de cualquier herramienta de desarrollo que respete y
reconozca las características de los JavaBeans. Si creamos un componente con las
29
Programación en Java
© Grupo EIDOS
especificaciones de los JavaBeans, ocultando los detalles de implementación y solamente
mostrando las propiedades, métodos y eventos públicos conseguiremos las siguientes ventajas:
1. Pueden ser utilizados por otros desarrolladores usando un interfaz estándar.
2. Se pueden situar dentro de las tablas de componentes de diferentes herramientas de
desarrollo.
3. Se pueden comercializar por separado como si se tratara de un producto.
4. Pueden ser actualizado con un impacto mínimo sobre los sistemas en los que se encuentre.
5. Es un componente escrito en Java puro, por lo tanto es independiente de la plataforma.
30
•
Ficheros JAR (Java ARchive): este formato de fichero presenta muchos ficheros dentro de
uno, y además permite la compresión de los mismos. Varios applets y sus componentes
requeridos (ficheros .class, imágenes y sonidos) pueden encontrarse dentro de un fichero JAR
y por lo tanto cargados por un navegador en una sola petición HTTP. Este formato de ficheros
es parecido a los ficheros Cabinet de Microsoft.
•
Mejoras en Swing: Swing es el otro API existente para crear interfaces de usuario gráficos,
ofrece muchos más componentes que el API AWT. Además los componentes Swing permiten
modificar su aspecto y comportamiento.
•
Ampliaciones de Entrada/Salida: el paquete java.io ha sido ampliado con flujos de caracteres,
que son parecidos a los flujos de bytes excepto en que contienen caracteres Unicode de 16 bits
en lugar de 8 bits. Los flujos de caracteres simplifican la escritura de programas que no
dependen de una codificación de caracteres determinada, y son más fácil de internacionalizar.
•
El Paquete java.math: este paquete ofrece dos nuevas clases BigInteger y BigDecimal, para el
tratamiento de operaciones numéricas. Un paquete, que definiremos en detalle más adelante,
podemos decir que es un conjunto de clases relacionadas entre sí. Los paquetes es una manera
de organizar y clasificar las clases que posee el lenguaje Java.
•
JDBC 2.0: JDBC es un API para ejecutar sentencias SQL (Structured Query Language), que
ofrece un interfaz estándar para el acceso a bases de datos. Ofrece un acceso uniforme a un
amplio número de bases de datos. El código de este API está completamente escrito en Java,
de hecho se encuentra en el paquete java.sql, por lo tanto ofrece también independencia de la
plataforma. Está basado y es muy similar a ODBC (Open Database Connectivity). Usando
JDBC es muy sencillo enviar sentencias SQL a cualquier base de datos relacional, gracias al
API de JDBC no es necesario escribir un programa que acceda a una base de datos Sybase,
otro programa que acceda a una base de datos Oracle u otro que acceda a una base de datos
Informix, el mismo programa servirá para ejecutar las sentencias SQL sobre todas estas bases
de datos. Básicamente JDBC permite: establecer una conexión con una base de datos, enviar
sentencias SQL y procesar los resultados. En la versión 2.0 de JDBC se ha mejorado el acceso
a datos haciéndolo más potente a través de la creación de diferentes tipos de cursores para
acceder a los datos.
•
JFC (Java Foundation Classes): con este nombre se agrupan un gran número de clases
especializadas en la construcción de interfaces de usuario. Dentro de JFC se encuentran las
APIs ya mencionadas para la construcción de interfaces de usuario, es decir, Swing y AWT.
Además incluye características de accesibilidad que permiten utilizar tecnologías tales como
lectores de pantalla o dispositivos Braille, utilización de gráficos en 2 dimensiones, soporte
© Grupo EIDOS
2. Introducción al lenguaje Java
para arrastrar y soltar (Drag and Drop), posibilidad de modificar el aspecto y comportamiento
de los componentes gráficos (pluggable Look and Feel), etc.
•
Servlets: los servlets son aplicaciones Java que se ejecutan en servidores Web y que permiten
construir páginas Web de forma dinámica. También permiten obtener información de los
formularios enviados por los clientes. Son una tecnología que realiza funciones similares a los
scripts CGI (Common Gateway Interface) y a las páginas ASP (Active Server Pages).
31
Sintaxis del lenguaje Java
Introducción
Este capítulo no podría faltar tampoco en nuestro curso de Java, este es el típico capítulo que tienen
todos los cursos o libros que traten un lenguaje de programación.
En este capítulo vamos a tratar la sintaxis del lenguaje Java. Los alumnos que ya conozcan C o C++
verán que la sintaxis es muy similar, de todas formas recomiendo a todos la lectura de este capítulo.
Este capítulo es necesario para saber como escribir el código Java, en el próximo capítulo veremos
como implementar los mecanismos de la POO a través de Java.
Veremos que muchos de los elementos del lenguaje Java son comunes al resto de los lenguajes
existentes.
Identificadores
Los identificadores son literales que representan nombres únicos para ser asignados a objetos,
variables, clases y métodos, de este modo el compilador puede identificarlos unívocamente. Estos
nombres sirven al programador, si éste les da sentido.
Por ejemplo, si vamos a implementar una clase para manejo del fichero de clientes, sería
recomendable que la misma se llamara algo así como ClientManager, o ManejadordeCliente, no
tendría sentido, aunque es totalmente válido llamarla Aj23.
Programación en Java
© Grupo EIDOS
Existen de todos modos algunas limitaciones a la hora de dar nombres a los identificadores, que son
las mismas que en la mayoría de los lenguajes.
Los identificadores tienen que comenzar por una letra, un subrayado o un símbolo de dólar, tras este
carácter, se pueden utilizar combinaciones de los ya mencionados y números del 0 al 9. No pueden
utilizarse, como es lógico, las palabras clave del lenguaje como identificadores (que mostraremos más
adelante). Aunque estos identificadores pueden ser cualquier longitud, para el compilador sólo son
significativos los primeros 32 caracteres.
Se debe tener cuidado al escribir los identificadores, porque Java distingue entre mayúsculas y
minúsculas, de este modo, Nombre y nombre son dos variables diferentes.
Java permite el uso de cualquier carácter del código Unicode para definir identificadores, de esta
forma, el identificador Año es totalmente válido en Java.
A la hora de nombrar los identificadores, Java presenta una serie de normas de estilo de uso
generalizado, lo que significa que se pueden adoptar o rechazar sin que ello influya en lo más mínimo
en el comportamiento del código, pero se debe advertir que la mayor parte del código Java existente
utiliza estas normas.
En Java se utiliza el word-mixing (EsteEsUnEjemploDeWordMixing), la primera letra se escribe en
mayúscula (ManejadorDeClientes, por ejemplo) para las clases, y no se utilizan los signos subrayado
ni dólar como primer carácter de un identificador. Esta última norma, tiene su explicación: las librerías
de C que se utilizan en Java, suelen utilizar los caracteres de subrayado y dólar como primeros
caracteres de sus identificadores, por lo que evitándolos, nos evitamos problemas de conflictos de
duplicidad. Otro dato de interés a este respecto es que a las clases no se les pone ninguna
identificación delante, ya que en Java todo son clases.
Los nombres de variables, objetos y métodos en Java, aunque se utiliza word-mixing, comienzan con
una minúscula, por ejemplo: toUpper().
Aun cuando esta es una sugerencia y no una norma, es conveniente seguirla, ya que así está notada
toda la jerarquía de clases de Java, y al distinguir el lenguaje entre mayúsculas y minúsculas, ni
podemos escribir estos nombres de otro modo ni debemos dar a nuestros identificadores de variables y
métodos nombres con la primera letra en mayúsculas (para mantener la consistencia con el estilo de
los creadores del Java).
Sin embargo, en el caso de las variables (aquí y en adelante el termino variable se refiere al nombre de
los objetos o de las variables de los tipos primitivos de Java), a muchos programadores nos gusta notar
el tipo al que pertenecen, clarificando así el código. De este modo, si una variables es de tipo cadena
(string) al nombre le precedería la letra "s". Por ejemplo, sNombre es el identificador de una variable
que contiene una cadena.
En la Tabla 1, se ofrece una sugerencia para los prefijos de nombres de variables atendiendo a su tipo.
Por supuesto, que esto es sólo una indicación y no una norma que se tenga que seguir.
Identificadores de variables
34
Prefijo
Tipo
Ejemplo
a
array
aElementos
b
boolean
bCancelar
© Grupo EIDOS
3. Sintaxis del lenguaje Java
c
char
cInicial
d
double
dDistancia
f
float
fInterés
i
integer
iColumna
l
long
lCantidad
s
string
sNombre
Tabla 1
Para los identificadores de variables que se refieren a objetos, utilizaremos tres letras como prefijo,
intentando que estas tres letras indiquen de la forma más clara posible el nombre de la clase a la que el
objeto pertenece.
Por ejemplo, si instanciamos un objeto de la clase Frame, el prefijo podría ser "frm", seguido del
nombre que queramos darle a la variable, por ejemplo, frmClientes.
Palabras clave
Las palabras clave son identificadores reservados por el lenguaje. Aunque el alumno sea programador
de C o C++, le sugerimos que lea con detenimiento la siguiente tabla, ya que Java tiene un número
mayor de palabras reservadas que C y C++.
Palabras Clave
abstract
continue
for
new
switch
boolean
default
goto
null
synchronized
break
do
if
package
this
byte
double
implements
private
threadsafe
byvalue
else
import
protected
throw
case
extends
instanceof
public
transient
catch
false
int
return
True
char
final
interface
short
Try
class
finally
long
static
Void
const
float
native
super
while
Tabla 2
35
Programación en Java
© Grupo EIDOS
Además de estas, existen algunas palabras reservadas, ahora no se utilizan pero es posible que se
conviertan en palabras clave en un futuro. Son las siguientes:
cast, future, generic, goto, inner, operator, outer, rest, var
Literales
Llamamos literales a aquellos elementos de un programa que no pueden variar. Se les llama también
constantes.
Los literales pueden ser: números, caracteres y cadenas. Los literales numéricos se dividen en: enteros,
coma flotante y booleanos (álgebra de Boole). De hecho, los literales booleanos están incluidos en
Java dentro de los numéricos por su herencia del C, ya que en C verdadero es 1 y falso 0. Los literales
de caracteres no son ASCII sino Unicode, por ello un carácter en Java necesita 16 bits para ser
representado, en lugar de 8 como ocurre en otros lenguajes.
Los literales dan en Java lugar a cada uno de los tipos básicos, anteponiéndoles la palabra reservada
correspondiente a la hora de declararlos.
Enteros
Los enteros, dependiendo de la precisión que el número pueda tener se dividen en los siguientes tipos
primarios:
•
byte 8 bits complemento a dos
•
short 16 bits complemento a dos
•
int 32 bits complemento a dos
•
long 64 bits complemento a dos
Los enteros se almacenan por defecto internamente como tipo int, que es un valor de 32 bits con signo.
Si fuese necesario que un literal entero se almacenase como de tipo long, puede hacerse añadiendo una
l o L al final del literal, lo que hace que el número se guarde internamente como un valor de 64 bits.
Así, el número 42 se representaría como de tipo int, mientras que 42L, sería un long.
Estos, pueden representarse en tres formatos diferentes: decimal, octal y hexadecimal. Dependiendo de
la base del sistema numérico en la que se deseen representar.
Los enteros en su representación decimal, no necesitan ninguna notación especial. Para hexadecimal
(base 16) se antepone el prefijo 0x o 0X, y para base 8 (sistema octal) se antepone un 0 inicial. Así, el
número decimal 42 en Java puede representarse como indica la Tabla 3.
Decimal Hexadecimal Octal
42
0x2A
Tabla 3
36
052
© Grupo EIDOS
3. Sintaxis del lenguaje Java
Coma flotante
Al igual que los literales enteros pueden ser de cuatro tipos diferentes, los reales en coma flotante –
números con parte entera y parte decimal–, pueden ser de dos tipos: float y double. Los primeros se
almacenan en 32 bits, mientras que los segundos se almacenan en 64 bits. La única diferencia, es su
capacidad de precisión, debida la mayor cantidad de información que necesitan para representarlos.
Por defecto, los literales en coma flotante son del tipo double, a diferencia de los enteros, donde no se
toma como predeterminado el de mayor precisión. Pero si se desea expresar uno de estos literales en
tipo float, basta con añadir una f o F al final del literal (para forzar tipo a double se añade una d o D,
pero puesto que este es ya tipo predeterminado, esto sólo sirve en caso de querer dar mayor claridad al
código).
Ambos (float y double) pueden representarse en notación estándar (3,14159) o científica (314159e-5).
•
float 32 bits IEEE 754
•
double 64 bits IEEE 754
Booleanos
El tipo booleano se ha implementado en Java para representar los valores de verdadero y falso, que en
C se representan con un literal entero (0 falso y 1 o cualquier cosa que no sea 0, verdadero). Se utiliza
para ello dos palabras reservadas: true y false.
•
true verdadero
•
false falso
Caracteres
Los caracteres, que se refieran un único carácter Unicode, se representan al igual que en C, entre
comillas simples. También pueden incluirse caracteres de control no imprimibles. En la Tabla 4 se
ofrecen estos caracteres.
Descripción
Representación
Barra invertida
\\
Continuación
\
Retroceso
\b
Retorno de carro
\r
Alimentación formularios
\f
Tabulador horizontal
\t
37
Programación en Java
© Grupo EIDOS
Línea nueva
\n
Comillas simples
\’
Comillas dobles
\"
Carácter unicode
\udddd
Carácter octal
\ddd
Tabla 4
Ejemplos de literales de carácter: a \n \u0042
Cadenas
Un literal de cadena es un conjunto de caracteres agrupados. Se representan entre comillas dobles.
Java, cada vez que encuentra un literal de tipo cadena, crea una nueva instancia de la clase String, a
diferencia del lenguaje C, donde un string es un array de caracteres. En Java podemos también, si así
lo deseamos, crear un array de caracteres, pero el lenguaje proporciona el tipo String para estos
menesteres.
Por ejemplo: "Mi mamá me mima, mi mamá me ama"
El literal null
El literal null se corresponde con el tipo null, quien tiene un solo valor: la referencia nula.
Operadores
Los operadores en Java, como se verá tienen también gran influencia del C, de hecho, son casi los
mismos, y funcionan casi exactamente igual. Los veremos más con detenimiento en el apartado de
expresiones y operadores.
Operadores (según tipo)
Unarios
++ --
-
~
Binarios
+
-
*
/
%
De bits
&
|
^
<<
>>
Relacionales
<
>
<= >=
= = !=
Booleanos
&
|
^
||
Aritméticos
38
&&
>>>
!
= = !=
?:
© Grupo EIDOS
3. Sintaxis del lenguaje Java
Cadenas
+
Asignación
=
+= -=
*=
/=
%=
&= |=
^=
Tabla 5
Separadores
Sólo hay un par de secuencias con otros caracteres que pueden aparecer en el código Java; son los
separadores simples, que van a definir la forma y función del código. Los separadores admitidos en
Java son los que se detallan en la Tabla 6
Separadores
( ) paréntesis
Para contener listas de parámetros en la definición y llamada a
métodos. También para definir precedencia en expresiones, contener
expresiones para control de flujo y rodear a las conversiones de tipo.
{ } llaves
Para contener los valores de matrices inicializadas automáticamente.
También se utiliza para definir un bloque de código, para clases,
métodos y ámbitos locales.
[ ] corchetes
Para declarar tipos matriz así como para referenciar los valores de la
matriz.
; punto y coma
Separador de sentencias.
, coma
Separa identificadores consecutivos en una declaración de variables.
También se utiliza para encadenar sentencias dentro de una sentencia
for.
. punto
Para separar nombres de paquete de subpaquetes y clases. También se
utiliza para separar una variable o método de una variable de
referencia.
Tabla 6
Comentarios
Los comentarios pueden ser considerados como un caso especial dentro de los elementos de la sintaxis
del lenguaje, ya que aunque estos sean reconocidos por el compilador, éste los ignora. Las razones son
tan obvias, que no vamos a entrar en detalles.
En Java existen tres tipos de comentarios (los dos del C y uno distinto).
Tipos de Comentarios
// comentario
Se ignoran todos los caracteres desde // hasta fin de línea
39
Programación en Java
© Grupo EIDOS
/* comentario Se ignoran todos los caracteres entre /* y */
*/
/** comentario Comentario de documentación (funciona como el anterior)
*/
Tabla 7
Merece especial atención el comentario de documentación. Cuando se emplea este tipo de
comentarios, podemos utilizar una herramienta especial llamada JavaDoc (desarrollada por Sun e
incluida en la herramienta JDK) para convertir automáticamente el texto que se encuentra abarcado
por este tipo de comentarios en documentación de nuestro código fuente. De este modo, la
documentación de nuestro código está dentro del propio código, y podemos crearla al tiempo que lo
escribimos o modificamos.
Esta herramienta además reconoce ciertos tokens dentro de esta área para dar formato al texto de
documentación que se va a crear, pero no vamos a profundizar más en ello.
Tipos de datos en Java
Como ya comentamos al hablar de los literales, cada uno de ellos se refiere a un tipo básico de datos,
aunque en Java hay algunos tipos de datos que no tienen representación como literales. Estos son los
arrays (o matrices) y las clases.
Quizás sea discutible considerar las clases como tipos de datos, pero si retomamos el capítulo
dedicado a POO, nos daremos cuenta de que al fin y al cabo, cuando creamos una clase lo que estamos
creando no es más que un sistema algebraico: definiendo elementos y operaciones sobre dichos
elementos; que es al fin y al cabo, lo que hacemos con los enteros, con los números reales o con las
cadenas, es decir con los tipos de datos preestablecidos por el lenguaje.
Ya comentábamos que una clase se podía asemejar a un tipo de datos y un objeto de la clase a una
variable de ese tipo de datos.
Veamos, aunque sea muy someramente, ya que los hemos explicado al hablar de los literales, cada uno
de los tipos de datos que Java como lenguaje nos proporciona. Aunque previamente, y para poder
poner ejemplos de declaración de tipos, debemos indicar la sintaxis de la sentencia de declaración.
Esta es (se debe observar que en Java, al igual que en C, las sentencias finalizan con un punto y coma
";"):
<tipo> <idetificador1> [= <valor1>] [, <identificadorN> [=
<valorN>]];
o bien, con asignación múltiple:
<tipo> <idetificador1>,... ,<identificadorN> = <valor>;
Enteros
Véase el apartado de Enteros en Literales
40
© Grupo EIDOS
3. Sintaxis del lenguaje Java
Tipos de datos Enteros
Tipo
Tamaño Ejemplo
byte
8 bits
Byte edad;
short
16 bits
Short alturaEdificio = 614;
int
32 bits
Int miCantidad = 3456740, tuCantidad = 645780;
long
64 bits
Long distanciaInical, distanciaFinal = 86868688;
Tabla 8
Coma flotante
Véase el apartado de Coma flotante en Literales
Tipos de datos de Coma Flotante
Tipo
Tamaño
Ejemplo
float
32 bits
float presión;
double
64 bits
double
distanciaEstelar;
Tabla 9
Booleanos
boolean seguir;
boolean cancelarProceso = true, abandonarAplicación = false;
Véase el apartado de Booleanos en Literales
Carácter
char inicial;
char miInicial = ´F´;
Véase el apartado de Caracteres en Literales
Cadenas
String miNombre = "Pep Rubirá"
String comillasDobles = "/""
41
Programación en Java
© Grupo EIDOS
Se deben hacer las siguientes consideraciones:
•
En este caso, la inicial del nombre de tipo (String, que como ya comentamos es una clase) está
en mayúsculas, esto, como ya dijimos es fundamental, ya que Java distingue entre mayúsculas
y minúsculas, por lo que si hubiésemos escrito el tipo de dato como "string", el compilador
nos reportaría un error al no encontrar ninguna clase que tuviese este nombre.
•
En la definición de datos de tipo carácter las comillas son simples y en la de tipo String son
dobles.
•
Para crear una cadena de caracteres donde se incluyan las comillas, utilizamos la secuencia de
escape correspondiente.
Véase el apartado de Cadenas en Literales.
Arrays
En Java, los arrays son objetos, son creados dinámicamente y pueden ser asignados a variables de tipo
Object, por lo que todos los métodos de la clase Object (esta clase la trataremos en un próximo
capítulo) pueden ser invocados desde un array.
Un array tiene un número entero de elementos que lo componen, y nos referimos a ellos por su índice,
que obviamente es un entero. Si un array tiene n elementos, el índice varía entre 0 y n-1 ambos
inclusive.
Un array puede tener 0 elementos, decimos entonces que el array está vacío. Del mismo modo, un
array puede tener una o más dimensiones. En cualquier caso, una vez creado el array éste no puede
cambiar de tamaño, es estático (existe en Java la clase Vector, que permite trabajar con "arrays"
dinámicos).
Todos los elementos de un array tienen que ser del mismo tipo, aunque estos pueden ser arrays, en tal
caso, debe llegar un momento en que los elementos de estos elementos sean datos de otro tipo.
Un array se "fabrica" en dos tiempos: el de declaración y el de creación. En el primero sólo se indican
el tipo y las dimensiones de éste, esta información es procesada por el compilador. En el segundo
tiempo se crea el array en memoria, para lo que se utiliza el operador new (este operador lo veremos
en siguiente capítulo para la creación de objetos), es entonces cuando indicamos el número de
elementos que va a contener.
Java no permite por lo tanto declarar arrays al estilo de C, donde al tiempo que se declara un array se
indica el número de elementos (int n[10] no es válido en Java). Esto es así porque Java no utiliza
punteros, sino objetos para crear variables de tipo array, lo que permite entre otras cosas comprobar
los límites del array cada vez que se va acceder a él, y reportar un error si se intenta sobrepasar sus
límites, en lugar de dejar colgada la aplicación y el sistema operativo, como ocurre en C.
La declaración de arrays en Java es casi igual que en C, salvo que los corchetes pueden utilizarse tras
el nombre de la variable (como en C) o tras el tipo de la variable. También pueden definirse los
elementos de un array en la declaración del mismo. Veamos algunos ejemplos de declaración de arrays
en el Código fuente 1.
int iNumeros[]; // Declara un array de enteros
char cAlafabeto[]; // Declara un array de caracteres
42
© Grupo EIDOS
3. Sintaxis del lenguaje Java
float fTemp[]; // Declara un array de floats
float[] fTemp; // Equivalente a la anterior
long lTabla[][]; // Array de dos dimensiones
char cAlfabeto []= new char[27] // Declara y construye in array de 27 elementos
float fTemp [][] = new float [10][3] // Declara un array de 2 dimensiones:
// una con 10 elementos y la otra con 3 elementos
int iPrimos = { 1,3,5,7 } // Declara un array de 4 elementos y le da valores
Código fuente 1
Esto mismo podría haber hecho como indica el Código fuente 2.
int iPrimos[];
iPrimos = new int[4];
iPrimos[0] = 1;
iPrimos[1] = 3;
iPrimos[2] = 5;
iPrimos[3] = 7;
Código fuente 2
Sin embargo, en las especificaciones de Sun para la versión 1.1 de Java, se ha ampliado levemente la
forma en cómo se pueden inicializar los arrays.
Ahora se permite inicializar los contenidos de un array al utilizar el operador new. Lo que flexibiliza la
creación de arrays. El Código fuente 3es ahora correcto.
String[] martians = new String[] { "Gidney", "Cloyd" };
Código fuente 3
Empaquetar tipos primitivos
Aunque Java es un lenguaje casi 100% orientado a objetos, internamente trabaja con dos tipos de
entidades: tipos primitivos y objetos. La forma en como maneja los tipos numérico, booleano y
carácter es muy similar a como lo hacen otros lenguajes procedurales como puedan ser C o Pascal, a
diferencia de otros lenguajes como SmallTalk para quien todo son objetos.
La única razón de que exista esta diferencia en Java, es que el manejo de tipos primitivos es más
eficiente que el de los objetos; y por lo tanto se puede conseguir un mayor rendimiento del sistema.
Sin embargo, muchos de los componentes del lenguaje Java sólo pueden trabajar con objetos, es decir
tienen que recibir una instancia que directa o indirectamente provenga de la clase Object, que es la raíz
de la jerarquía de clases de Java. Para resolver este conflicto, el lenguaje Java dispone de una serie de
clases que empaquetan los tipos primitivos.
El paquete java.lang (se debe recordar que un paquete es una forma de organizar y agrupar clases
relacionadas, en los capítulos siguientes trataremos más en detalle los paquetes) contiene las clases de
que se dispone en Java para empaquetar los tipos primitivos.
43
Programación en Java
© Grupo EIDOS
Clase
Tipo primitivo que empaqueta
Integer
Int
Long
Long
Float
Float
Double
Double
Boolean
Boolean
Tabla 10
Si se observa la tabla anterior, nos damos cuenta que no existen clases empaquetadoras para los tipos
primitivos byte y short, lo que realmente no supone ningún problema, ya que estos dos tipos pueden
tratarse como objetos utilizando la clase Integer.
Si echamos un vistazo a la jerarquía de clases del Java, se observará que todas las clases
empaquetadoras descienden de la superclase Number.
Conversión de tipos de datos
Java al igual que C admite el moldeado de tipo (casting) o conversión de tipos, lo que nos permite
convertir un tipo de dato en otro. Lo que se suele utilizar principalmente para cambiar el tipo de dato
devuelto por una función para que coincida con el tipo de dato esperado por otra a la que se lo vamos a
pasar como parámetro.
Para realizar el moldeado de tipo (casting), se coloca el tipo desea entre paréntesis a la izquierda del
valor que vamos a convertir.
El típico ejemplo que muestra esto es el de conversión del valor devuelto por el método read() del
sistema estándar de entrada. Este método devuelve un dato de tipo int, y normalmente deseamos
almacenarlo como un tipo char, lo convertiremos como muestra el Código fuente 4.
char c;
......................
......................
c = (char) System.in.read();
Código fuente 4
A la hora de convertir unos datos a otros, puede perderse información dependiendo de la capacidad de
almacenamiento que uno y otro tipo de dato tengan. Por lo que debe tener muy en cuenta la
información que hemos dado a este respecto en esta sección.
Si deseamos convertir un tipo de dato cuya capacidad de almacenamiento es de 64 bits (por ejemplo
un long) en otro de menor capacidad (por ejemplo un int, con 32 bits), es muy posible que en la
conversión perdamos información. Esto ocurrirá si los bits altos (aquellos que están a la izquierda en
44
© Grupo EIDOS
3. Sintaxis del lenguaje Java
una representación binaria del número) del dato long tenían información (lo que ocurre con toda
seguridad para números muy grandes).
Otro caso distinto de pérdida de información es el que ocurre cuando se moldea un double para
convertirlo en un long. Aunque la capacidad de almacenamiento de ambos sea de 64 bits, el tipo
double tiene parte entera y parte fraccionaria, mientras que el long sólo tiene parte entera.
Todo esto se debe tener muy presente cuando se vaya a realizar una conversión de tipo, ya que estos
errores a veces son difíciles de detectar y nos pueden dar más de un quebradero de cabeza.
En la Tabla 11 presentamos las conversiones en las que no puede haber pérdida de información
(conversiones seguras).
Moldeado sin Pérdida de Información
Tipo fuente Tipo Destino
byte
short, char, int, long, float, double
short
int, long, float, double
char
int, long, float, double
int
long, float, double
long
float, double
float
Double
Tabla 11
Bloques y ámbitos
Bloques
Al igual que C, Java utiliza las llaves ({} la primera de inicio y la otra de fin de bloque) para
determinar los bloques dentro de un programa. Todo lo que se encuentra entre estas dos llaves se
considera un bloque. Los bloques son parte de la sintaxis del lenguaje.
Los bloques pueden y suelen anidarse; utilizándose la sangría para clarificar el contenido de un
bloque. De este modo, el bloque más externo se sitúa al margen izquierdo del fichero de código fuente,
y cada vez que se anida un bloque se indenta (sangra) el texto que lo contienen un número
determinado de columnas, normalmente tres o cuatro.
El sangrado no tiene ninguna utilidad para el compilador, pero sin ella la lectura del código por parte
del programador es casi imposible.
45
Programación en Java
© Grupo EIDOS
Ámbitos
Los bloques además, definen los ámbitos de las variables. El ámbito se refiere a la longevidad de las
variables.
Una variable existe sólo dentro del bloque donde ha sido declarada, eliminándola el compilador una
vez que se sale de dicho bloque. Esto es cierto para todos los tipos de datos de Java. Si el dato es un
objeto, y su clase tiene un destructor asociado (distinto al destructor por defecto, que es interno y Java
lo maneja por sí solo) este es invocado al salir la variable de ámbito.
Cuando una variable sale de ámbito, es eliminada y la memoria que ésta ocupaba es liberada por el
recolector de basura (garbage collector).
Expresiones
Una expresión no es más que una secuencia de elementos que puede ser evaluada por el compilador.
Una llamada a una función, una asignación, un cálculo aritmético o lógico son algunos ejemplos de
expresiones.
Las expresiones suelen dividirse según su tipo, es decir según el tipo del valor que devuelven al ser
resueltas, en: aritméticas y lógicas. Dentro de cada una de ellas existen subdivisiones.
El compilador las analiza y, si está bien construido, las simplifica todo lo posible, evaluándose en
tiempo de ejecución.
Algunos ejemplos de expresiones aparecen en el Código fuente 5.
System.out.println( "Hola" + c );
a = 21;
b = a + b + c + 15;
c = (a + b) * (y-7);
d = 2 / 3 + 4 * 5
d = a && b;
e = (a && b) || c
Código fuente 5
Las expresiones se analizan de izquierda a derecha, salvo que existan paréntesis, en tal caso (como
ocurre en matemáticas), los fragmentos de la expresión encerrados entre paréntesis son evaluados
antes. Si no hubiese paréntesis, hay algunos fragmentos que son evaluados antes que otros,
dependiendo de la precedencia (importancia) de los operadores que se encuentran entre los operandos,
pero a igual nivel de precedencia, se mantiene la evaluación de izquierda a derecha (sobre precedencia
de operadores, consultar el apartado Precedencia de los operadores). En cualquier caso, se recomienda
efusivamente para aclarar el código y eliminar posibles errores, utilizar siempre paréntesis para
determinar qué fragmentos de la expresión se van a evaluar primero.
En el caso de la expresión de ejemplo cuyo resultado se asigna a la variable d, podemos interpretarla
de varias maneras, ya que de no saber en qué sentido se evalúan las expresiones y cual es la
precedencia de los operadores, el resultado es imprevisible. De todos modos, adelantamos que esta
expresión en Java equivale a lo que muestra el Código fuente 6.
46
© Grupo EIDOS
3. Sintaxis del lenguaje Java
d = (2 / 3) + (4 * 5)
Código fuente 6
Este ejemplo ilustra claramente la conveniencia de utilizar los paréntesis al declarar expresiones.
En Java, a diferencia de otros lenguajes de programación, al evaluar una expresión lógica, podemos
hacer que esta sea evaluada de forma perezosa o no perezosa, dependiendo del operador que
utilicemos.
Una expresión, se evalúa de forma perezosa cuando tras conseguir el resultado buscado, el evaluador
no continúa evaluando el resto de las expresiones por la derecha. Veamos un ejemplo.
Supongamos que las siguientes funciones devuelven los valores a continuación indicados:
•
Hora() Devuelve la hora actual en formato numérico (0-24 hrs)
•
DíasMes() Devuelve el día del mes en formato numérico (1-31)
•
Año() Devuelve el año en curso en formato numérico (0-...)
Siendo los operadores tal como se describen:
•
|| OR no exclusivo
•
&& AND
Entonces, la siguiente expresión lógica (que puede estar dentro de una instrucción de bifurcación
condicional if):
Hora() >= 12 || (DíaMes() > 15 && Año() == 1997)
Se evaluará como indica la Tabla 12.
Evaluación Perezosa
Hora()
DíaMes() Año()
Al ser true el resultado la función Hora(), la expresión es
verdadera, por lo que no es necesario invocar las funciones
DíaMes() ni Año().
14
10
10
Forma de evaluarse
10
Al ser false el resultado devuelto por Hora(), es necesario invocar
DíaMes(), pero como su resultado es también false, no es necesario
invocar Año(), ya que el resultado de la expresión es false.
25
Al ser false el resultado de Hora(), es necesario invocar DíaMes(),
y como su resultado es true, el resultado de la expresión depende
del valor devuelto por Año(), por lo que es necesario invocar esta
función, y en este caso, al ser éste true, el resultado de la expresión
es true.
1997
47
Programación en Java
10
25
© Grupo EIDOS
Este caso es idéntico al anterior, pero al ser false el resultado de
Año(), el resultado de la expresión es false.
1998
Tabla 12
Sin embargo, en ciertos casos deseamos que la expresión se evalúe completamente, normalmente
porque deseamos que todas las funciones que la componen sean invocadas y realicen su cometido.
Para estos casos, Java dispone de operadores que fuerzan al evaluador a recorrer toda la expresión.
Estos son: &, | y ^.
Puesto que es muy común que las expresiones contengan operadores, ahora vamos a entrar en más
detalle sobre los operadores que Java nos proporciona.
Clasificación de operadores
En este apartado veremos, organizados según el tipo, los distintos operadores que el lenguaje Java
incorpora. El programador de C y/o C++, comprobará que son prácticamente los mismos, aunque Java
permite mayor flexibilidad en algunos de ellos y añade algún otro.
Aritméticos
Operadores sobre enteros
Estos operadores pueden aplicarse a todos los tipos de datos enteros: byte, short, int, long. Se dividen
en:
•
Unarios: utilizan un solo operando para realizar la operación.
•
Binarios: utilizan dos operandos para realizar la operación.
•
Relacionales: se comparan dos operandos.
Operadores Aritméticos para Números Enteros
Op.
Descripción
++
Incremento. Añade una unidad al valor actual de
la variable. (1)
--
Decremento. Sustrae una unidad al valor actual
de la variable. (1)
-
Negación. Equivale a multiplicar por –1 el valor
de la variable.
~
Complemento a nivel de bits. (2)
Unarios
48
© Grupo EIDOS
Binarios
Relacionales
3. Sintaxis del lenguaje Java
+
Adición
-
Sustracción
*
Multiplicación
/
División
%
Módulo
<<
Desplazamiento a la izquierda. (3)
>>
Desplazamiento a la derecha. (3)
>>>
Desplazamiento a la derecha con relleno de
ceros. (3)
&
AND a nivel de bits.
|
OR a nivel de bits.
^
XOR a nivel de bits.
<
Menor que
>
Mayor que
<=
Menor o igual que
>=
Mayor o igual que
==
Igual que
!=
Distinto que
Tabla 13
Tanto los operadores unarios como los binarios, independientemente del tipo de los operandos sobre
los que se realice la operación, devuelven un int para todos los casos, excepto que uno de los
operandos sea un long, en cuyo caso el valor devuelto es de tipo long.
(1) Estos operadores pueden utilizarse en forma de prefijo (++variable) o forma de sufijo (varible++).
En el primer caso el incremento (o decremento) de la variable se realiza antes de evaluar la expresión,
y en segundo se realiza tras evaluar la expresión.
(2) Este operador conmuta el número a nivel de bits, es decir, todos los bits que estaban a 0 pasan a ser
1, y todos los bits que estaban a 1 pasan a 0.
(3) Estos operadores desplazan los bits a la derecha o izquierda el número de posiciones especificado
como segundo operando.
49
Programación en Java
© Grupo EIDOS
Operadores sobre reales
Estos operadores trabajan sobre número de coma flotante, es decir, los tipos: float y double. Lo dicho
anteriormente acerca de los operadores sobre enteros es aplicable a los operadores sobre números
reales salvo que el resultado de la expresión será de tipo float si ambos operandos son de este tipo y en
caso contrario el tipo devuelto será double.
Operadores Aritméticos para Números Reales
Op.
Descripción
++
Incremento. Añade una unidad al valor actual de la
variable.
--
Decremento. Sustrae una unidad al valor actual de la
variable.
+
Adición
-
Sustracción
*
Multiplicación
/
División
%
Módulo
<
Menor que
>
Mayor que
<=
Menor o igual que
>=
Mayor o igual que
==
Igual que
!=
Distinto que
Unarios
Binarios
Relacionales
Tabla 14
Booleanos
Operadores Booleanos
50
Operador
Descripción
&
AND (no perezoso)
|
OR (no perezoso)
© Grupo EIDOS
3. Sintaxis del lenguaje Java
^
XOR (no perezoso)
&&
AND (perezoso)
||
OR (perezoso)
!
NOT (negación)
==
Igualdad
!=
Distinto a
?:
Condicional (op. ternario)
Tabla 15
Son aquellos que efectúan operaciones sobre datos de tipo booleano, y como es lógico, el tipo de dato
que devuelven es booleano.
Relacionales
<
Menor que
>
Mayor que
<=
Menor o igual que
>=
Mayor o igual que
==
Igual que
!=
Distinto que
Relacionales
Tabla 16
Cadena
En Java existe un solo operador que utilice cadenas como operandos, este es el operador binario +,
este operador concatena cadenas. En el Código fuente 7 vemos un ejemplo.
String
str1 =
str2 =
str3 =
str1, str2, str3;
"En lugar de la Mancha";
"de cuyo nombre no quiero acordarme";
str1 + " " + str2;
Código fuente 7
Tras aplicar el operador de concatenación de cadenas, el valor la variable str3 es:
51
Programación en Java
© Grupo EIDOS
"En lugar de la Mancha de cuyo nombre no quiero acordarme"
Asignación
Estos operadores, salvo el operador de asignación simple (=) se utilizan para escribir menos, y por lo
tanto, su forma simplificada es exactamente equivalente a su forma extendida.
Operadores de Asignación
Op.
Descripción
Equivalencia
=
Simple
+=
Adición
var1 = var1 + var2 Æ var1 += var2
–=
Sustracción
var1 = var1 – var2 Æ var1 –= var2
*=
Multiplicación var1 = var1 * var2 Æ var1 *= var2
/=
División
var1 = var1 \ var2 Æ var1 \= var2
%=
Módulo
var1 = var1 % var2 Æ var1 %= var2
&=
AND
var1 = var1 & var2 Æ var1 &= var2
|=
OR
var1 = var1 | var2 Æ var1 |= var2
^=
XOR
var1 = var1 ^ var2 Æ var1 ^= var2
Tabla 17
Precedencia de los operadores
A continuación se presenta todos los operadores que hemos visto, según su orden de precedencia de
mayor a menor, es decir, según su importancia a la hora de ser evaluados.
Esto quiere decir, que salvo que se agrupen expresiones mediante el uso de paréntesis, aquellos
operadores que tengan mayor orden de precedencia, serán evaluados primero.
52
•
[] ()
•
++ --
•
! ~ instanceof
•
/%
•
+-
•
<< >> >>>
© Grupo EIDOS
•
< > <= >= = = !=
•
&^|
•
&& ||
•
?:
•
= += –= *= /= %= &= |= ^=
3. Sintaxis del lenguaje Java
Control de flujo
Las sentencias de control de flujo nos permiten que ciertas partes de un programa no se ejecuten
(condicionales) o que se ejecuten más de una vez (bucles).
Sin ellas, los programas Java se ejecutarían de arriba abajo, se procesarían todas sus líneas y una sola
vez cada una de ellas.
Las condiciones permiten bifurcar el flujo de la aplicación y en Java se dispone de las sentencias ifelse y switch, los bucles permiten repetir un fragmento de código un número determinado de veces, y
las sentencias disponibles en Java para realiza bucles son: for, while y do-while.
Todas estas sentencias funcionan exactamente igual que sus homónimas de C, por lo que si el alumno
las conoce, puede saltárselas tranquilamente.
Java dispone además de otras dos sentencias que permiten hacer saltos: break y continue.
If-else
La sintaxis de esta estructura es la siguiente:
if (<ExprBooleana>)
sentencia1;
[else [if (<ExprBooleana>) ]
sentencia2;]
Aunque también pueden utilizarse bloques en lugar de sentencias, del siguiente modo:
if (<ExprBooleana>) {
sentencias;
}
[else [if (<ExprBooleana>) ] {
sentencias;
}]
<ExprBooleana> puede ser cualquier expresión que, al ser evaluada, devuelva un resultado de tipo
Booleano.
Como esta estructura es la más común a través de todos los lenguajes, asumimos que es conocida de
sobra, y no entraremos en los detalles de su funcionamiento.
53
Programación en Java
© Grupo EIDOS
Switch
La sintaxis de esta estructura es la siguiente:
switch( <Expresión> ) {
case <Constante1>:
sentencias;
[break;]
[case <Constante2>:
sentencias;
[break;]
........................
]
[default:
sentencias;
[break;]]
}
En esta sentencia, el resultado de la evaluación de <Expresión> es comparado con cada una de las
constantes (<Constante1>, <Constante2>,.....) hasta que se encuentra una que coincida, en ese
momento se ejecutan todas las sentencias que se encuentran dentro de ese case, hasta encontrar un
break, por lo que si no hay un break al final del case, los cases siguientes también se ejecutarán.
<Expresión> debe pertenecer a alguno de los tipos primitivos de Java, no puede ser una clase.
Si no se encontrara coincidencia alguna, y existiera la cláusula default, estas sentencias serán
ejecutadas hasta encontrar un break. De todos modos, como esta cláusula, caso de existir, siempre se
pone la última, no es necesario incluir el break.
Esta rama puede simularse mediante el uso de la rama if-else, pero switch ha sido diseñada
específicamente para escoger un resultado de entre un grupo de resultados posibles, y es de suponer
que el código que se genera es más eficiente que el que se generaría si usáramos para el mismo
propósito una estructura if-else.
Veamos un ejemplo simple de switch, en el Código fuente 8.
switch( iDíaSemana ) {
case 1:
sDia = "lunes";
break;
case 2:
sDia = "martes";
break;
case 3:
sDia = "miércoles";
break;
case 4:
sDia = "jueves";
break;
case 5:
sDia = "viernes";
break;
case 6:
sDia = "sábado";
break;
case 7:
sDia = "domingo";
break;
54
© Grupo EIDOS
3. Sintaxis del lenguaje Java
default:
sDia = "No se qué día es";
}
Código fuente 8
En el Código fuente 9 vemos otro ejemplo en el que no utilizamos una sentencia break tras cada case.
int iMes;
int iDías;
long nAño;
. . .
switch (iMes) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
iDías = 31;
break;
case 4:
case 6:
case 9:
case 11:
iDías = 30;
break;
case 2:
if ( ((lAño % 4 == 0) && !(lAño % 100 == 0)) || (lAño % 400 == 0) )
iDías = 29;
else
iDías = 28;
break;
default:
iDias = 0;
}
Código fuente 9
For
La sintaxis del bucle for es la siguiente:
for ( [<ExprInicializa>]; [<ExprCondición>]; [<ExprIteración>])
sentencia;
O bien:
for ( <ExprInicializa>; <ExprCondición>; <ExprIteración>){
sentencias;
}
Aunque estas tres expresiones pueden utilizarse para el propósito que se desee, normalmente se
utilizan para los siguientes fines:
•
ExprInicializa: en esta expresión se inicializa la variable(s) que va a controlar el bucle.
55
Programación en Java
© Grupo EIDOS
•
ExprCondición: esta expresión controla cuando finalizan las iteraciones del bucle.
•
ExprIteración: esta expresión indica cómo cambia la variable de control del bucle.
Se debe señalar que las expresiones son todas optativas, y aunque se pueda escribir un bucle sin
ninguna de ellas, no tiene mucho sentido hacerlo. Si así se hiciera, se tendría que mantener los puntos
y comas que separan las tres expresiones.
Cada una de estas expresiones que se definen en la cabecera del bucle pueden ser simples o
expresiones compuestas separadas por comas. Veamos un ejemplo de cada una de ellas:
Vemos, en el Código fuente 10, expresiones simples.
for (x = 1; x <= 10; x++)
System.out.println( x );
Código fuente 10
La cabecera de este bucle for puede leerse del siguiente modo: "Para x igual a uno, y mientras que x
sea menor o igual que diez, incrementa x en una unidad".
El Código fuente 11, nos muestra expresiones compuestas.
for (x = 1, t = 30; x <= 10 && t-=x != 20; x++, t -= 2 ){
............................
}
Código fuente 11
La cabecera de este bucle for puede leerse del siguiente modo: "Para x igual a uno, y t igual a treinta,
mientras que x sea menor o igual que diez y mientras x menos t sea distinto de 20, incrementa x en una
unidad y disminuye t en dos unidades".
Debido a la potencia y la versatilidad que esta forma de concebir los bucles for ofrece, a veces el
programador siente el deseo de agrupar varias expresiones y parte de lo que serían las sentencias del
cuerpo del bucle en la cabecera del mismo, haciéndola compleja y de difícil lectura. Recomendamos
huir de este estilo de programación por lo complicado que puede ser posteriormente entender un
código escrito de este modo. Resulta más apropiado escribir un poco más de código si con ello
conseguimos claridad, y dejar que el compilador haga las optimizaciones que considere oportunas, en
lugar de enredarlo todo en un esfuerzo de incrementar la velocidad de nuestra aplicación o de
demostrar a los demás o a nosotros mismos cuan inteligentes somos.
While
Las principales diferencias entre el bucle for y el bucle while son que en éste no se inicializan los
valores de control ni se dispone de la expresión de iteración.
La sintaxis de este bucle es la siguiente:
56
© Grupo EIDOS
3. Sintaxis del lenguaje Java
while (CondBooleana)
sentencia;
O bien:
while (CondBooleana){
sentencias;
}
La sentencia o sentencias definidas en el cuerpo de un bucle while se repiten mientras que la condición
de su cabecera sea verdadera. Es por esto que esta condición tiene que ser una expresión cuyo
resultado sea de tipo booleano.
Debido a que este tipo de bucles se encuentra en todos los lenguajes, no vamos a entrar en más detalles
acerca de su funcionamiento.
Do-while:
La sintaxis de este bucle es la siguiente:
do
sentencia;
while (CondBooleana);
O bien
do {
sentencias;
}
while (CondBooleana);
Como puede verse, este bucle es casi idéntico al bucle while, salvo que en este caso, la expresión
booleana se evalúa tras haberse realizado la primera iteración del bucle. Debido al parecido de este con
el anterior, y por las mismas razones ya expuestas al hablar del bucle while, no vamos a entrar en más
detalles.
Break, continue y etiquetas
Java dispone de las sentencias break y continue y las etiquetas para realizar saltos. El caso de la
sentencia return, es especial y lo trataremos al final de este apartado.
Ya hemos visto el uso de la sentencia break cuando tratamos la sentencia switch. Sin embargo, break
puede utilizarse en cualquier sitio de la aplicación, aunque sólo tiene sentido dentro de la sentencia
switch o dentro de los bucles.
break, hace que el flujo de la aplicación salte al estamento siguiente al que se está ejecutando en el
momento de ser encontrado.
Veamos un ejemplo de uso de esta sentencia dentro de un bucle, en el Código fuente 12.
for( i = 1; i <= 10; i++ ) {
57
Programación en Java
© Grupo EIDOS
if (i == 5 ) break;
System.out.println( i );
}
Código fuente 12
Este bucle pinta solamente los números del 1 al 4. Ya que break se ejecuta cuando i == 5, lo que hace
que el flujo de la aplicación pase control instrucción que se encuentra inmediatamente tras el bucle.
La sentencia continue es similar a break, pero en lugar de transferir el flujo de la aplicación fuera del
bucle, continue lo lleva a la cabecera del bucle. Por lo tanto, y como es lógico, esta instrucción sólo
puede utilizarse dentro de bucles.
En el Código fuente 13 podemos ver un ejemplo del uso de esta sentencia.
for( i = 1; i <= 10; i++ ) {
if (i == 5 ) continue;
System.out.println( i );
}
Código fuente 13
Este bucle muestra todos los números del 1 al 10 excepto el número 5. Ya que continue se ejecuta
cuando i == 5, lo que hace que se salten todas las sentencias por debajo de continue y que el flujo de la
aplicación vuelva a la cabecera del bucle.
Aunque goto es una palabra reservada, en Java los saltos no están permitidos. Esto como ya hemos
comentado en alguna ocasión, es debido a que los arquitectos de este lenguaje han intentado eliminar
todos aquellos recursos que puedan dar lugar a un mal estilo de programación. Y está aceptado por
toda la comunidad de teóricos de la programación que los saltos a una etiqueta son totalmente
desaconsejables.
Sin embargo, Java permite asociar una etiqueta cuando se va a realizar un salto, para de este modo
clarificar el código. La etiqueta se asocia con las instrucciones break y continue, que son las únicas
sentencias de salto que Java contempla. Se dice en estos casos que se ha hecho un break etiquetado o
un continue etiquetado.
En cualquier caso esta técnica es sólo aconsejable cuando se está trabajando con bucles anidados y se
quiere salir de ellos a distintos niveles. No ayudando a la legibilidad del código en ningún otro
supuesto teórico.
Para realizar un salto a una etiqueta, primero debemos definir la etiqueta, lo que se hace escribiendo
un literal que va a ser la etiqueta seguida del operador dos puntos (:) antes de una sentencia de Java. Y
desde cualquier punto se puede hacer un break con el formato:
break <etiqueta>
Veamos un ejemplo (en el Código fuente 14) del uso de las etiquetas en el entorno para el que fueron
pensadas.
uno: for( ){
58
© Grupo EIDOS
3. Sintaxis del lenguaje Java
dos: for( ){
continue; // seguiría en el bucle interno
break; // se saldría del bucle interno
continue uno; // seguiría en el bucle principal
break uno; // se saldría del bucle principal
}
}
Código fuente 14
Return
Esta instrucción es un caso especial, ya que aunque permite realizar un salto, no es una instrucción de
salto propiamente dicha.
Esta sentencia tiene la siguiente sintaxis:
return <valor>;
Cuando return es encontrado en el programa, el flujo salta fuera del método donde fue encontrado, no
ejecutándose ninguna otra instrucción por debajo de esta y devuelve el control al método que lo
invocó. Si el método donde se encuentra return es el principal, la aplicación termina.
Esta sentencia permite devolver un valor al método que llama a otro tras la ejecución de un proceso
determinado. Lo que en Java es muy importante, porque los métodos, si se especifica que tienen que
devolver un dato de un tipo determinado (cualquiera excepto void), tienen que contener una
instrucción return que devuelva un valor coincidente con la declaración del tipo del método. En otro
caso un mensaje de advertencia será reportado por el compilador. Un programa Java no puede
compilarse si este contiene errores o advertencias.
Try, catch, finally y throws:
Estas sentencias de control de flujo son: try-catch-finally y throws. Permiten el manejo de excepciones
en Java.
Cuando un error ocurre dentro de un método, el método puede lanzar (throws) una excepción para
indicar a quien lo invocó que un error ha ocurrido y cual ha sido el error. El método que ha llamado al
que ha lanzado la excepción, puede entonces utilizar try, catch y finally para capturar y manejar la
excepción.
Del uso estas sentencias hablaremos en profundidad en el capítulo correspondiente.
59
POO en Java: objetos
Introducción
A lo largo de este capítulo, y en los dos que le siguen, vamos a abordar tanto la sintaxis de la POO en
Java como otros elementos del lenguaje que se encuentran íntimamente relacionados con las clases. En
el capítulo anterior vimos la sintaxis general del lenguaje, y los capítulos siguientes veremos como
utilizando esa sintaxis se implementa toda la teoría de la Programación Orientada a Objetos vista en el
primer capítulo de este curso.
Se debe señalar, como ya hemos comentado con anterioridad, que las piezas clave del lenguaje Java,
como lenguaje de POO que es, son las clases y los objetos. Java se apoya sobre una completa y
compleja jerarquía de clases predefinidas del lenguaje, pero en esta serie de capítulos, referenciados
como POO en Java, además de comentar la estructura de la jerarquía de clases existente, mostraremos
como crear nuestras propias clases, para poder reutilizarlas en nuestros programas como si formaran
parte de la jerarquía de clases estándar presentes en el lenguaje Java.
A continuación vamos a ir viendo como utilizar objetos y clases dentro del lenguaje Java y ya
comenzaremos a aprender a escribir nuestro código Java.
Objetos
Como ya hemos comentado en varias ocasiones, los objetos son las piezas fundamentales de cualquier
lenguaje de programación que sea orientado a objetos, por lo tanto vamos a tratar en un primer lugar
los objetos en Java. Lo haremos a lo largo de varios apartados, para pasar después a explicar como
crear nuestros propios tipos de objetos, es decir, como implementar nuestras propias clases.
Programación en Java
© Grupo EIDOS
En un programa Java vamos a estar creando objetos de distintos tipos de clases, estos objetos
interactúan entre sí a través del mecanismo de mensajes, es decir, invocando métodos.
Una vez creado el objeto realizará la función que se le haya encomendado: ejecutar una animación,
formar parte de un interfaz de usuario, enviar o recibir información a través de la red, etc. Cuando el
objeto ha terminado su trabajo para el cual fue creado es destruido por el recolector de basura para
liberar recursos que podrán ser reutilizados por otros objetos.
A la vista de lo anterior podemos decir que un objeto tiene un ciclo de vida, y que en este ciclo de vida
podemos distinguir tres fases o etapas:
1. Creación.
2. Utilización.
3. Destrucción.
En los siguientes apartados vamos a tratar cada una de las etapas del ciclo de vida de un objeto y como
las implementamos desde el lenguaje Java.
Creación de objetos
En este apartado se va a tratar la primera fase del ciclo de vida de un objeto.
El proceso de crear un objeto a partir de una clase se divide en tres pasos: declaración, instanciación e
inicialización. Vamos a ver cada uno de ellos.
Declaración
La sintaxis para declarar un objeto en Java es la siguiente:
<tipo> <nombre>;
Donde:
<tipo> es el nombre de clase, es decir, el tipo de dato que se va a crear.
<nombre> es el nombre de la variable con el que nos referiremos al objeto.
Por ejemplo, vemos el Código fuente 15.
String sNombre;
Date dHoy;
Código fuente 15
Como se puede observar, no hay ninguna diferencia entre estas declaraciones y las declaraciones de
los tipos primitivos, es decir las que muestra el Código fuente 16.
62
© Grupo EIDOS
4. POO en Java: objetos
int iEdad;
char cInicial;
Código fuente 16
Y esto, en un lenguaje de POO, es lo lógico. Como ya comentamos al hablar de tipos y conversión de
datos (en el capítulo anterior), cuando creamos una nueva clase, estamos sencillamente creando un
nuevo tipo de datos.
Hasta el momento, no hay ningún objeto por sitio alguno, de hecho, las variables sNombre y dHoy,
todavía no contienen objetos, es decir, no se ha creado todavía ningún objeto ni de clase String ni de
clase Date, ¿entonces qué hemos hecho?. Pues simplemente le hemos indicado al compilador que la
variable sNombre es de tipo String, es decir que en algún punto del programa va a contener un objeto
de la clase String, y que la variable dHoy es de tipo Date, es decir que en algún punto del programa va
a contener un objeto de la clase Date.
De este modo, si más adelante intentamos instanciar un objeto de una clase distinta a la declarada, el
compilador detectará el error y lo reportará, impidiendo que finalice este proceso.
Terminado con el primer paso vamos con el segundo dentro del proceso de creación de objetos.
Instanciación
Para instanciar una clase, en Java se utiliza el operador new. Si el alumno es programador de C++, el
operador new le resultará familiar. Pero debe tener cuidado, porque en Java no funciona del modo en
como lo hace en C++. Mientras que en C++ new se utiliza para crear un objeto dinámicamente en
memoria, y devuelve un puntero al objeto creado, en Java este operador devuelve una referencia al
propio objeto (se debe recordar que en Java no existen los punteros).
El operador new invoca al constructor (de ellos hablaremos la fase de inicialización) de la clase y le
pasa los parámetros de inicialización si los hubiere. El operador new crea el objeto y el constructor lo
inicializa.
La sintaxis del operador new es la siguiente:
[<Variable> =] new <Constructor>( [<par1>, <par2>, ..., <parN>] )
De este modo, para crear un objeto de la clase String, haríamos lo que muestra el Código fuente 17.
sNombre = new String();
Código fuente 17
Y para crear uno de la clase Date, lo que nos muestra el Código fuente 18.
dHoy = new Date( );
Código fuente 18
63
Programación en Java
© Grupo EIDOS
Evidentemente, si a la izquierda de la expresión no hubiéramos puesto una variable, la referencia
devuelta por el operador new se habría perdido; del mismo modo, si el tipo de variable no coincidiera
con el tipo devuelto por el operador, el compilador reportaría un error indicando que los tipos no
concuerdan.
Inicialización
Ya tenemos un objeto de la clase String y otro de la clase Date creados, pero ¿Qué contienen estos
objetos? dicho de otro modo ¿Cuál es su valor?.
En el caso de sNombre, la cadena es una cadena vacía y en el caso de dHoy, la hora a la cual fue el
objeto creado. Sin embargo, en casi todas las ocasiones, cuando al crear un objeto, solemos querer
darle un valor. Esto es lo que se llama inicialización del objeto. Este proceso lo realiza el constructor
de la clase.
Un constructor no es más que un método con el mismo nombre que la clase y que puede o no recibir
una serie de parámetros que se utilizan para inicializar el objeto. Gracias a la sobrecarga de métodos
(concepto que debe conocer el alumno del primer capítulo) es posible, y muy útil, disponer de varios
constructores. Existe un constructor especial, llamado constructor por defecto, que es aquel que no
recibe ningún argumento o parámetro.
En el Código fuente 19 aparecen algunas inicializaciones posibles para los objetos anteriormente
creados.
sNombre = new String( "Albert Einstein" );
sNombre = new String( "1234" );
dHoy = new Date( 2000, 6, 1 );
dHoy = new Date( 2001, 1, 1 );
Código fuente 19
Como hemos comentado, para poder inicializar los objetos de distintos modos, las clases suelen
proporcionar varios constructores. Así, en Java, la clase String, dispone de siete constructores
distintos, y la clase Date de seis.
La declaración y la instanciación también las podemos realizar en una única línea de código, como se
puede observar en el Código fuente 20.
Date dHoy = new Date( 2000, 1, 1 );
String sNombre = new String( "Albert Einstein" );
Código fuente 20
Utilización de objetos
Una vez creado el objeto debemos ver los mecanismos que existen para poder utilizarlo. En este
apartado se va a explicar cómo se acceden a los datos y métodos de un objeto, que por otra parte, es el
modo estándar en la POO. El operador de envío de mensaje en Java, es el punto (.), es decir, es el
64
© Grupo EIDOS
4. POO en Java: objetos
operador para lanzar o invocar un método de un objeto. Siendo la sintaxis genérica de envío de
mensaje la siguiente:
objeto.mensaje
Y para acceder a los atributos de un objeto (o variables miembro) es:
objeto.atributo
Y para los métodos (o funciones miembro) es:
objeto.método( [parámetros] )
Veamos algunos ejemplos en el Código fuente 21. Para ello, vamos a construir un nuevo objeto,
utilizando la clase Rectangle contenida en el paquete java.awt de la librería de clases de Java, más
delante en siguientes capítulos comentaremos con detenimiento en que consisten los paquetes y los
principales que ofrece Java, de momento se debe indicar que un paquete es una forma de organizar
clases, al igual que organizamos ficheros en un directorio en nuestro disco duro.
// Instanciación del objeto rctCaja con unas dimensiones:
//x, y, alto, ancho
Rectangle rctCaja = new Rectangle( 100, 100, 200, 200 );
System.out.println( "x = " + rctCaja.x ); // Consultar x
System.out.println( "y = " + rctCaja.x ); // Consultar y
System.out.println( "Alto = " + rctCaja.height ); // Consultar Alto
System.out.println( "Ancho = " + rctCaja.width); // Consular Ancho
Código fuente 21
En el Código fuente 21 se utiliza el siguiente código System.out.println() que puede despistar a
algunos lectores, de momento únicamente comentaremos que de esta forma conseguimos imprimir
algo por pantalla, en el capítulo en el que se comentan las aplicaciones Java, entraremos en más
detalles.
Para cambiar un valor simplemente haríamos lo que muestra Código fuente 22.
rctCaja.x = 150;
Código fuente 22
O para realizar un cálculo (Código fuente 23).
area = rctCaja.height * rctCaja.width;
Código fuente 23
Para acceder a un método, empleamos el mismo operador, como se puede ver en el Código fuente 24.
65
Programación en Java
© Grupo EIDOS
rctCaja.resize( 150, 250 );
Código fuente 24
Si el método devolviese un valor podríamos almacenarlo en una variable como muestra el Código
fuente 25.
String sCaja;
Caja = rctCaja.toString();//Representación del rectángulo como cadena
Código fuente 25
Destrucción de objetos
Al igual que existe un método constructor para crear objetos existe un método destructor para
liberarlos. Del mismo modo que el constructor es llamado por el operador new cuando el objeto es
creado, el destructor, si existe, es invocado también. El nombre del método destructor debe ser
finalize().
Del hecho de que el destructor es llamado cuando el objeto va a ser eliminado de memoria, se deduce
que el destructor no es llamado cuando el objeto sale fuera de ámbito, sino cuando va a ser destruido
por el recolector de basura (garbage collector).
Como ya hemos comentado, cuando una variable sale de ámbito, y un objeto siempre está referenciado
por una variable, ésta es marcada para ser eliminada de memoria, y el recolector la eliminará cuando
pueda hacerlo, ya que es una tarea que se desarrolla en background (en un segundo plano), y de forma
incremental.
Esto implica que no podemos saber cuando el método de finalización de un objeto va a ser invocado,
lo que es parcialmente cierto, aunque existe la posibilidad de forzar que ocurra el proceso de
finalización llamando al método runFinalization() de la clase System (de momento simplemente
diremos que esta clase representa al sistema en ejecución) lo que se hace como muestra el Código
fuente 26.
System.runFinalization();
Código fuente 26
Este método llama a todos los métodos finalize() de todos los objetos que están esperando al recolector
de basura. Pero no existe un modo automático para asegurarse de que el destructor va a ser llamado al
salir el objeto de ámbito.
En cualquier caso, y por el tipo de tareas que los destructores realizan, esto no tiene la menor
importancia, ya que los destructores suelen encargarse de liberar los recursos capturados por el objeto
(lo que normalmente se hace en el constructor), cerrar ficheros abiertos, cerrar sockets utilizados,
liberar memoria, etc.
66
© Grupo EIDOS
4. POO en Java: objetos
Por otro lado, una clase no tiene por qué tener un destructor, pero si lo tiene, éste tiene que reunir las
siguientes características:
•
El nombre del método tiene que ser finalize(). Ya que el destructor es invocado por el
recolector de basura, éste no puede saber cómo se llama el método de la clase a menos que se
establezca una forma de hacerlo, y lo que se ha hecho es darle un nombre fijo. El que se ha
escogido en Java es finalize(), aunque podrían haber escogido otro cualquiera. También se
podría haber hecho como en C++, que el destructor tiene el mismo nombre de la clase
precedido por la virgulilla ("~").
•
No puede recibir ningún parámetro. Puesto que el recolector de basura no puede saber qué
parámetros desea recibir el destructor, y puesto que cuando el destructor va a ser invocado es
posible que algunos o todos los parámetros que desea recibir no estén en disponibles.
•
No puede haber más que un destructor. Esto es conclusión directa de lo anterior, ya que al no
tener parámetros, no puede existir la sobrecarga.
•
No puede devolver valor alguno. Es decir, el método tiene que ser de tipo void. Por el mismo
motivo que el recolector de basura es quien invoca al destructor, éste no puede devolver valor
alguno, ya que aun cuando devolviese un valor este no podría ser recogido por proceso alguno
porque el objeto ya ha salido de ámbito.
Para ser correctos, hay que decir que el destructor siempre existe, ya que la clase Object, de la que
heredan todas las otras clases en Java, dispone del método finalize(), éste es un método que no hace
nada, por lo que si creamos una clase que hereda directamente de la clase Object no es necesario
llamar al destructor de la clase padre.
Sin embargo, en otras ocasiones, al heredar de otras clases en las que existe un método finalize() que
desempeña una tarea necesaria para la correcta finalización del objeto, es necesario que tras realizar la
labor oportuna en el método finalize() de su clase, llame al método finalize() de la clase padre para que
éste pueda realizar su labor.
La sintaxis de un destructor es como sigue:
void finalize() {
// Tarea de finalización
}
Se puede observar que como los destructores no pueden devolver ningún valor, el tipo que devuelve
este método es void, de todas formas en el apartado dedicado a los métodos en el próximo capítulo lo
veremos en mayor detalle.
Una vez comentado como podemos tratar los objetos en Java vamos a pasar a comentar en el siguiente
capítulo la segunda pieza clave de la POO, las clases.
67
POO en Java: clases
Clases
Hasta ahora hemos creado objetos a partir de clases existentes, pero como es lógico, se pueden crear
nuevas clases e instanciar objetos de las clases definidas por nosotros mismos.
Una clase de forma genérica se compone de: la declaración de la clase y del cuerpo de la clase, el cual,
a su vez se divide en la sección de declaración (opcionalmente también la inicialización) de los
atributos (variables miembro) y la sección de declaración e implementación de los métodos (funciones
miembro).
De este modo, la plantilla de una clase quedaría de la siguiente manera:
declaraciónDeLaClase {
declaraciónDeLosAtributos
declaraciónDeLosMétodos
}
Siendo el cuerpo de la clase la amplia sección encerrada entre las dos llaves.
Una clase puede o no contener atributos y/o métodos, pero al menos tiene que contener uno de los dos
componentes.
En los siguientes apartados, a lo largo del presente capítulo, iremos viendo cada una de las secciones
que compone la construcción de una clase en Java.
Programación en Java
© Grupo EIDOS
Declaración de la clase
La declaración de una clase implica indicar el nombre que va a tener la clase junto con otras
características que puede presentar la clase. La sintaxis de la declaración de la clase es la siguiente:
class <NombreClase> {
..........
}
Como se puede ver, la declaración de una clase se compone, al menos, de la palabra clave class y del
nombre de la clase. El nombre de la clase tiene que ser un identificador (véase el apartado
Identificadores del primer capítulo) válido en Java.
Aunque esta mínima declaración suele ser suficiente en algunos casos, es posible indicar, al declarar
una clase, alguna otra información adicional. La sintaxis de una declaración completa de la clase sería
la siguiente:
[<Modificador(es)>] class <NombreClase> [extends <NombreSuperClase>]
[implements <NombreInterface1> [, <NombreInterface2>, ... ]] {
........................
}
A continuación vamos a ir comentando cada una de las características adicionales que podemos
indicar a la hora de declarar una clase y que aparece en la sintaxis mostrada arriba.
Superclase de la clase
En Java, para hacer que una clase herede de otra (véase para más detalles el apartado Herencia de este
mismo capítulo), se utiliza la palabra reservada extends antes del nombre de la superclase de la cual
deriva. La sintaxis es como sigue:
class <NombreClase> extends <NombreSuperClase>{
..........................
}
En Java, todas las clases heredan directa o indirectamente de la clase Object (para más información
sobre la clase Object acudir al apartado del mismo nombre dentro del siguiente capítulo), ya que esta
clase se encuentra en la raíz de la jerarquía de clases. Si no se especifica que una clase hereda de otra,
Java asume que hereda directamente de la clase Object.
Únicamente podremos heredar de una clase, ya que Java no implementa el mecanismo de herencia
múltiple.
Interfaces de la clase
Si la clase implementara algún interfaz (véase el apartado Interfaces del capítulo siguiente), este o
estos, se especifican en la declaración de la clase.
En pocas palabras, un interfaz es un conjunto de constantes y métodos sin implementar, que la clase
que lo utiliza, tiene que implementar (codificar si se prefiere). De todas formas trataremos los
interfaces en más detalle más adelante en el siguiente capítulo.
70
© Grupo EIDOS
5. POO en Java: clases
La sintaxis para declarar una clase que utiliza interfaces es la siguiente:
class <NombreClase> implements <NombreInterface1>
[,<NombreInterface2>, ... ]{
..........................
}
Por convención, la cláusula implements se especifica después de la cláusula extends, si la hubiere.
Como se puede observar se pueden implementar uno o más interfaces.
Modificadores de la clase
Existen varios modificadores que son aplicables a la clase, en caso de tener que especificar alguno,
este se sitúa al principio de la declaración, según la siguiente sintaxis:
<Modificador> class <NombreClase> {
........................
}
Los modificadores nos dan información adicional de la forma en la que la clase va a ser tratada.
Los modificadores posibles que se pueden anteponer a la clase son los siguientes:
•
public: este es un modificador de acceso que indica que la clase es accesible de forma pública,
es decir, a la clase se puede acceder y ser instanciada desde cualquier otra clase. Si no
indicamos este modificador la clase sólo podrá ser accesible para clases dentro del mismo
paquete (para información sobre los paquetes en Java acudir al apartado Paquetes en el
capítulo siguiente).
•
abstract: este modificador indica que la clase es abstracta y por lo tanto no es instanciable, es
decir, no vamos a poder crear objetos de esta clase. Las clases abstractas las utilizaremos para
representar una clase que se encuentra en la raíz de una jerarquía de clases y recoge algunas
características que son comunes a todas las clases hijas, pero que difieren en su
implementación, para más información sobre clases abstractas consultar el apartado que posee
el mismo nombre.
•
final: si aplicamos este modificador a una clase indicaremos que la clase no puede ser
subclasificable, es decir, ninguna clase podrán heredar de ella y por lo tanto nunca podrá ser
una clase padre. Una clase la declararemos como final por cuestiones de seguridad, para
asegurarnos que ninguna clase hereda de ella para modificar comportamientos, y también por
cuestiones de diseño de nuestra jerarquía de clases, para limitar en que momento se debe
detener el mecanismo de herencia.
Una vez comentados todos los elementos que forman parte de la declaración de una clase, se puede ver
que los únicos elementos necesarios para declarar una clase son la palabra del lenguaje class y el
nombre de la clase.
Si no se especifican los elementos opcionales, los valores por defecto que el compilador asumen son
los siguientes: no-final, no-public, no-abstract, subclase de Object y no implementa interfaz alguno.
El cuerpo de la clase se compone de atributos y métodos, analizaremos cada uno de estos componentes
en los siguientes apartados.
71
Programación en Java
© Grupo EIDOS
Cuerpo de la clase. Declaración de atributos
En este apartado abordaremos el primero de los elementos que podemos definir dentro del cuerpo de
una clase, es decir, los atributos de la clase.
La sintaxis general para declaración de los atributos de la clase es la siguiente:
<Modificador (es)> <tipo> <nombreVariable>
Veamos cada uno de los elementos de la declaración:
Modificadores
Dentro de los modificadores que le podemos aplicar a un atributo se puede distinguir un grupo de ellos
que se denomina modificadores de acceso. Estos modificadores de acceso son excluyentes entre sí e
indican la forma de acceso a los atributos de una clase desde otras clases. Los modificadores de acceso
posibles son los siguientes:
•
public: con este modificador de acceso indicamos que todas las clases tienen acceso a este
atributo. Se suelen declarar como públicos los atributos cuyo acceso a los mismos no puede
producir resultados no deseables. Aunque los atributos de una clase pueden ser public, esto
está totalmente desaconsejado en la POO, los atributos deben ser accedidos solamente desde
dentro de la clase y para manejarlos de la clase debe proporcionar un conjunto de métodos,
denominados métodos de acceso.
•
private: este modificador de acceso es todo lo contrario a public. Un atributo declarado como
private sólo es accesible desde dentro de la clase en la que está declarado. Es el nivel de
acceso más restrictivo de todos.
•
Modificador por defecto o package: es el modificador de acceso predeterminado, es decir, si
no indicamos un modificador de acceso para un atributo, se aplicará por defecto este tipo de
acceso. Este tipo de atributos es accesible desde las clases que se encuentren dentro del mismo
paquete (ver apartado Paquetes en el siguiente capítulo) en el que se encuentra la clase en la
que se declara el atributo. Ya que este tipo de acceso es el predeterminado no es necesaria una
palabra reservada.
•
protected: los atributos con este modificador son accesibles desde las clases hijas de la clase
en la que se encuentran declarados los atributos, y también es accesible desde las clases que se
encuentren dentro del mismo paquete.
En Tabla 18 se muestra los diferentes niveles de acceso de un atributo, y quién puede acceder en cada
caso.
Modificador
72
La propia
clase
Clase hija
Paquete
private
X
protected
X
X
X
public
X
X
X
Resto del
mundo
X
© Grupo EIDOS
5. POO en Java: clases
Por defecto o X
package
X
Tabla 18
Además de los modificadores de acceso un atributo puede presentar otros modificadores denominados
modificadores de contenido, que en este caso no son excluyentes, y que son los dos que indican a
continuación:
•
static: un atributo con el modificador de contenido static mantiene su contenido para todas las
instancias de la clase que se hagan, así como para las clases hijas que de ella se hereden. A
estos atributos se les denomina atributos de clase o variables de la clase, como contraposición
a los atributos de instancia. Mientras que los atributos de instancia se inicializan para cada
nueva instancia que se haga de la clase, es decir, existe una copia por cada instancia de la
clase, de los atributos de la clase existe una sola instancia, independientemente del número de
instanciaciones que de la clase se realicen. De este modo, todos los objetos comparten un
lugar de almacenamiento común.
Debido a la particularidad de este modificador de contenido vamos a comentarlo más con
detenimiento a través de un ejemplo.
El ejemplo más típico de atributo de la clase es un contador del número de objetos existentes
de la clase. Para ello, sólo hay que incrementar el contador desde el constructor de la clase y
decrementarlo desde el destructor.
El código podría ser algo como lo que muestra el Código fuente 27.
class Cuenteo {
//atributo de clase
static long lObj = 0;
// Constructor, incrementa el valor
Cuenteo(){
lObj++;
}
// Destructor, decrementa el valor
void finalize(){
lObj--;
}
// Obtener el número de objetos existentes
long GetObj(){
//devolvemos el valor
return lObj;
}
}
Código fuente 27
Otra cualidad más de los atributos static es que no es necesario instanciar un objeto de la clase
para acceder a ellos. Lo cual resulta muy práctico, sobre todo cuando se trabaja con ciertas
clases.
Cuando hacemos System.out.println(), lo hacemos sin crear previamente un objeto de la clase
System. Cuando lo lógico sería lo que muestra el Código fuente 28.
73
Programación en Java
© Grupo EIDOS
System oSystem = new System();
oSystem.out.println( "Hola" )
Código fuente 28
No lo hacemos así, sino que llamamos directamente al método println() del atributo out de la
clase System sin instanciar previamente un objeto de esa clase. Esto es posible porque out es
un atributo static de la clase System.
Una vez comentado el modificador de contenido static vamos a pasar al segundo de estos
modificadores.
•
final: este modificador indica que el atributo es una constante., es decir, el valor de este
atributo no puede ser modificado. Por convenio los atributos que son constantes se escriben
todos en mayúsculas. Un ejemplo del uso de este tipo de atributos se puede observar en el
Código fuente 29.
class MiClase{
final float PI = 3.14159;
// resto de la implementación de la clase
}
Código fuente 29
Una vez vistos los modificadores de acceso y los de contenido, vamos a ver el resto de modificadores
que se le pueden aplicar a un atributo de la clase.
•
transient: por defecto, los atributos forman parte de la información persistente del objeto. Los
atributos persistentes se salvan cuando se graba en un archivo el objeto, pero no ocurre así con
aquellas declaradas como transient (transitorias).
•
volatile: este modificador indica al compilador que no debe realizar optimizaciones sobre este
atributo. No se suele utilizar.
Ahora podríamos rescribir la sintaxis de la declaración de un atributo indicando todos los
modificadores de la siguiente forma:
[public | protected | private] [static] [final] [transient]
[volatile] <tipo> <nombreVariable>
Y aquí termina la sección dedicada a los modificadores que se pueden utilizar en la declaración de
atributos de la clase.
Tipo
Indica el tipo del atributo es decir, la clase o tipo primitivos a los que pertenece.
74
© Grupo EIDOS
5. POO en Java: clases
Nombre variable
Aquí indicamos el nombre con el que vamos a identificar al atributo, y puede ser cualquier
identificador válido en Java, para más información sobre identificadores consultar el capítulo dedicado
a la sintaxis del lenguaje Java.
Un ejemplo simple podría ser el que muestra el Código fuente 30.
class Caja {
int iTop, iLeft, iBottom, iRight;
...........................
}
Código fuente 30
Cuerpo de la clase. Implementación de métodos
Los métodos proporcionan la operatividad y el comportamiento de la clase. De forma similar a la
implementación de una clase, la implementación de un método se divide en dos partes: la declaración
del método y el cuerpo del método.
declaraciónDelMétodo{
declaración de variables
cuerpoDelMétodo
}
Por otro lado, un método puede recibir parámetros en su llamada, por lo que los componentes de un
método quedarían del siguiente modo:
declaraciónDelMétodo( [listaParámetros] ) {
declaración de variables
implementación
}
En las siguientes secciones vamos a ver cada uno de los componentes que forman parte de la
implementación de un método.
Declaración del método
Del mismo modo que la declaración de la clase o de sus atributos proporcionan información al
compilador acerca de estos componentes, la declaración de un método proporciona una información
similar, la cual es utilizada por el compilador para hacer las comprobaciones necesarias sobre la
correcta utilización del mismo en el resto del código.
La sintaxis de la declaración de un método es la siguiente:
<Modificador (es)> <tipoDeRetorno> <nombreMétodo>
([<listaParámetros>])
[throws <listaExcepciones>]{
}
75
Programación en Java
© Grupo EIDOS
Si detallamos los modificadores del método:
[public | protected | private] [static] [final] [abstract] [native]
[synchronized] <tipoReturn> <nombreMétodo> ([<listaParámetros>])
[throws <listaExcepciones>]{ }
Y la sintaxis mínima necesaria para realizar la declaración de un método es:
<tipoDeRetorno> <nombreMétodo> ([<listaParámetros>]){
}
Algunos de los elementos que aparecen en la declaración general de un método son iguales que los
que aparecían en la declaración de atributos y tienen el mismo significado, vamos a comentar cada uno
de ellos.
En un primer lugar tenemos los modificadores de acceso. Los modificadores de acceso de un método
son los mismos que aparecían en los atributos, es decir, public, private, protected y el modificador por
defecto o package, y además tienen el mismo significado.
También los métodos presentan los modificadores de contenido static y final. Un método static es
aquel cuya implementación es la misma para todos los objetos de la clase, y además un método static
no puede acceder a los atributos de instancia de la clase, sólo tiene acceso a los atributos static de la
clase (atributos de clase). Los métodos static pueden ser invocados sin necesidad de instanciar un
objeto de la clase.
Un ejemplo del modificador static lo encontramos en la clase Math, en la que todos sus métodos son
estáticos, y por lo tanto nos permite realizar operaciones matemáticas sin tener que crear un objeto de
esta clase. Por ejemplo veamos el Código fuente 31.
Math.sin( 18 ); // Devuelve el seno de 18
Math.abs( -37 ); // Devuelve el valor absoluto de –37
Código fuente 31
Aunque nada nos impide crear un objeto de la clase y acceder a sus métodos a través del objeto.
El modificador de contenido final en la declaración de métodos tiene un significado distinto que en la
declaración de atributos. Cuando el modificador se aplica a un método, indica que éste no puede
reemplazarse cuando se hereda de la clase, es decir, las clases hijas no pueden sobrescribir el método
de la clase padre o superclase.
Otro modificador que presenta la declaración de métodos es abstract, este modificador ya no aparece
en la declaración de atributos, sino que aparecía en la declaración de clases. Un método abstract no
tiene implementación, es decir, es un método con el cuerpo vacío y que debe encontrarse en una clase
abstract. La implementación de los métodos abstractos se realizará en las clases hija de la clase
abstracta que contenga dichos métodos, para más información sobre el tema consultar el apartado
Clases Abstractas dentro de este mismo capítulo.
Un modificador sólo aplicable a métodos es native. Este modificador indica que el método ha sido
escrito en código nativo, es decir, en un lenguaje dependiente de la plataforma como puede ser C o
C++. Por ello, estos métodos constan tan sólo de la declaración, no teniendo cuerpo.
76
© Grupo EIDOS
5. POO en Java: clases
native long Cálculo();
Código fuente 32
Estos métodos residen en archivos externos de código fuente en el lenguaje nativo correspondiente y
es necesario seguir unas normas determinadas para poder integrarlos con el resto de la aplicación Java,
pero estas son características avanzadas del lenguaje que se escapan de los objetivos del presente
curso.
El último modificador utilizado para la declaración de métodos es synchronized. Si indicamos que un
método es de tipo synchronized, la Máquina Virtual de Java se comportará de la siguiente forma:
cuando un proceso esté accediendo a ese método, los demás procesos que deseen acceder al mismo
método deberán esperar en una cola hasta que el proceso en curso termine.
Puesto que Java permite que varios procesos se estén ejecutando al mismo tiempo, debe proporcionar
los medios para crearlos, destruirlos y fabricarlos fiables. Para esto último sirve este modificador.
Si, por ejemplo, escribimos un método que sabemos de antemano que puede ser llamado por varios
procesos concurrentemente, en la mayor parte de los casos deberemos impedir que más de un proceso
acceda al método (o a parte de él) mientras que otro proceso lo está haciendo.
A continuación y para tener una visión general de todos los modificadores, se incluye una tabla que
muestra en que casos se puede aplicar cada uno de los modificadores.
Aplicabilidad de los Modificadores
Clase
Método
Atributo
Predeterminado (package) Sí
Sí
Sí
public
Sí
Sí
Sí
protected
No
Sí
Sí
private
No
Sí
Sí
static
No
Sí
Sí
final
Sí
Sí
Sí
synchronized
No
Sí
No
native
No
Sí
No
abstract
Sí
Sí
No
Tabla 19
Una vez indicados los modificadores del método se debe indica el tipo del valor de retorno del
método. En Java, un método puede devolver un valor o no devolver nada. En el primer caso, al
77
Programación en Java
© Grupo EIDOS
declarar el método se tiene que especificar el tipo de dato que se va a devolver, en el segundo caso,
hay que declararlo como de tipo void.
Para devolver el valor deseado, se utiliza la sentencia return seguida del valor, el cual puede ser el
resultado de una expresión de cualquier tipo, lo que obviamente incluye el contenido de un dato de la
clase o una variable, o bien un tipo primitivo (true, null, int, etc.).
Un método que no sea declarado como void, debe contener en su implementación una sentencia return
para devolver el valor correspondiente, que debe ser del mismo tipo que el indicado en la declaración
del método. Un ejemplo podría ser el que muestra el Código fuente 33.
public boolean estaVacio(){
if (items.size()==0)
return true;
else
return false;
}
Código fuente 33
Una vez indicados los modificadores y el tipo del valor de retorno del método, lo siguiente que
debemos facilitar es el nombre del método, el nombre puede ser es cualquier identificador válido en
Java.
Respecto al nombre de los métodos hay una serie de consideraciones:
•
Puesto que Java soporta sobrecarga de métodos, varios métodos dentro de la misma clase
pueden tener el mismo nombre.
•
Si un método tiene el mismo nombre que la clase, es considerado el constructor de la clase.
•
Los métodos de las clases hijas, pueden redefinir los de las clases padres, pero en tal caso,
ambos tienen que tener el mismo nombre, el mismo valor de retorno y la misma lista de
parámetros (más información en el apartado Herencia).
A continuación del nombre del método aparece la lista de parámetros que puede recibir el método. Si
el método no recibe ningún parámetro sólo se indican los paréntesis. En el Código fuente 34 se
muestra la declaración de un método con parámetros.
void MiVentana( int iTop, int iLeft, int iBottom, int iRight, sTítulo,boolean
BarraEstado ){
<implementación>
}
Código fuente 34
Como se puede observar, los argumentos que un método recibe son parejas de <tipo>
<nombreVariable>, separados por comas.
Este método recibe cuatro argumentos de tipo numérico entero, que son las coordenadas de la ventana,
un argumento de tipo cadena que es el título de la ventana y un argumento lógico (booleano) que
78
© Grupo EIDOS
5. POO en Java: clases
indica si la ventana tiene o no barra de estado. Veamos ahora algunas otras consideraciones acerca de
los argumentos en Java:
•
En Java, cualquier tipo de dato válido puede ser pasado como argumento a un método: tipos
primitivos, objetos, arrays, etc.
•
A diferencia de C, en Java, no se puede pasar una función (método en el caso de Java) como
argumento a un método, pero se puede pasar el objeto e invocar el método deseado.
•
Cuando los nombres de los parámetros que recibe un método son iguales a los de los atributos
de la clase, los parámetros son ocultados por los atributos, siendo necesario anteponer la
palabra reservada this a los atributos para poder referirse a ellas (más adelante retomaremos la
palabra reservada this), this representa una referencia a la clase actual.
class Caja{
int iTop, iLeft, iBottom, iRight;
Caja( iTop, iLeft, iBottom, iRight ){
this.iTop = iTop;
this.iLeft = iLeft;
this.iBottom = iBottom;
this.iRight = iRight;
}
}
Código fuente 35
•
De lo dicho anteriormente, se puede deducir que no se permite que el nombre de un argumento
sea igual al de las variables locales al método. Y obviamente, no se permite la duplicidad de
nombres de argumentos para el mismo método.
•
En Java, los argumentos se pasan por valor, no por referencia, esto quiere decir, que para los
datos de tipos primitivos, lo que se pasa es el valor de la variable, y para los datos de tipo
referencia (como son los objetos) se pasa la referencia, pero ésta no puede ser cambiada. De
este modo, si lo que se desea es cambiar un valor, es necesario pasar como argumento al
método un objeto que contenga ese valor en un atributo, y que incluya algún mecanismo para
poder modificarlo, ya sea porque el atributo es público y se puede acceder a él directamente, o
porque el objeto dispone de un método para poder alterar el atributo.
Después de la lista de parámetros del método podemos utiliza la cláusula throws. Esta cláusula se
utiliza para lanzar los errores (excepciones) que puedan producirse en el método. A continuación de
esta cláusula aparece el nombre de las las clases de las excepciones que se pueden lanzar, separadas
entre comas. El tratamiento de excepciones lo veremos con detenimiento en el apartado
correspondiente en el próximo capítulo.
Un ejemplo de la utilización de la cláusula throws lo podemos ver en el Código fuente 36.
public void borraFichero() throws IOException{
}
Código fuente 36
79
Programación en Java
© Grupo EIDOS
Declaración de variables
Esta sección, al igual que la siguiente, ya formaría parte del cuerpo del método.
Tras la sección de declaración del método, se pueden declarar las variables que el método vaya a
necesitar. Estas son locales al método y tanto su visibilidad como su longevidad están inscritas dentro
del cuerpo del método. Estas variables también se denominan objetos locales.
Los variables locales son creadas cada vez que el método es invocado y destruidas al terminar éste y
son sólo conocidas por el método, sin embargo los atributos existen mientras exista el objeto y son
conocidas (al menos) por todos los métodos de la clase.
En el Código fuente 37 se puede observar la declaración de variables locales en un método.
class Caja{
// Variables miembro de la clase
int iTop, iLeft, iBottom, iRight;
// Constructor
Caja( iTop, iLeft, iBottom, iRight ){
....................
}
// Extraer el área de la caja
long Area(){
long lArea; // Variable local
lArea = iTop * iLeft;
return lArea;
}
}
Código fuente 37
Implementación del método
Implementar un método es simplemente escribir el código necesario para que el método realice la
función para la que ha sido creado.
Hay tres palabras clave del lenguaje que merecen una mención especial a la hora de implementar un
método, estas palabras son: this, super y return.
•
this: con esta palabra, podemos referirnos a los miembros de la clase. Como ya vimos
representa una referencia a la clase actual. Dentro de la implementación de un método
utilizaremos this para deshacer la ambigüedad que se produzca entre los nombres de los
atributos de la clase y el nombre de los parámetros del método, como ya vimos anteriormente.
Para más información acerca de this, véase el apartado Herencia. Un ejemplo del uso de this
se muestra en el Código fuente 38.
class HSBColor {
//atributos de la clase
int hue, saturation, brightness;
//constructor
HSBColor (int hue, int saturation, int brightness) {
this.hue = hue;
this.saturation = saturation;
80
© Grupo EIDOS
5. POO en Java: clases
this.brightness = brightness;
}
}
Código fuente 38
•
super: si un método de una clase hija redefine (sobrescribe) un método de su clase padre, se
puede referir al método redefinido anteponiendo super. Esta palabra reservada también se
puede utilizar para acceder a los atributos de la clase padre, cuando estos sean redefinidos por
atributos de la clase hija. Para más información acerca de super, véase el apartado Herencia.
•
return: la palabra clave return permite devolver un valor desde un método, este valor puede ser
cualquier tipo de dato válido de Java. Veamos un ejemplo del uso de return en el Código
fuente 39.
class Test {
void nada( String s ) {
System.out.println( "Cadena: " + s );
}
int entero( int i ) {
return i * i;
}
boolean lógico( boolean b ) {
return ! b;
}
String cadena( String s ) {
if( s.length ) == 0
return null;
else
return s.toUpper();
}
}
Código fuente 39
A la vista del Código fuente 39 se deben realizar los siguientes comentarios:
•
Los métodos devuelven un valor consecuente con la declaración formal que se ha hecho de los
mismos. En caso de que no se hiciera así, el compilador reportaría un error en la coincidencia
de los tipos y no permitiría generar la clase correspondiente.
•
El primer método al ser declarado de tipo void, no contiene la sentencia return.
•
En el caso de que un método tenga que devolver un objeto, el tipo de dato del objeto devuelto
tiene que ser o una subclase de la clase indicada o la propia clase indicada, reportándose el
error de tipos no coincidentes en cualquier otro caso.
Una clase puede tener tantos métodos con el mismo nombre como se desee, siempre que el número y/o
tipo de argumentos que cada método recibe sea diferente. Como el compilador guarda durante el
proceso de compilación los parámetros junto con el nombre del método, sabe a quién va dirigida en
cada caso la llamada. Esta capacidad presente en la POO se denomina sobrecarga
En Java existe sobrecarga de métodos, pero no de operadores, como en C++.
81
Programación en Java
© Grupo EIDOS
Herencia
En este apartado vamos a retomar los conceptos sobre la herencia en la Programación Orientada a
objetos, y vamos a comentar como implementa el lenguaje Java el mecanismo de herencia.
En Java, como en todo lenguaje de POO se pueden heredar unas clases de otras. A la clase heredada se
le llama, subclase o clase hija, y a la clase de la que se hereda superclase o clase padre. De hecho, en
Java todas las clases heredan, ya sea explícita o implícitamente, de la clase Object (esta clase se verá
con detenimiento en el apartado correspondiente del siguiente capítulo).
Al heredar, la clase heredada toma directamente el comportamiento de su superclase, pero puesto que
ésta puede derivar de otra, y esta de otra, etc., una clase toma indirectamente el comportamiento de
todas las clases de la rama del árbol de la jerarquía de clases a la que pertenece.
Se heredan los atributos y los métodos, por lo tanto, ambos pueden ser redefinidos en las clases hijas,
aunque lo más común es redefinir métodos. Gracias a la herencia, las clases se van convirtiendo en
más especializadas.
Muchas veces las clases, especialmente aquellas que se encuentran próximas a la raíz en el árbol de la
jerarquía de clases, son abstractas, es decir, sólo existen para proporcionar una base para la creación de
clases más específicas, y por lo tanto no puede instanciarse objetos de ellas; son las clases abstractas
(en otros lenguajes de POO se denominan clases virtuales).
Veamos ahora algunas de las particularidades de la herencia en Java:
•
En Java una clase no puede tener más que una clase padre o superclase, es decir, no se admite
la herencia múltiple.
•
Java proporciona un gran control a la hora de manejar la herencia de clases, indicando qué se
puede heredar y qué no, qué se puede redefinir y qué no así como qué hay que redefinir.
•
Se heredan los atributos y métodos public y protected.
•
Se heredan los atributos y métodos declarados con el acceso por defecto (package), siempre
que la clase hija se encuentre dentro del mismo paquete que la clase padre.
•
No se heredan aquellos atributos y métodos que la subclase declara con el mismo nombre que
en la superclase. Aunque se puede acceder a los atributos y métodos de la superclase mediante
el uso de la palabra clave super, como veremos a continuación.
•
No se heredan los atributos y métodos private.
Cuando una clase hereda de otra se indica al declarar la clase, y para ello se utiliza la palabra clave de
Java extends.
La sintaxis es la siguiente (para consultar la sintaxis completa de la declaración de una clase, véase en
apartados anteriores):
class <NombreClase> extends <NombreSuperClase>{
..........................
}
Por ejemplo, veamos el Código fuente 40.
82
© Grupo EIDOS
5. POO en Java: clases
class Marco extends Area{
<codificación de la clase>
}
Código fuente 40
Con la palabra reservada this, podemos referirnos a los atributos y métodos de la clase, permite
diferenciar entre los argumentos de los métodos y los atributos de la clase con el mismo nombre.
De hecho, siempre que dentro del cuerpo de un método nos refiramos a cualquier miembro de la clase,
ya sea un atributo o método, podemos anteponer this, aunque en caso de no existir duplicidad, el
compilador asume que nos referimos a un miembro (método o atributo) de la clase. Aquí bajo la
denominación miembro se engloba a métodos y atributos.
Algunos programadores prefieren utilizar this para dejar claro que se está haciendo referencia a un
miembro de la clase.
Al contrario que this, super permite hacer referencia a miembros de la clase padre (o a los ancestros
anteriores, que no hayan sido ocultados por la clase padre) que se hayan redefinido en la clase hija.
Si un método de una clase hija redefine un miembro, ya sea atributo o método, de su clase padre, es
posible hacer referencia al miembro redefinido anteponiendo super.
El siguiente ejemplo demuestra el uso de this y super, a la hora de redefinir o sobrescribir atributos y
métodos de la clase padre.
Si tenemos la clase padre UnaClase (Código fuente 41), y heredamos de ella la clase OtraClase que
sobrescribe tanto el atributo de la clase padre como el método (Código fuente 42).
class UnaClase {
boolean bVariable;
void Metodo() {
bVariable = true;
}
}
Código fuente 41
class OtraClase extends UnaClase {
boolean bVariable;
void Metodo() {
this.bVariable = false;
super.Metodo();
System.out.println(bVariable);
System.out.println(super.bVariable);
}
}
Código fuente 42
83
Programación en Java
© Grupo EIDOS
Entonces, el resultado de llamar a OtraClase.Método() será el siguiente:
false
true
Ya que el método Metodo() de la clase hija llama al método de igual nombre, el cual pone a true el
valor de su variable, y el de la clase hija pone a false el de la suya, aunque ambos métodos y variables
tengan el mismo nombre, podemos distinguirlos al referirnos a ellos utilizando o no, según sea el caso,
la palabra super.
De hecho, el Código fuente 43, también podría haberse escrito como muestra el Código fuente 44
this.bVariable = false;
Código fuente 43
bVariable = false;
Código fuente 44
Ya que en ausencia del identificador this, el compilador asume que nos referimos al atributo de la
clase.
Se debe observar que por los mismos motivos, la línea que aparece en el Código fuente 45, equivale a
la instrucción que muestra el Código fuente 46.
super.Método();
Código fuente 45
super.bVariable = true;
Código fuente 46
Cuando una clase hereda de otra puede bien ampliar el comportamiento o bien sustituir el
comportamiento de los métodos de la clase padre. Veamos ambas posibilidades y algunos otros
aspectos relacionados con este tema.
Sustituir la implementación de un método
Es muy común que en una clase se quiera cambiar completamente el comportamiento de un método de
la clase de la que se hereda. Algunos métodos tienen que ser sustituidos por otros para un correcto
funcionamiento de la clase heredada.
84
© Grupo EIDOS
5. POO en Java: clases
Para sustituir un método de una clase superior por otro en la clase heredada, basta con crear un método
en la clase hija con el mismo nombre que el que deseamos sustituir e implementar el comportamiento
deseado. Veamos, por ejemplo el Código fuente 47.
class A {
void hacerAlgo() {
// hacer lo que sea
}
}
class B extends A {
void hacerAlgo() {
// aquí hacemos otra cosa
}
}
Código fuente 47
Ampliar la implementación de un método
En otras ocasiones, lo que se desea no es cambiar el comportamiento de un método de la superclase,
sino ampliarlo. El caso más común es el de los constructores, donde suele llamarse al constructor de la
superclase pasándole los parámetros que éste requiere y utilizándose el resto de parámetros para
inicializar los valores de la subclase.
Como es lógico, cuando se amplia la funcionalidad de un método, se tiene que preservar la
funcionalidad antigua y añadirle la nueva. Para ello, se invoca desde el método de la clase heredada
(subclase o clase hija) al método del mismo nombre de la superclase, lo que se hace con la palabra
reservada super.
class Padre {
void elMétodo( int param1 ) {
// hacer lo que sea
}
}
class Hija extends Padre {
void elMétodo( int param1, param2 ) {
// llamamos al método de la clase padre
super.elMetodo( param1 );
// aquí hacemos otra cosa más
}
}
Código fuente 48
En los constructores es casi imperativo (a menos que hubiera que hacer transformaciones con los
parámetros que el método recibe) llamar antes de hacer ninguna otra cosa, al constructor de la
superclase.
Métodos que una clase heredada no puede redefinir
No se pueden sustituir los siguientes métodos (ya sea para ampliarlos o para reemplazarlos):
85
Programación en Java
© Grupo EIDOS
•
Métodos declarados como final. Por definición, un método declarado como final no puede ser
modificado en las clases heredadas. Si se intentara, el compilador generaría un error.
•
Métodos declarados como static: Un método static es un método de la clase, y lógicamente no
puede ser sustituido.
Métodos que una clase heredada tiene que redefinir
En Java se pueden declarar métodos que existen sólo para ser redefinidos, estos son los métodos
definidos con el modificador abstract. Las clases que hereden de una clase con métodos abstract deben
obligatoriamente redefinir los métodos abstractos, ya que sino no tendrían ningún contenido. En el
siguiente apartado se comenta con detenimiento las clases abstractas.
Clases abstractas
En este apartado vamos a comentar el concepto de clase abstracta y como es implementado el mismo
en el lenguaje Java.
De forma general podemos definir una clase abstracta como una clase que no es instanciable, y que se
utiliza en el diseño de una jerarquía de clases.
Si una clase contiene uno o más métodos abstractos, tiene que ser definida como abstracta. Los
métodos abstractos no tienen contenido alguno, no tienen cuerpo, por lo que estas clases se utilizan
solamente con fines de diseño, no de implementación, como ya habíamos indicado anteriormente no se
pueden instanciar objetos de una clase abstracta.
Cuando un método es abstracto, éste, al no tener contenido, tiene que ser redefinido e implementado
en las clases hijas de la clase abstracta, es decir, una clase abstracta se suele situar en la raíz de una
jerarquía de clases.
Las clases abstractas suelen definir conceptos genéricos que no deben concretar en ningún objeto. En
el mundo real podemos encontrar una clase abstracta como la clase Comida, sería una clase abstracta
ya que nunca vemos objetos Comida, comemos objetos Manzana, Fresa, Filete, Sopa, etc. La clase
Comida representa el concepto abstracto de las cosas que podemos comer, pero nunca vamos poder a
comer un objeto Comida.
De forma similar, en la Programación Orientada a Objetos, hay veces en las que necesitamos
modelizar un concepto abstracto (como en el caso de Comida) sin tener que crear una instancia de ello.
Por ejemplo la clase Number de Java representa el concepto abstracto de los números. Tiene sentido
modelizar y definir el concepto de número en un programa, pero no tiene sentido crear un objeto
genérico de número.
La clase Number va a ser la clase padre de las clases Integer y Float, estas dos otras clases ya
implementan un tipo específico de números. Por lo tanto una clase abstracta puede ser únicamente
subclasificada, es decir, se puede heredar de ella, pero no se puede instanciar. La clase abstracta
Number estaría declarada como indica el Código fuente 49.
Como ya hemos indicado una clase abstracta puede contener métodos abstractos (y también métodos
no abstractos), que no van a contener ninguna implementación, por lo tanto mediante estos métodos
podremos definir el comportamiento que van a tener las clases que hereden de la clase abstracta.
86
© Grupo EIDOS
5. POO en Java: clases
abstract class Number{
..........
}
Código fuente 49
Las clases hijas de la clase abstracta deberán implementar (redefinir) obligatoriamente todos los
métodos abstractos de la clase padre.
Hay sin embargo tres tipos de métodos que no pueden hacerse abstractos:
•
Constructores: como es lógico, un constructor no puede ser abstracto ya que éste tiene que
inicializar el objeto.
•
Estáticos: los métodos static no pueden ser abstractos porque no pueden ser redefinidos en las
clases hijas.
•
Privados: los métodos abstractos tienen que ser redefinidos en las clases hijas, pero por
definición, los métodos private no son visibles desde las clases heredadas por lo que esta labor
se hace imposible.
A continuación se muestra un ejemplo del uso de clases abstractas. Tenemos una clase abstracta
denominada ObjetoGrafico, que representa un objeto que podemos dibujar. Esta clase ofrece dos
atributos, x e y que indican la posición en la que se encuentra el objeto a dibujar; también ofrece dos
métodos, el método moverA(), que no es abstracto y que permite desplazar el objeto a un lugar
determinado, y un segundo método que si es abstracto y que de llama dibujar(), y su función es la de
dibujar la figura.
El método moverA() se implementa en la clase abstracta, ya que desplazar un objeto gráfico a una
posición se hace siempre igual con independencia del tipo de objeto. Sin embargo, el método dibujar()
será distinto en cada tipo de objeto gráfico o figura, no se dibuja igual una circunferencia que un
rectángulo.
El código de la clase ObjetoGrafico aparece en el Código fuente 50.
abstract class ObjetoGrafico{
int x,y;
..........
void moverA(int nuevaX, int nuevaY){
//implementación del método
}
abstract void dibujar();
}
Código fuente 50
De la clase ObjetoGrafico heredan otras dos clases: Rectangulo y Circunferencia. Por lo tanto la
jerarquía de clases quedaría como muestra la Figura 4.
Las clases Rectangulo y Circunferencia, al heredar de ObjetoGrafico, deben redefinir o sobrescribir el
método abstracto dibujar() para que tenga un contenido.
87
Programación en Java
© Grupo EIDOS
Figura 4
El aspecto que pueden tener las clases Rectangulo y Circunferencia es el que se muestra en el Código
fuente 51.
class Rectangulo extends ObjetoGrafico{
void dibujar(){
//implementación del método
}
}
class Circunferencia extends ObjetoGrafico{
void dibujar(){
//implementación del método
}
}
Código fuente 51
88
POO en Java: otros conceptos
Introducción
Este es el último capítulo de los dedicados a la forma en la que el lenguaje Java implementa el
paradigma de la Programación Orientada a Objetos. En los capítulos anteriores hemos visto el
tratamiento e implementación que el lenguaje Java hace de los objetos y clases, y en este capítulo
vamos a ver el concepto de interfaz y excepción.
Además comentaremos un concepto propio del lenguaje: los paquetes, describiendo de forma breve los
paquetes principales que ofrece Java.
Terminaremos realizando una descripción de la clase raíz de la jerarquía de clases de Java, es decir, la
clase Object.
Interfaces
Un interfaz en Java es un conjunto de constantes y de métodos que no poseen una implementación, un
interfaz define un comportamiento o un protocolo. Vamos a ver el concepto de interfaz a través de un
ejemplo.
Un interfaz podrá ser RobarCasa, en este interfaz se define el protocolo o comportamiento necesario
para robar una casa. Este interfaz tiene los métodos accederCasa(), buscarBotin() y abandonarCasa().
Estos tres métodos indican a grandes rasgos los pasos para robar una casa.
Programación en Java
© Grupo EIDOS
El primero de los métodos, consiste en conseguir entran en la casa, este método no tiene ninguna
implementación, al igual que todos métodos que formen parte de un interfaz, ya que cada ladrón
tendrá un procedimiento determinado: forzar la puerta, entrar por la ventana, etc. Lo mismo ocurre con
los otros dos métodos.
De esta forma la clase que implemente el interfaz obligatoriamente debe dar un contenido determinado
a todos los métodos indicados por el interfaz. En el caso del ejemplo anterior podemos tener dos clases
LadronPiso y LadronChalet, que van a implementar el interfaz RobarCasa.
Cada uno de estos dos tipos de ladrones implementará de forma distinta el interfaz, ya que no es lo
mismo entran a robar en un piso décimo que en una casa de campo, pero lo que si está claro es el
protocolo que van a utilizar en todos los casos los ladrones que implementen el interfaz RobarCasa:
acceder a la casa, buscar los objetos de valor y huir con ellos.
Se podría pensar que el interfaz se puede sustituir por una clase abstracta y que las clases que
implementan el comportamiento heredaran de ella, como ya habíamos visto en el apartado anterior.
Pero esto no es adecuado en la mayoría de los casos, ya que por ejemplo, la clase LadronPiso puede
ser una clase que herede de la clase Persona y no podríamos hacer que heredará también de la clase
Robo, ya que en Java no se permite la herencia múltiple.
Una vez comentado el concepto de interfaz e ilustrado con ejemplos en la vida real, vamos a ver como
definimos en Java un interfaz.
La definición de un interfaz tiene dos partes o secciones: la declaración del interfaz y el cuerpo del
interfaz.
Declaración del interfaz
La declaración de un interfaz tiene la siguiente sintaxis general:
[public] interface NombreInterfaz [extends <NombreInterface1> [,
<NombreInterface2>, ... ]]{
}
Un interfaz se puede declarar como público con el modificador public, para que lo pueda implementar
cualquier clase, pero sino indicamos nada (por defecto package), sólo se podrá utilizar en las clases
que se encuentren en el mismo paquete que el interfaz.
La palabra reservada interface indica que lo que estamos definiendo es un interfaz. A continuación
aparece el nombre del interfaz que puede ser cualquier identificador válido del lenguaje Java.
A continuación se indican los interfaces de los que se hereda, en el caso de los interfaces si que se
permite la herencia múltiple, como se puede observar. Un interfaz hereda todos los métodos y
constantes de sus interfaces padre. Para heredar de los interfaces padre se sigue utilizando la palabra
reservada extends.
Cuerpo del interfaz
En el cuerpo del interfaz nos encontramos la definición de los métodos y constantes del interfaz.
90
© Grupo EIDOS
6. POO en Java: otros conceptos
Los métodos declarados en el interfaz son implícitamente abstractos, ya que no ofrecen ningún tipo de
implementación, esta implementación la realizarán las clases que implementen el interfaz. Por lo tanto
no es necesario utilizar la palabra reservada abstract con los métodos de un interfaz.
Los atributos que se definen en un interfaz son implícitamente constantes, y es como si se aplicaran los
modificadores public static final. Aunque no es necesario utilizarlos, y como regla de estilo los
modificadores static y final nunca aparecen en la declaración de los atributos (en este caso constantes)
de un interfaz.
Un ejemplo de código de un interfaz lo tenemos en el Código fuente 52 que representa el
comportamiento de un reloj despertador.
public interface Despetador{
//atributos del interfaz
public long UN_SEGUNDO=1000;
public long UN_MINUTO=60000;
//método del interfaz
public void despertar();
}
Código fuente 52
La clase Reloj implementaría este interfaz dándole un contenido al método despertar().
Excepciones
En el presente apartado vamos a comentar el mecanismo de errores y recuperación de los mismos que
nos ofrece el lenguaje Java.
Una excepción lo podemos definir como un suceso excepcional que se produce durante la ejecución de
un programa y que interrumpe el flujo normal de las instrucciones del programa.
Existen distintos tipos de errores que pueden causar excepciones, desde problemas en el hardware a
problemas en la programación, al acceder por ejemplo a un elemento de un array que no existe.
En Java, cuando se produce un error en un método, el método crea un objeto excepción y se lo pasará
al entorno de ejecución. Este objeto contendrá información acerca del error que se ha producido.
El entorno de ejecución es el responsable de encontrar el código que trate el error que se ha producido.
En la terminología del lenguaje Java, crear un objeto excepción y pasárselo al entorno de ejecución se
denomina lanzar una excepción.
Los candidatos a manejar o tratar el error que se ha producido son los métodos que se encuentran en la
pila de llamadas del método en el que se ha producido el error, es decir, los métodos que se han
invocado antes. El entorno de ejecución va buscando hacia atrás en la pila de llamadas desde el
método en el que se ha producido el error, hasta que encuentra el método que tiene el tratamiento de la
excepción adecuado.
Un tratamiento de excepción adecuado se produce cuando la excepción que se trata es del mismo tipo
que la que se ha lanzado al producirse el error. Cuando se trata la excepción en la terminología Java se
dice que se ha atrapado la excepción.
91
Programación en Java
© Grupo EIDOS
Si el entorno de ejecución no encuentra ningún método que trate la excepción se detendrá la ejecución
del programa.
En Java un método en el que se pueden dar excepciones, tiene dos posibilidades, o atrapar la
excepción o lanzarla.
Un método atrapará una excepción ofreciendo un manejador de la excepción. Más adelante
mostraremos como se implementan en Java. Por el contrario, si un método no atrapa una excepción
deberá lanzarla, para que sea atrapada por otro método.
Java tiene diferentes tipos de excepciones, excepciones de entrada/salida, en tiempo de ejecución e
incluso podemos crear nuestras propias excepciones.
Las excepciones en tiempo de ejecución son aquellas que se producen durante la ejecución del
programa, es decir, en su entorno de ejecución. Este tipo de excepciones incluye: errores aritméticos,
de punteros, acceso a índices, etc. Estas excepciones no son obligatorias tratarlas, sin embargo hay
otras, que no son de tiempo de ejecución, y que el compilador si obliga a tratarlas, es decir, el método
debe lanzarlas o atraparlas. Estas excepciones se denominan excepciones verificadas.
A continuación vamos a explicar como lanzar nuestras excepciones dentro de un método.
Si queremos lanzar una excepción en un método utilizaremos la sentencia throw. Esta sentencia
requiere un único argumento: un objeto que descienda de la clase Throwable. La sintaxis de esta
sentencia es:
throw SubClaseThrowable
Para indicar que un método puede lanzar una excepción se utiliza la cláusula throws, ya que Java nos
obliga a atrapar la excepción, o bien a lanzarla, como es el caso. La cláusula se sitúa a continuación de
la lista de parámetros del método.
Un ejemplo de un método que genera una excepción e indica que la va a lanzar es el que muestra el
Código fuente 53.
public Object pop() throws EmptyStackException{
Object obj;
if (size==0)
//si la pila esta vacía se crea una instancia de la excepción
throw new EmptyStackException();
//se desapila el elemento de la pila
obj=objectAt(size-1);
setObjectAt(size-1,null);
size--;
return obj; //se devuelve el elemento desapilado
}
Código fuente 53
En el ejemplo anterior el método pop() desapila un elemento de una estructura de datos pila, pero si la
pila está vacía crea una instancia de la clase EmptyStackException y la lanza.
En esta caso la excepción la hemos lanzado nosotros mismos, pero hay otras ocasiones en las que
invocamos a un método de un objeto determinado, y este método es el que va a lanzar la excepción, en
este caso, en el método nuestro en el que le estemos llamando deberemos utilizar la cláusula throws
92
© Grupo EIDOS
6. POO en Java: otros conceptos
para indicar que la excepción no la vamos a tratar y que nuestro método también la va a lanzar. Así
por ejemplo, si queremos llamar al método pop() desde otro método distinto, y éste nuevo método
lanza la excepción del método pop(), escribiremos el Código fuente 54.
public miMetodo() throws EmptyStackException{
Object objeto;
objeto=pop();
}
Código fuente 54
Ya hemos visto como lanzar excepciones, ahora debemos ver como atraparlas.
En Java para atrapar y tratar excepciones se utilizan las palabras reservadas try, catch y finally,
formando parte de un bloque que va a ser el manejador de la excepción.
Para tratar excepciones, el primer paso es declarar un bloque try que englobe a todas las sentencias
susceptibles de producir una excepción. Si se produce alguna excepción en los métodos comprendidos
dentro del bloque try, se ejecutará el manejador de excepciones asociado al bloque try.
try{
//sentencias que pueden lanzar excepciones
}
El manejador de excepciones asociado a un bloque try se indica mediante uno o más bloques catch. La
sintaxis de la sentencia catch es:
catch(SubClaseTrowable nombreVariable){
[//sentencias que manejan la excepción]
}
En su forma más sencilla el bloque catch puede ir vacío, sin ninguna sentencia. Los parámetros de
catch declaran una variable del tipo de excepción que se desea atrapar. Mediante la variable de la
excepción se recogerá el objeto correspondiente a la excepción que se ha producido y se utilizará para
consultar la información que ofrece.
Podemos utilizar para un mismo bloque try varios bloques catch, de esta forma podremos indicar
diferentes tipos de excepciones a tratar. Se debe ir desde la excepción más particular a la más general,
para que no se oculten entre sí, cuando se produzca la excepción se ejecutará el bloque catch que
corresponda al tipo de excepción en cada caso. De esta forma la sintaxis de los bloques try y catch
quedaría:
try {
. . .
} catch (SubClaseTrowable nombreVariable) {
. . .
} catch (SubClaseTrowable nombreVariable) {
. . .
}
Como hemos visto a un bloque try le deben corresponder uno o más bloques catch, y hay veces en las
que también le puede corresponder un bloque finally, aunque no de forma obligatoria.
93
Programación en Java
© Grupo EIDOS
En un bloque finally escribiremos todo el código de limpieza (liberar objetos, ficheros, etc.) en el caso
de que se produzca una excepción, finally nos asegura que todas las sentencias de su bloque se
ejecutarán cuando se produzca la excepción, una vez tratada por el bloque catch correspondiente.
En el Código fuente 55 ofrecemos un esquema de código que relaciona estos tres tipos de bloques, así
un bloque try tendrá uno o más bloques catch y uno o ningún bloques finally.
try {
. . .
} catch (SubClaseTrowable nombreVariable) {
. . .
} [catch (SubClaseTrowable nombreVariable) {
. . .
}
[....]
finally{
.....
}
Código fuente 55
En siguientes capítulos volveremos a retomar el tema de las excepciones en diversos ejemplos, ya que
es una práctica común en la programación en Java.
Paquetes
El término paquete dentro de la programación en Java ya lo hemos mencionado en algunos momentos
del curso, ha llegado la hora de ver los paquetes en profundidad.
Los paquetes es una forma de clasificar y organizar clases e interfaces, para evitar conflictos de
nombres, para localizar una clase de forma más sencilla y rápida y para controlar el acceso
(modificador de acceso por defecto) a las clases.
Las clases que conforman la jerarquía de clases de Java se encuentran organizadas y son miembros de
distintos paquetes, de esta forma las clases básicas del lenguaje están en el paquete java.lang, las
clases de entrada/salida en el paquete java.io, etc. Es decir, cada paquete contiene una serie de clases e
interfaces relacionados.
Para crear un paquete simplemente deberemos situar la línea que muestra el Código fuente 56 al
principio del fichero fuente de la clase o interfaz que queremos que pertenezca al paquete.
package nombrepaquete;
public class MiClase{
}
Código fuente 56
Deberemos incluir la sentencia package en cada fichero fuente de las clases e interfaces que formen
parte de un paquete determinado. Si en un fichero fuente se definen varias clases, todas ellas
pertenecerán al paquete indicado por la sentencia package.
94
© Grupo EIDOS
6. POO en Java: otros conceptos
Si no utilizamos una sentencia package al principio del fichero fuente se considera que las clases o
interfaces pertenecen al paquete por defecto, que es un paquete sin nombre.
Los paquetes nos permiten tener distintas clases con el mismo nombre si se encuentran en paquetes
diferentes, el compilador distingue las clases a través de los paquetes.
Para la nomenclatura de paquetes existe un convenio establecido, las compañías desarrolladoras de
software utilizan su nombre de dominio en Internet como nombre de paquete. Normalmente se sigue la
siguiente nomenclatura: com.compañia.paquete, como se puede observar no se utilizan letras
mayúsculas. Así por ejemplo, un paquete de clases gráficas desarrollado por la empresa Grupo EIDOS
sería: com.eidos.graficos.
Podemos decir que el nombre completo de una clase se compone de: el nombre del paquete del que
forma parte y el propio nombre de la clase. De esta forma si la clase Circunferencia forma parte del
paquete del ejemplo anterior, su nombre completo sería: com.eidos.graficos.Circunferencia.
Si hacemos referencia a esta clase de la siguiente forma: Circunferencia, decimos que estamos
utilizando la nomenclatura reducida.
Dentro del código fuente de una clase nos podremos referir a otra clase utilizando la nomenclatura
reducida si la clase que estamos definiendo se encuentra en el mismo paquete que la que estamos
referenciando en nuestro código. Pero para poder hacer referencia a una clase que se encuentra en un
paquete distinto con esta nomenclatura reducida deberemos importar la clase desde su paquete
correspondiente.
Para importar una clase en un paquete deberemos utilizar la sentencia import antes de la declaración
de cualquier interfaz o clase, y facilitando el nombre completo de la clase que queremos importar. La
sintaxis de esta sentencia es:
import nombrepaquete.NombreClase
Si necesitamos importar varias clases de varios paquetes irán las diferentes sentencias import unas
debajo de otras, como muestra el Código fuente 57.
import com.eidos.graficos.Rectangulo;
import com.eidos.graficos.Triangulo;
import java.awt.Button;
Código fuente 57
Para importar un paquete con todas sus clases, es decir, el paquete completo, utilizaremos en lugar del
nombre de la clase, un asterisco (*), como indica el Código fuente 58.
import com.eidos.graficos.*;
import java.awt.*;
Código fuente 58
No hay ningún tipo de eficiencia a la hora de importar una única clase de un paquete o el paquete
completo.
95
Programación en Java
© Grupo EIDOS
Si el paquete contiene subpaquetes, mediante la notación * no se importan los subpaquetes, sino que
tenemos que indicarlo de la siguiente forma:
//se importan todas las clases del paquete pero no sus subpaquetes
import com.eidos.graficos.*;
//para importar el paquete 2d
import com.eidos.graficos.2d.*;
Código fuente 59
Por defecto, cuando creamos un fichero fuente, de forma implícita se importa por completo el paquete
java.lang, este paquete contiene las clases básicas del lenguaje Java. De esta forma cada fichero fuente
es como si tuviera la sentencia import (Código fuente 60).
import java.lang.*;
Código fuente 60
Además del paquete java.lang, el sistema en tiempo de ejecución del lenguaje Java importa el paquete
por defecto (sin nombre) y el paquete actual.
El nombre de los paquetes no es arbitrario, sino que tiene una correspondencia física en el sistema de
archivos. Vamos a comentar esta afirmación detenidamente.
El nombre de un fichero fuente en Java se construye con el nombre de la clase pública definido en el y
la extensión .java. Es decir, en un fichero fuente sólo podremos definir una única clase como public, y
además el nombre de esta clase debe coincidir exactamente (mayúsculas y minúsculas) con el nombre
del fichero en la que se encuentra definida. De esta forma la clase pública Rectangulo debe estar
definida en un fichero fuente llamado Rectangulo.java. Al compilar el fichero fuente se generará el
fichero .class correspondiente, en este caso sería Rectangulo.class.
Y el nombre del paquete en el que se encuentre la clase tiene una correspondencia con la estructura de
directorios en la que se encuentra el fichero fuente. De esta forma, si la clase Rectangulo se encuentra
en el paquete com.eidos.graficos, el fichero fuente, Rectangulo.java, debe encontrarse en el camino
com\eidos\graficos. Ahora bien, es necesario identificar desde dónde se comienza a tomar la ruta para
que se localice el fichero fuente que representa a una clase que pertenece a un determinado paquete.
El compilador debe ser capaz de localizar todos los paquetes, tanto los estándar del lenguaje Java
como los definidos por los programadores, es decir, el compilador debe conocer dónde comienza la
estructura de directorios definida por los paquetes y en la cual se localizan las clases. Esto se consigue
mediante el ClassPath (ruta o camino de clase).
El ClassPath contendrá todas las rutas en las que comienzan las estructuras de directorios en las que
están contenidas las clases. De forma predeterminada, todas las clases estándar de Java se indican en el
ClassPath, este camino dependerá del entorno de desarrollo que hayamos elegido para construir
nuestros programas Java. Vamos a comentar la forma en la que utilizan el ClassPath los distintos
entornos de desarrollo que hemos elegido para el presente curso (JDK, Visual J++ y JBuilder).
En la versión 1.1 del lenguaje Java las clases estándar de Java se encontraban en un fichero llamado
classes.zip.
96
© Grupo EIDOS
6. POO en Java: otros conceptos
En el caso de la herramienta de Microsoft Visual J++ 6.0 las clases se encuentran localizadas en
diversos ficheros ZIP que se encuentran en el camino: directorioWindows\Java\Packages.
Por lo tanto estas dos herramientas añaden estas rutas al ClassPath al instalarse.
Sin embargo en la versión 2 del lenguaje Java el JDK 1.3 no requiere utilizar la variable ClassPath
para indicar las clases estándar del lenguaje, el ClassPath sólo será utilizado para indicar clases
adicionales, por ejemplo, clases desarrolladas por nosotros.
El ClassPath lo podemos establecer de dos formas diferentes, a través las opciones de compilación del
entorno de desarrollo, o bien a través de una variable de entorno del sistema denominada
CLASSPATH. En la variable de entorno CLASSPATH se especifica, separadas entre punto y como
(;), las rutas a las diferentes clases Java que deseamos localizar para poder utilizarlas en nuestro
código.
Para indicar la variable de entorno ClassPath directamente acudiremos al fichero AUTOEXEC.BAT
en el caso de encontrarnos en sistemas operativos Windows 95/98 y añadiremos la línea
correspondiente. CLASSPATH=ruta_clases_1;ruta_clases_2;... ;ruta_clases_n. Si estamos en sistemas
operativos Windows NT/2000 seleccionaremos la opción Panel de Control/Sistema/Entorno, y dentro
de ésta añadiremos una variable de entorno CLASSPATH si no existe, y si existe la modificaremos
indicando el camino adecuado para las clases.
Como ya hemos indicado, la herramienta JDK 1.3 de Sun no requiere que se establezca el ClassPath
para las clases estándar, pero si es necesario para utilizar otras clases, como pueden ser clases creadas
por nosotros o por terceros, en este caso estableceremos la variable de entorno CLASSPATH
manualmente, como indicábamos en el párrafo anterior,
Para indicar el camino a las clases desde Visual J++ debemos acudir a la opción Proyecto|Propiedades
de Proyecto y seleccionar la pestaña Ruta de acceso a clases. Crearemos una nueva ruta pulsando el
botón Nueva, en ese momento debemos especificar el camino en el que se encuentran las clases que
queremos utilizar. En la figura 1 se puede ver la pantalla que nos permite gestionar las rutas de acceso
a las clases.
Figura 5
97
Programación en Java
© Grupo EIDOS
Principales paquetes del lenguaje Java
En este apartado se van a describir brevemente los paquetes más importantes que ofrece el lenguaje
Java. Estos paquetes contienen las clases principales y que más vamos a usar de la jerarquía de clases
del lenguaje. Algunos de estos paquetes no los trataremos en el curso, ya que ofrecen características
avanzadas de Java para las que necesaitaríamos otro curso de la extensión de este mismo o superior.
98
•
java.lang: este paquete contiene las clases básicas del lenguaje, este paquete no es necesario
importarlo, ya que como dijimos anteriormente, es importado automáticamente por el entorno
de ejecución. El resto de los paquetes del lenguaje Java si es necesario importarlos, cuando
deseemos utilizar sus clases, esto lo haremos mediante la sentencia import. En este paquete se
encuentra la clase Object, que sirve como raíz para la jerarquía de clases de Java, esta clase la
comentaremos en el apartado siguiente. Otras clases importantes comprendidas en este
paquete son System, que representa al sistema en el que se está ejecutando la aplicación,
Thread, que representa un hilo de ejecución y Exception que representa a las excepciones de
forma general. De todas formas a lo largo del siguiente capítulo trataremos en más
profundidad estas clases.
•
java.applet: en este paquete se encuentran todas las clases que son necesarias para construir
los programas Java más populares, los applets. En algunos casos los applets también se
denominan miniaplicaciones, término no del todo correcto, un applet puede ser una compleja
aplicación, aunque muchas veces los applets que vemos en Internet parecen estar sólo
destinados a tareas estéticas para las páginas Web. La clase principal es la clase Applet que
representa a un programa que se ejecuta en el entorno de una página Web mostrada por un
navegador. En este curso hay un capítulo completamente dedicado a los applets de Java.
•
java.awt: en este paquete se encuentran uno de los grupos de clases relacionadas con la
construcción de interfaces de usuario, es decir, clases que nos permiten construir ventanas,
botones, cajas de texto, etc. AWT son las iniciales de Abstract Window Toolkit, algo así como
herramientas abstractas para ventanas. Los interfaces de usuario se podrán mostrar igualmente
en aplicaciones Java y applets Java. Algunas de las clases que podemos encontrar en este
paquete son Button, TextField, Frame, GridLayout, Label, etc., todas ellas útiles para crear
interfaces de usuario. En este paquete encontramos un subpaquete especializado en el
tratamiento de eventos, y es el que vamos a comentar a continuación.
•
java.awt.event: este subpaquete del paquete java.awt, es el encargado de proporcionar todas
las clases que permiten realizar el tratamiento de eventos en Java. Existe una completa
jerarquía de clases para representar cada uno de los eventos que se puedan producir a partir de
la interacción del usuario con el interfaz de usuario. En el capítulo correspondiente trataremos
ampliamente el tema de los eventos dentro de la programación en Java. En este paquete
podemos encontrar clases como ActionEvent, que representa el evento que se produce cuando
pulsamos un botón o MouseEvent que representa eventos del ratón.
•
java.io; este paquete reúne todas las clases relacionadas con la entrada/salida, ya sea para
manipular ficheros, leer o escribir en pantalla, en memoria, etc. Este paquete ofrece clase
como FileReader, que representa un fichero del que se quiere leer, ByteArrayOutputStream,
representa un array que se quiere escribir en memoria. La entrada/salida en Java tiene una
serie de complejidades y particularidades que se tratarán en el capítulo correspondiente.
•
java.net: aquí encontramos una serie de clases que tienen que ver directamente con la
programación en Internet, como puede ser utilizar sockets, obtener información de URLs
(Uniform Resource Locator), manipular direcciones IP (Internet Protocol). Es decir, este
paquete tiene las clases que se encuentran relacionadas con la programación a través de la red.
© Grupo EIDOS
6. POO en Java: otros conceptos
En este paquete podemos encontrar las clases como: URL, que representa una localización de
un recurso dentro de Internet, o Socket, que representa una conexión del lado del cliente con
un servicio determinado a través de la red. A través de este paquete podremos construir
aplicaciones dentro de la arquitectura cliente servidor.
•
java.sql: a través de este paquete se nos ofrecen todas las clases necesarias para programar en
Java el acceso a bases de datos, a este conjunto de clases se le suele denominar API JDBC. El
paquete java.sql se sale de los objetivos de este curso. Aunque podemos comentar que en este
paquete se ofrecen clases como pueden ser Connection, que representa una conexión con la
base de datos, Statement, representa una sentencia SQL, Driver, representa el driver utilizado
para establecer la conexión con la base de datos.
•
javax.swing: este paquete reúne el segundo conjunto de clases que se utilizan para construir
interfaces de usuario. Los componentes que se engloban dentro de este paquete se denominan
componentes Swing, y suponen una alternativa mucho más potente que AWT para construir
interfaces de usuario muchos más complejos. Este paquete es nuevo en Java 2 y tiene un gran
número de clases: JFrame, JButton, JLabel, JOptionPane, JPanel, JApplet, etc. Este paquete
añade nuevos componentes para la construcción de interfaces de usuario que no se
encontraban presentes en el paquete java.awt, además se ofrecen nuevas características como
son el aspecto y comportamiento para distintos sistemas, ventanas de diálogo configurables,
componentes estructurados (JTable y JTree), capacidades para las funciones deshacer (undo),
potentes manipulaciones de texto, etc. Los componentes Swing se denominan componentes
ligeros ya que se encuentran completamente escritos en Java sin utilizar ningún código nativo
y no depende su aspecto ni comportamiento del sistema operativo en el que se utilicen. Este
paquete se encuentra englobado en lo que se denomina JFC (Java Foundation Classes).
•
javax.swing.event: este paquete contiene una serie de clases e interfaces relacionados con los
nuevos eventos ofrecidos por los componentes Swing, como puede ser MenuListerner o
TreeSelectionListener.
•
java.util: como su nombre indica este paquete ofrece una serie de clases que se utilizan como
utilidades para el lenguaje. Algunas de estas clases son: Date, para el tratamiento de fechas,
Random, para la generación de números aleatorios, Enumeration, para tratar un conjunto de
objetos.
•
java.beans: como ya se había comentado, en capítulos anteriores, los JavaBeans son
componentes software que permiten a los desarrolladores escribir y ofrecer componentes Java
que pueden ser utilizados por otras herramientas de desarrollo. Este paquete nos ofrece los
medios necesarios para desarrollar JavaBeans.
•
java.rmi: Remote Method Invocation (invocación remota de métodos, RMI) , permite crear
objetos cuyos métodos pueden ser invocados por otros objetos ejecutándose en otras máquinas
virtuales, incluso máquinas virtuales ejecutándose en otro host.
•
java.text: este paquete ofrece herramientas para la internacionalización de texto, tales como
formato de fecha y formato numérico.
•
java.security: este paquete y sus subpaquetes ofrecen interfaces básicos para operaciones
relativas a la seguridad, tales como autenticación, autorización, firma de datos y encriptación.
•
java.math: este paquete ofrece herramientas para manipulaciones matemáticas.
•
javax.accessibility: define una serie de contratos entre interfaces de usuario y tecnologías de
rehabilitación.
99
Programación en Java
© Grupo EIDOS
A lo largo de los diferentes capítulos del curso iremos detallando algunas de las clases más
importantes de estos paquetes.
Además de los paquetes que hemos comentado, existen una serie de subpaquetes que ofrecen unas
funcionalidades más específicas y que pasamos a exponer a continuación.
•
java.awt.dnd: este paquete ofrece las clases necesarias para implementar mecanismos Drag
and Drop dentro de interfaces de usuario, es decir, permiten arrastrar y soltar componentes del
interfaz de usuario para de esta forma intercambiar información.
•
java.awt.font: ofrece clases e interfaces para tratar y manipular fuentes.
•
java.awt.image: paquete especializado en el tratamiento y creación de imágenes.
•
java.awt.print: soporte para realizar tareas de impresión.
•
java.util.jar: ofrece clases para escribir y leer ficheros en formato JAR (Java ARchive).
•
java.util.zip: ofrece clases para escribir y leer ficheros en formato ZIP.
•
javax.sound.midi: presenta clases e interfaces para entrada/salida y síntesis de datos en en
formato MIDI (Musical Instrument Digital Interface).
•
javax.swing.border: ofrece clases e interfaces para dibujar bordes entorno a componentes
Swing.
•
javax.swing.plaf: mediante este paquete se ofrecen las clases e interfaces para implementar el
mecanismo pluggable look and feel, es decir, el aspecto y comportamiento configurable de los
componentes Swing.
•
javax.swing.table: permite manipular el componente Swing JTable, del paquete javax.swing,
que representa la estructura de una tabla.
•
javax.swing.table: otro subpaquete del paquete javax.swing, en este caso permite manipular
componentes de texto, tanto editables como no editables.
•
javax.swing.tree: permite manipular el componente Swing JTree, del paquete javax.swing,
que representa una estructura de árbol.
•
javax.swing.undo: ofrece soporte para la funcionalidad deshacer/rehacer de aplicaciones tales
como editores de texto
Como se puede comprobar Java ofrece una completa jerarquía de clases organizadas a través de
paquetes.
La clase Object
Se ha añadido este apartado dedicado a la clase Object debido a su importancia dentro de la jerarquía
de clases de Java, y representa el comportamiento básico que todos los objetos deben tener.
Como ya hemos comentado en alguna ocasión, la clase Object, se encuentra en la raíz de toda la
jerarquía de clases que forman el lenguaje Java; y por lo tanto, toda clase escrita en Java desciende de
100
© Grupo EIDOS
6. POO en Java: otros conceptos
esta clase, ya sea de forma directa, o indirectamente, ya que hereda de otra, la cual a su vez hereda de
otra, y así sucesivamente hasta llegar a la clase Object.
En aquellas clases en las que se no incluye la palabra extends en su declaración Java asume que la
clase padre es Object. Con lo cual se hereda directamente de la clase raíz Object. A continuación
vamos a comentar los distintos métodos que presenta la clase Object, ya que estos métodos los
contendrán todas las clases del lenguaje Java.
Constructor: Object()
Éste es el constructor de la clase y simplemente crea una nueva instancia de la clase Object.
Método clone()
Éste método, permite obtener un duplicado del objeto. Es decir, crea un nuevo objeto de la misma
clase e inicializa todos los atributos del objeto clonado asignándoles el mismo valor que el que tenían
en el objeto original.
Método equals()
Indica si dos objetos son iguales. Para cualesquiera valores de x e y, x.equals(y) devolverá true sí y
sólo si x e y se refieren al mismo objeto.
Método finalize()
Éste método es llamado por el recolector de basura. No hace nada, y por lo tanto para darle alguna
tarea, debe ser redefinido o sobreescrito.
Método getClass()
Determina la clase de un objeto en tiempo de ejecución.
Método hashCode()
Proporciona el código hash de un objeto, y se proporciona para darle utilidad a las hashtables.
Método notify()
Reactiva una tarea que se encuentra en estado de espera.
Método notifyAll()
Reactiva todas las tareas que se encuentren en estado de espera.
Método toString()
Crea una cadena de texto que representa al objeto, de forma que una persona pueda identificar el
objeto.
Método wait()
Pone en modo de espera una tarea.
101
POO en Java: un ejemplo. Resumen de
conceptos
Introducción
Este capítulo propone un sencillo ejemplo en el que se retoman y utilizan muchos de los conceptos de
la POO aplicados al lenguaje Java, y que hemos explicado en los tres capítulos anteriores.
Además trataremos la herramienta de desarrollo de Sun Microsystems JDK (Java Development Kit)
1.3. Utilizaremos el JDK 1.3 para generar (compilar) nuestras clases e interfaces del ejemplo.
En el siguiente capítulo trataremos las aplicaciones Java con detenimiento.
Descripción del problema
El ejemplo que vamos a presentar se trata de un videojuego que va a consistir únicamente en mostrar
una serie de mensajes en pantalla, como se podrá comprobar no se trata de nada espectacular, pero
muestra algunos de los conceptos principales de la POO aplicados al lenguaje Java.
En este videojuego debemos dirigir a una serie de extraterrestres que tienen como misión la de invadir
el planeta tierra..
A continuación se comentan las distintas clases que se van a utilizar en el ejemplo y la función de cada
una de ellas.
Programación en Java
© Grupo EIDOS
•
Extraterrestre: clase abstracta que representa a los extraterrestres de forma general. Nunca
vamos a instanciar objetos de esta clase, sino que vamos a tener dos tipos de extraterrestres
que van a heredar de esta clase.
•
Selenita: es un tipo de extraterrestre concreto, por lo tanto heredará de la clase Extraterrestre.
•
Venusiano: es otro tipo de extraterrestre que hereda también de la clase Extraterrestre.
•
DominarPlaneta: se trata de un interfaz (comportamiento) que implementarán de forma
distinta cada uno de los dos tipos de extraterrestres.
•
VideoJuego: es la clase principal o la clase de arranque y se va a encargar de poner el
videojuego en funcionamiento instanciando los objetos de las clases correspondientes y
lanzando métodos sobre ellos.
Vamos a ver a continuación como implementar las distintas clases e interfaces y como utilizar de
forma sencilla la herramienta de desarrollo JDK 1.3.
Utilizando el JDK 1.3
Ya hemos comentado en capítulos anteriores y en este mismo capítulo, que el JDK 1.3 es la última
versión del entorno de desarrollo que propone Sun Microsystems, los creadores del lenguaje Java, por
lo tanto esta es la única herramienta de desarrollo en Java necesaria para seguir el presente curso.
Aunque también comentaremos (ya hemos visto algo de ellas) otras dos herramientas, una de
Microsoft (en el siguiente capítulo), Visual J++ 6.0 y otra de Borland, JBuilder 3.5.
El JDK 1.3 recoge la última versión del lenguaje Java, es decir, lo que se denomina Java 2 o también
la plataforma Java 2. Este software es gratuito y lo podemos obtener del sitio Web de Sun
http://java.sun.com. Aunque curiosamente no lo encontraremos como JDK 1.3, sino que se incluye
dentro de otro software denominado Java 2 SDK (Software Development Kit) Standard Edition 1.3.0.
Una vez que hemos descargado el Java 2 SDK 1.3.0 procedemos a su instalación, que consiste en
ejecutar el fichero que hemos descargado (j2sdk1_3_0-win.exe, en la versión Windows) y seguir los
sencillos pasos que nos indica el programa de instalación.
Por otro lado podemos descargar también la documentación del Java 2 SDK 1.3.0, ya que supone una
referencia muy útil, tanto para expertos como para novatos del lenguaje Java. En esta documentación
se detallan todas las clases e interfaces de cada uno de los paquetes que forman parte de la
especificación del lenguaje Java.
El software se instala por defecto en el directorio jdk 1.3. Puede ser útil incluir en él la variable PATH
de Windows la ruta c:\jdk1.3\bin para poder ejecutar y compilar nuestras clases Java de forma rápida y
sencilla.
El JDK 1.3 funciona en forma de comandos, por ejemplo si queremos compilar una clase determinada
escribiremos lo que muestra el Código fuente 61 en nuestra línea de comandos.
javac NombreClase.java
Código fuente 61
104
© Grupo EIDOS
7. POO en Java: un ejemplo. Resumen de conceptos
Para escribir el código de nuestras clases utilizaremos un editor de textos como puede ser el bloc de
notas de Windows o cualquier otro editor que no introduzca caracteres especiales.
Cada clase pública debe definirse en un fichero de código fuente Java distinto, en nuestro caso vamos
a tener cinco ficheros .java, ya que todas nuestras clases van a ser públicas. Además el fichero de
código fuente debe tener el mismo nombre que la clase pública definida en el mismo, y debe coincidir
en mayúsculas y minúsculas.
Las herramientas más interesantes que ofrece el JDK y que podemos ejecutar desde la línea de
comandos son las siguientes:
•
java: es la máquina virtual de Java, es decir, el intérprete que vamos a utilizar para ejecutar
nuestras aplicaciones Java.
•
javac: es el compilador del código fuente de Java, generará los bytecodes correspondientes (en
forma de fichero .class) y nos indicará si el código fuente es correcto.
•
javadoc: mediante esta utilidad se puede generar documentación de forma automática a partir
de los comentarios de nuestro código fuente.
•
jar: utilidad que permite manejar los ficheros JAR (Java archive), ficheros comprimidos de
Java.
•
appletviewer: es el visor de applets, ejecuta los applets fuera del contexto de un navegador
Web.
Estas herramientas las iremos utilizando y comentando según se vaya desarrollando el curso.
Vamos a pasar a la implementación de las clases de nuestro ejemplo en el siguiente apartado.
Creando las clases
Vamos a comenzar por la clase abstracta Extraterrestre, que como ya hemos dicho va a representar a
los extraterrestres de manera genérica. Esta clase se va componer de los siguientes elementos:
•
Tres atributos que, para cumplir con la normativa de la POO, se declararán como privados y
que van a describir un extraterrestre. Estos atributos son del tipo primitivo de Java int y de la
clase String.
•
Varios métodos de acceso para obtener y manipular los diferentes atributos de la clase.
•
Un constructor que inicializa los valores de los atributos de la clase.
•
Y otros métodos típicos de extraterrestres.
Veamos el Código fuente 62 de la clase Extraterrestre.
public abstract class Extraterrestre{
//atributos privados
private int piernas;
private int ojos;
105
Programación en Java
© Grupo EIDOS
private String color;
//constructor
public Extraterrestre(int piernas, int ojos, String color){
this.piernas=piernas;
this.ojos=ojos;
this.color=color;
}
//métodos
public void atacarTerricola(){
System.out.println("Terrícola capturado");
}
public void teletransportar(String lugar){
System.out.println("Ahora estoy en:"+lugar);
}
//métodos de acceso
public int devuelvePiernas(){
return piernas;
}
public void asignaPiernas(int piernas){
this.piernas=piernas;
}
public int devuelveOjos(){
return ojos;
}
public void asignaOjos(int ojos){
this.ojos=ojos;
}
public String devuelveColor(){
return color;
}
public void asignaColor(String color){
this.color=color;
}
}
Código fuente 62
Como se puede ver es una clase pública y abstracta y que hereda de la clase Object, ya que no hemos
indicado que herede de otra clase. También se puede observar que en los métodos de acceso para
diferenciar a los parámetros de los atributos de utiliza la palabra reservada this. El fichero fuente tiene
el nombre Extraterrestre.java y al compilarlo con el comando javac del JDK 1.3 obtenemos el fichero
.class correspondiente. Si se produce algún error en la compilación no se generará el fichero .class
correspondiente y el JDK nos lo notificará por pantalla, como se muestra en la Figura 6.
Pasemos ahora a la clase Venusiano. Esta clase hereda de la clase Extraterrestre, y por lo tanto
presentará los mismos métodos públicos que los de su clase padre. Esta nueva clase añade un atributo
y presenta un constructor sin parámetros, pero dentro de este constructor se llama al constructor de la
clase padre a través de la palabra reservada super, para indicar las características de los venusianos.
Veamos el Código fuente 63 de la clase Venusiano.
public class Venusiano extends Extraterrestre{
//añade atributo
private int cabezas;
//constructor sin argumentos, todos los venusianos son iguales
public Venusiano(){
//debemos llamar al constructor de la clase padre
super(4,4,"Verde");
106
© Grupo EIDOS
7. POO en Java: un ejemplo. Resumen de conceptos
this.cabezas=2;
}
public void atacarTerricola(){
//si no queremos sobreescribir completamente el método de la
//clase padre
super.atacarTerricola();
//los venusianos vienen para experimentar
System.out.println("Experimentar con terrícola");
}
}
Código fuente 63
Figura 6
Como se puede comprobar esta clase sobrescribe, no de forma total, el método atacarTerricola() de la
clase Extraterrestre, se vuelve a hacer uso de la palabra reservada super.
El interfaz DominarPlaneta presenta varios métodos que se pueden utilizar en este tipo de
comportamiento, por lo tanto toda clase que quiera implementar este interfaz está obligada a dar un
contenido a los métodos que define. El interfaz DominarPlaneta tiene el Código fuente 64.
public interface DominarPlaneta
{
public abstract void aniquilarPoblacion(String planeta);
public abstract void proclamarGobierno(String planeta);
public abstract void controlarRecursos(String planeta);
}
Código fuente 64
Un interfaz se compilará de la misma forma que una clase.
107
Programación en Java
© Grupo EIDOS
La siguiente clase es la clase Selenita, que hereda también de la clase Extraterrestre y además como no
vienen en son de paz implementan el interfaz DominarPlaneta, pero uno de los métodos no desean
implementarlo, por lo que se deja el cuerpo del método vacío. Esta clase añade un nuevo atributo y
nuevos métodos. Veamos su código, en el Código fuente 65, al igual que hemos hecho con el resto de
las clases.
public class Selenita extends Extraterrestre implements DominarPlaneta{
//añade atributo
private String armamento;
public void atacarTerricola(){
//si no queremos sobreescribir completamente el método de la
//clase padre
super.atacarTerricola();
//los venusianos vienen aniquilar la raza humana
System.out.println("Aniquilar terrícola");
dispararArma();
}
//añade método
public void dispararArma(){
if (armamento.equals("cañón láser")){
System.out.println("BOUMMM");
}
else{
System.out.print("Bzzzzzz");
}
}
//constructor con parámetro, cada selenita puede tener un arma
//diferente
public Selenita(String armamento){
super(2,1,"Azul");
this.armamento=armamento;
}
//añade método de acceso
public void asignarArmamento(String armamento){
this.armamento=armamento;
}
//métodos del interfaz
public void proclamarGobierno(String planeta){
System.out.println("Eliminar gobernantes del planeta:"+planeta);
System.out.println("Gobernamos sobre el planeta "+planeta);
}
public void aniquilarPoblacion(String planeta){
System.out.println("Utilización de armas nucleares sobre el planeta
"+planeta);
}
//no interesa este método
public void controlarRecursos(String planeta){}
}
Código fuente 65
La última clase de nuestro ejemplo es la clase VideoJuego, esta clase va a ser la clase principal y se va
a encargar de instanciar los distintos objetos de las clases Selenita y Venusiano que pertenecen al
videojuego.
Esta clase presenta un único método, el método main(), que veremos en detalle en el próximo capítulo,
únicamente adelantaremos que es el método de arranque de una clase y es necesario si queremos
ejecutar una clase que representa una aplicación Java.
108
© Grupo EIDOS
7. POO en Java: un ejemplo. Resumen de conceptos
Veamos el código de la clase VideoJuego (Código fuente 66).
public class VideoJuego{
public static void main(String args[]){
//Un venusiano:
Venusiano alien=new Venusiano();
System.out.println("Es de color: "+alien.devuelveColor());
alien.teletransportar("Soria");
alien.atacarTerricola();
System.out.println();
//Un selenita
Selenita alien2=new Selenita("cañón láser");
alien2.teletransportar("Madrid");
alien2.atacarTerricola();
//utilizamos el interfaz que implementa el selenita:
alien2.aniquilarPoblacion("Tierra");
System.out.println();
}
}
Código fuente 66
Una vez compilada esta clase vamos a ejecutarla para observar el resultado de la utilización de los dos
objetos de nuestro juego, el objeto de la clase Venusiano y el objeto de la clase Selenita.
Para ejecutar una clase utilizamos el intérprete que ofrece el JDK, se trata del comando java. Para
ejecutar una clase se debe escribir en la línea de comandos la instrucción que muestra el Código fuente
67.
java NombreClase
Código fuente 67
El resultado de ejecutar la clase VideoJuego es el que muestra la Figura 7.
Figura 7
El código fuente completo de este ejemplo se puede obtener aquí.
En el próximo capítulo se tratan las aplicaciones Java con detenimiento.
109
Aplicaciones Java
Introducción
Hasta ahora hemos definido primero conceptos de Programación Orientada a Objetos, luego vimos las
particularidades del lenguaje Java, su sintaxis y también como implementa los mecanismos de la POO,
por lo tanto ya estamos en disposición de comenzar a realizar programas Java.
En este capítulo vamos a comentar todos los aspectos de uno de los tipos de programas que se puede
realizar con el lenguaje Java, las aplicaciones Java. No se debe olvidar que Java ofrece dos tipos de
programas: applets y aplicaciones. En este curso se ha dedicado un capítulo para cada tipo de
programa.
En el presente capítulo también vamos comentar y explicar como utilizar las herramientas de
desarrollo Microsoft Visual J++ 6.0 y Borland JBuilder 3.5. Pero siempre teniendo en cuenta que
vamos a generar código estándar del lenguaje Java.
Las aplicaciones Java son aplicaciones de propósito general al igual que existen en otros lenguajes
como C++ o Visual Basic, lo único que las distingue es que no vamos a generar un ejecutable como en
los otros lenguajes de programación, sino que, como ya sabrá el lector, generamos ficheros de clase
que se encuentran en bytecode y que son independientes de la plataforma.
A la hora de ejecutar nuestra aplicación Java será necesario disponer del intérprete de Java adecuado
para el sistema operativo en el que nos encontremos.
Se ha creído conveniente explicar antes las aplicaciones Java que los applets Java, debido a que un
applet se puede considerar un tipo particular de aplicación.
Programación en Java
© Grupo EIDOS
En los siguientes apartados vamos a ir comentando el proceso para crear una aplicación y también nos
adentraremos en algunos aspectos del lenguaje Java, según sea necesario.
Introducción a Visual J++ 6
En este curso no se pretende realizar un tutorial completo de la herramienta de desarrollo de Microsoft
Visual J++ 6 ni tampoco de la herramienta JBuilder 3.5 de Borland, sino que vamos a comentar
aquellos aspectos que sean de un mayor interés para la programación en Java (JBuilder 3.5 lo
trataremos en el apartado correspondiente).
En este apartado vamos a comentar los pasos a seguir para poder realizar una sencilla aplicación Java
dentro de Visual J++.
MS Visual J++ 6 se encuentra formando parte de la suite de herramientas de desarrollo Microsoft
Visual Studio 6, otras herramientas que podemos encontrar en este paquete son Visual C++ 6, Visual
Basic 6, Visual InterDev 6...
Una vez arrancado el entorno de desarrollo, seleccionando la opción correspondiente del menú de
Inicio, aparece la ventana de Nuevo Proyecto, que se puede observar en la Figura 8. Se puede
comprobar también que aparecen otras opciones correspondientes en este caso a Visual InterDev, esto
es debido a que en mi equipo de pruebas tengo instalada esta herramienta, por lo tanto esta pantalla
puede variar según los componentes que se tengan instalados de Visual Studio.
Nosotros, como es evidente, nos vamos a ocupar de los Proyectos de Visual J++. Dentro de la carpeta
Proyectos de Visual J++ existen otras tres subcarpetas, en este capítulo vamos a tratar la primera, la
subcarpeta Aplicaciones.
Figura 8
Seleccionamos la carpeta Aplicaciones, y se nos mostrarán tres tipos de aplicaciones distintas que
podemos realizar con Visual J++: Aplicación Windows, Aplicación de consola y Asistente para
aplicaciones. Vamos a comentar cada una de ellas:
•
112
Aplicación Windows: si seleccionamos este tipo de aplicación, el entorno de desarrollo de
forma automática nos creará un formulario que será el arranque de la aplicación. En este caso
© Grupo EIDOS
8. Aplicaciones Java
el interfaz que muestra el entorno de desarrollo es muy similar al de Visual Basic, es decir, un
formulario sobre el que vamos añadiendo los controles seleccionados en la barra de
herramientas correspondiente. Pero en Java y con Visual J++ no resulta tan sencillo, ya que en
este tipo de aplicaciones no se utilizan las clases de interfaz de usuario estándar de Java, es
decir, las clases del AWT (Abstract Window Toolkit) o componentes Swing, sino que se
utilizan unas clases propietarias de Microsoft denominadas WFC (Windows Foundation
Clases). Como ya comentamos al principio de este capítulo, vamos a realizar programas que
sean 100% Java, por lo tanto no utilizaremos las clases contenidas en WFC y tampoco
utilizaremos este tipo de aplicación Java. En la Figura 9 se puede ver lo amigable que resulta
el entorno ofrecido por Visual J++. Lamentamos desanimar al alumno, pero si queremos
generar código Java puro, en nuestro entorno de desarrollo nunca veremos este tipo de
aplicaciones.
Figura 9
•
Aplicación de consola: este tipo de aplicación es la que vamos a utilizar para realizar nuestras
aplicaciones Java. Representa una aplicación Java y nos ofrece un código muy básico que nos
sirve de esquema para la aplicación y que comentaremos más adelante. En este caso, no
disponemos de ningún formulario ni de ninguna barra de herramientas que nos permita ir
añadiendo los diferentes controles a nuestra aplicación, aquí todo lo tendremos que hacer a
través de código. En la Figura 10 se muestra el aspecto del entorno de desarrollo cuando se
selecciona este tipo de aplicación, como se puede ver no es tan amigable como el caso anterior
(por ejemplo, la barra de herramientas con las clases WFC se encuentra desactivada), pero
estaremos generando código 100% Java, que es lo que nos interesa.
Figura 10
113
Programación en Java
•
© Grupo EIDOS
Asistente para aplicaciones: esta opción no es una aplicación en sí, sino que es un asistente
que nos guía a través de sucesivos pasos para construir dos tipos de aplicaciones: aplicación
basada en formularios y Aplicación basada en formularios con datos. Este asistente nos
generará el código necesario y nos irá preguntando en cada paso nuestras preferencias, por
ejemplo, información para la conexión de datos, si es necesaria una barra de menú, etc.
También nos permite general para la aplicación un fichero ejecutable. Como se intuye, el
código que se genera va a utilizar las clases de Microsoft, es decir, las WFC, por lo tanto,
tampoco vamos a utilizar este asistente. En la Figura 11 se puede observar uno de los pasos
del asistente.
Figura 11
Hemos empezado a hablar de los proyectos dentro de Visual J++ pero todavía no hemos comentado
que es exactamente un proyecto y que elementos forman parte del mismo. A continuación iniciamos
esta tarea.
Un proyecto dentro de Visual J++ va a ser un contenedor que va a agrupar desde el punto de vista
organizativo todas las clases que creemos para una aplicación.
Físicamente un proyecto se corresponde con un directorio en nuestro sistema de archivos, es decir, si
tenemos el proyecto llamado Pruebas, existirá un directorio Pruebas que contendrá todos los ficheros
que formen parte del proyecto. Fundamentalmente un proyecto va a contener ficheros fuente de Java,
es decir, ficheros .java.
Un proyecto se engloba en una entidad de nivel superior denominada solución. Una solución tiene la
función de guardar referencias a proyectos, es decir, es una solución vamos a tener distintos proyectos.
Una solución no se corresponde físicamente con un directorio, como ocurría en el caso de los
proyectos, sino que se trata de un fichero .SLN que apunta a todos los proyectos que contiene.
Podemos agregar a una solución tantos proyectos como queramos.
114
© Grupo EIDOS
8. Aplicaciones Java
Una vez creado un proyecto, seleccionando la opción Proyectos en J++|Aplicaciones|Aplicación de
consola, el entorno de desarrollo genera el Código fuente 68 para nosotros, con una serie de
comentarios.
/**
* Esta clase puede tomar un número variable de parámetros
* en la línea de comandos. La ejecución del programa comienza
* con el método main(). La llamada al constructor de clase no tiene lugar a menos
que se cree un objeto del tipo 'Class1'
* en el método main().
*/
public class Class1{
/**
* Punto de entrada principal para la aplicación.
*
* @param args Matriz de parámetros pasados a la aplicación
* mediante la línea de comandos.
*/
public static void main (String[] args){
// TODO: Agregar aquí el código de inicialización
}
}
Código fuente 68
Como se puede observar, de forma automática se crea una clase pública denominada Class1, y dentro
de esta clase se declara un método llamado main(), que va a ser el método de arranque de la clase y el
protagonista de nuestro nuevo apartado.
Como el lector habrá podido observar, delante de uno de los comentarios se añade la cadena TODO:,
si a un comentario le añadimos esta cadena, aparecerá como una tarea pendiente de realizar en la lista
de tareas. Esto resulta muy útil cuando dejemos en nuestro código secciones o sentencias pendientes
de codificación, después consultando la lista de tareas sabremos que tenemos pendiente de realizar.
Para ver la lista de tareas ( que se puede observar en la Figura 12) debemos seleccionar la opción de
menú Ver|Otras Ventanas|Lista de tareas.
Figura 12
Además de añadir tareas a través de comentarios especiales de código, también las podremos añadir a
través de la lista de tareas de forma directa pulsando sobre la primera fila. La gestión de tareas a través
de la lista de tareas es bastante útil y muy fácil de utilizar.
Una vez que hemos creado una aplicación y ya la tenemos disponible, vamos a realizar con ella una
tarea muy sencilla. Vamos a mostrar el típico saludo Hola Mundo a través de la pantalla.
115
Programación en Java
© Grupo EIDOS
Como ya veremos en el siguiente apartado, el método main() se ejecuta siempre, por lo tanto vamos a
utilizar este método para mostrar el mensaje. Si eliminamos los comentarios generados por Visual J++,
el código completo sería como el Código fuente 69.
public class Class1{
public static void main (String[] args){
System.out.println("Hola Mundo");
}
}
Código fuente 69
Al escribir la sentencia anterior se habrá podido comprobar dos de las grandes ayudas que nos ofrece
Visual J++ al programador. Por un lado tenemos el intellisense, este mecanismo se habrá observado al
situar el punto delante de System. Este mecanismo nos ofrece una lista de los atributos y métodos que
tenemos disponibles, también nos informa de los parámetros que debemos pasarle a un método, etc.
La segunda ayuda es la comprobación de sintaxis que se va realizando, si se escribe alguna sentencia
errónea, Visual J++ lo indicará subrayándola en rojo.
Ahora vamos a pasar a ejecutar nuestra sencilla aplicación. Para ello debemos pulsar el botón de play
en el entorno de desarrollo. Si lo ejecutamos de esta forma prácticamente no nos da tiempo a ver el
resultado. También podemos ejecutar nuestra aplicación invocando directamente al intérprete de Java
desde la línea de comandos de MS-DOS.
El intérprete de Java que ofrece Visual J++ se denomina Jview (es el equivalente al comando java del
JDK), y para utilizarlo desde la línea de comandos utilizaremos la sintaxis:
jview NombreClase
De esta forma se ejecuta una clase determinada. Si ahora nos desplazamos dónde se ha generado el
fichero .class de nuestra aplicación y escribiremos jview Class1, se ejecutará nuestra clase y veremos
el resultado (Figura 13). Si se intenta ejecutar una clase de una aplicación Java que no posee un
método main() se producirá un error y no se ejecutará.
Si queremos lanzar nuestra aplicación a través del intérprete Jview, antes deberemos compilarla para
que se generen los ficheros .class correspondientes, que son los que contendrán los bytecodes.
Figura 13
116
© Grupo EIDOS
8. Aplicaciones Java
Para compilar una clase seleccionamos la opción de menú Generar|Generar NombreProyecto.
Podemos seleccionar generar todas las clases de un proyecto o bien todas las clases de todos los
proyectos incluidos en la solución de Visual J++.
A la hora de generar los ficheros .class se nos informa de los errores que se hayan encontrado en
nuestro código, si se producen errores, no se generarán los ficheros .class de las clases que los
contengan.
Los errores detectados durante el proceso de generación (compilación) de la clase aparecerán en la
lista de tareas, junto con su descripción. Si pulsamos sobre un error, el cursor se desplazará a la línea
del editor de código en la que se encuentra dicho error.
Vamos a comentar que es lo que sucede en nuestro ejemplo cuando ejecutamos nuestra clase, ya sea
desde el entorno de Visual J++ o desde la línea de comandos con Jview. En ambos casos se invoca al
intérprete jview, aunque en el primer caso quede oculto porque la llamada la realiza el entorno Visual
J++ por nosotros. Al intérprete se le debe facilitar el nombre de la clase principal de la aplicación, en
este caso sólo tenemos la clase Class1. Una vez indicada la clase a ejecutar el intérprete comienza su
trabajo y va ejecutando nuestro código línea a línea, comenzado por el método main().
En nuestro ejemplo sólo se ejecuta el método main(), ya que en realidad nuestra clase no hace nada, no
posee ningún atributo ni ningún método más que el método main(), lo normal es que, como veremos
en el siguiente apartado el método main() se encargue de crear una instancia de la propia clase y llame
a algunos métodos de la misma para realizar alguna función.
A lo largo del presente capítulo y de los siguientes iremos descubriendo y comentando las diferentes
características y ayudas que nos ofrece el entorno de desarrollo Visual J++ 6.
Introducción a JBuilder 3.5
En este apartado vamos a comentar la herramienta de desarrollo en Java Boland JBuilder 3.5.
Realizaremos la misma acción que en el apartado anterior, es decir, construir la sencilla aplicación
Hola Mundo.
Avisamos desde aquí que la herramienta JBuilder 3.5 tiene unos requerimientos bastantes altos en lo
que a hardware se refiere. Es necesario como mínimo un procesador Pentium II y 128 MB de RAM, y
su versión para Windows es necesario Windows NT 4.0 Server o superior.
Al igual que ocurría con Visual J++, JBuilder utiliza los proyectos para organizar y contener ficheros
fuente de Java. Vamos a crear el proyecto que va a contener nuestra sencilla aplicación de ejemplo,
para ellos seleccionamos la opción Archivo|Nuevo proyecto, y en ese momento se iniciará el asistente
de proyectos.
JBuilder incluye un asistente para proyectos que simplifica la creación de los mismos. Este asistente
configura automáticamente el marco de trabajo del proyecto y permite la introducción de datos como
pueden ser la ubicación del proyecto en disco, el autor y una descripción del mismo.
En el primer paso del asistente de proyectos indicamos el nombre del proyecto y la localización del
mismo. También podemos indicar si queremos que se genere una página HTML que va a contener las
notas relativas al proyecto. Este primer paso se puede ver en la Figura 14.
117
Programación en Java
© Grupo EIDOS
Figura 14
Si pulsamos siguiente aparecerá un formulario como el de la Figura 15, en el que podremos indicar el
título y autor del proyecto, a que empresa pertenece y también una descripción del mismo.
Figura 15
Si pulsamos Finalizar habremos terminado con este asistente y podremos comprobar que JBuilder ha
creado un fichero HTML con el mismo nombre del proyecto. Si damos doble click sobre él veremos
una página HTML con los datos indicados en el asistente del proyecto. Esta página la podemos editar
desde la vista Fuente y modificar y añadir la información que queramos, esta página HTML sirve
como documentación para el proyecto.
Cuando nos situamos en la vista Fuente, podemos ver el código HTML y en el panel inferior de la
derecha, denominado panel de estructura, podemos ver la estructura del documento HTML.
Ahora debemos añadir la clase que nos muestre el mensaje Hola Mundo por la pantalla, al igual que
hacíamos en el apartado anterior.
Para añadir una clase al proyecto seleccionamos la opción de menú Archivo|Clase nueva y en ese
momento se iniciará la ejecución del asistente de clases.
118
© Grupo EIDOS
8. Aplicaciones Java
Figura 16
Este asistente nos permite indicar una serie de características que va a tener nuestra nueva clase. Los
datos que podemos indicar al asistente, que aparece en la Figura 17, se dividen en dos grupos,
información de la clase y opciones de la misma.
Figura 17
En la información de la clase encontramos lo siguiente:
•
El paquete al que pertenece nuestra clase.
•
El nombre de la clase, que si es pública deberá coincidir con el nombre del fichero fuente de
Java.
•
La clase base, es decir, la clase de la que heredamos. En la lista desplegable aparecen tres
clases: la clase Object, que es la clase raíz de todas las clases de Java, la clase Component que
119
Programación en Java
© Grupo EIDOS
es la raíz de los componentes AWT y la clase JComponent que es la raíz de los componentes
Swing. Los componentes AWT y Swing los trataremos en próximos capítulos cuando
abordemos la construcción de interfaces de usuario en Java.
Y en opciones podemos indicar lo siguiente:
•
Pública: nuestra clase si es pública se podrá instanciar y utilizar desde cualquier otra clase.
•
Generar función Main: se generará el método main() que es el método de arranque de la clase
y lo veremos con el detenimiento que se merece en el siguiente apartado.
•
Generar constructor por defecto: nos genera el constructor por defecto de nuestra clase.
•
Modificar métodos abstractos: con esta opción se generarán implementaciones vacías de los
métodos abstractos de la clase padre de la que se hereda.
En nuestro caso vamos a indicar que la clase es pública, que hereda de la clase Object, es decir, de la
que heredan todas las clases de Java directa o indirectamente, también seleccionaremos que se genere
la función Main. Nuestra clase se va a llamar HolaMundo y no forma parte de ningún paquete. Al
pulsar Aceptar el asistente ha creado el fichero fuente de Java correspondiente (HolaMundo.java) y ha
generado el Código fuente 70.
/**
* Título: Ejemplo sencillo con JBulider<p>
* Descripción: Proyecto que muestra el mensaje Hola Mundo<p>
* Copyright: Copyright (c) Angel Esteban<p>
* Empresa: Grupo EIDOS<p>
* @author Angel Esteban
* @version 1.0
*/
public class HolaMundo {
public static void main(String[] args) {
}
}
Código fuente 70
Ahora vamos a añadir la sentencia en el método main() que va a mostrar el mensaje por pantalla, como
se puede observar JBuilder ofrece una ayuda similar al Intellisense de Visual J++, en este caso se
denomina CodeInsight.
System.out.print("Hola Mundo");
Código fuente 71
Ahora debemos compilar nuestra clase para generar el fichero .class correspondiente, para ello
acudimos a la opción de menú Proyecto|Ejecutar Make de "HolaMundo.java", al seleccionar esta
opción se compilará nuestra clase HolaMundo, sólo nos falta ejecutar nuestra aplicación d ejemplo,
para ello pulsaremos en la barra de herramientas el botón verde en forma de "play", en ese momento
aparece una pantalla (Figura 18) que nos pregunta las propiedades de ejecución del proyecto.
120
© Grupo EIDOS
8. Aplicaciones Java
Figura 18
En esta pantalla debemos indicar la clase principal del proyecto, es decir, la clase que se va a ejecutar,
para ello pulsaremos el botón Asignar y seleccionaremos la clase HolaMundo y pulsamos Aceptar.
El resultado de la ejecución de la aplicación lo veremos en el panel inferior, este panel es denominado
panel de mensajes y se puede observar en la Figura 19.
Figura 19
Si observamos el directorio en el que se encuentra el proyecto vemos los ficheros que se han generado
durante nuestro proceso de creación de la aplicación. Por un lado tenemos un fichero .jpr que es el
fichero de proyecto y que contiene todas las referencias del proyecto. También parece una página
HTML que es la página de documentación del proyecto, que ya hemos comentado con anterioridad.
Además de estos ficheros aparecen dos directorios, el directorio classes y el directorio scr. El
directorio classes contiene todos los ficheros de bytecode, es decir, los ficheros de clases .class y el
directorio src contiene los ficheros de código fuente, es decir, los ficheros .java.
Al igual que sucedía con Visual J++ podemos ejecutar las clases generadas por JBuilder también
desde la línea de comandos. JBuilder contiene la versión 1.2 del JDK (Java Development Kit), por lo
tanto para ejecutar las clases utilizaremos la herramienta java que posee el JDK 1.2. El JDK se
encuentra en el directorio c:\JBuilder35\jdk1.2.2.
121
Programación en Java
© Grupo EIDOS
El método main()
Una clase por sí sola no es nada, es decir, es un conjunto de definiciones de atributos, constantes y
métodos, pero hasta que no se instancia un objeto de la clase, no tiene una existencia real. Es similar al
tipo de datos entero, con el tipo de datos no podemos hacer operaciones, pero cuando creamos una
variable de ese tipo ya si que podremos operar con ella.
La aplicación que construyamos finalmente tendrá una clase principal que será la que utilizaremos
como parámetro cuando llamemos al intérprete de Java que interpretará y ejecutará nuestra aplicación.
Ahora bien, como ya hemos comentado una clase sin instanciar en muchos casos nos sirve de bien
poco, por lo tanto debe existir un mecanismo que de forma automática cuando ejecutemos una
aplicación cree una instancia de su clase principal.
Todas las aplicaciones Java deben tener un método main(), este es el método de arranque de la clase,
este método debe a parecer en la clase principal de la aplicación. De esta forma al iniciar la ejecución
de la aplicación lo primero que se hace es iniciar la ejecución del método main() y es en este método el
lugar en el que debemos crear una instancia de la clase principal de la aplicación o bien de otras clases
que va a utilizar la aplicación.
Por ejemplo, si la clase principal de nuestra aplicación se llama Mensajes, esta clase debe implementar
el método main(), en el que además de realizar las tareas que se estimen necesarias, se deberá crear
una instancia de la clase Mensajes. También es posible realizar instancias de otras clases secundarias
de la aplicación que vayamos a utilizar.
Para que quede más clara la finalidad el método main() vamos a retomar el ejemplo de los los
apartados anteriores y lo vamos a rescribir para que nuestra clase defina el método saludo() y
tengamos que crear una instancia de la misma para poder utilizarla. El nuevo código sería el Código
fuente 72.
public class Class1{
public void saludo(){
System.out.println("Hola Mundo");
}
public static void main (String[] args){
Class1 objClass1=new Class1();
objClass1.saludo();
}
}
Código fuente 72
En este caso además del método main(), nuestra clase ofrece el método saludo(). En la primera línea
del método main() creamos una instancia de nuestra clase Class1, es decir, un objeto de la clase
Class1. Como se puede observar aunque no definamos un constructor, lo podemos utilizar, lo que
utilizamos es el constructor por defecto, que lo poseen todas las clases.
Ahora si que estamos utilizando la POO de forma más clara, creando un objeto de nuestra propia clase
y lanzándole un método que hemos definido.
Vamos a fijarnos ahora en la declaración del método main(). El método main() siempre debe
declararse como público, estático y que no devuelve parámetros, es decir, posee los modificadores
public static void.
122
© Grupo EIDOS
8. Aplicaciones Java
El método main() se debe declarar público para que pueda ser llamado siempre desde cualquier lugar,
de esta forma es llamado por el intérprete.
Es estático porque no se lanza sobre una instancia de la clase, es decir, no hace falta crear un objeto de
la clase para lanzar el método main() sobre el, de hecho, si pudiéramos ver la llamada que realiza el
intérprete con el método main() para que arranque la ejecución de una clase sería algo así:
Clase.main().
El método main() recibe como parámetro un array de cadenas de caracteres, llamado args
normalmente (aunque se puede utilizar cualquier nombre de objeto válido), que representan los
argumentos de la línea de comandos que le podemos pasar a la clase a la hora de ejecutarse.
El primer argumento, que se encuentra lógicamente en el índice cero, hace referencia primer
parámetro que le siga al nombre de la clase que estamos ejecutando con el intérprete, y así
sucesivamente, cada parámetro en la línea de comandos se separa con un espacio en blanco.
Para manejar los parámetros de entrada, vamos a modificar el ejemplo anterior y vamos a sobrecargar
el método saludo(), es decir, vamos a seguir utilizando el mismo nombre de método pero pasándole un
parámetro. Este parámetro va a ser un objeto de la clase String (cadena de caracteres) que va a ser el
nombre de la persona a la que queremos saludar.
El nombre lo pasamos a través de la línea de comandos como un argumento, que lo recuperaremos en
nuestra clase a través del primer elemento del parámetro args del método main().
El código resultante es el Código fuente 73.
public class Class1{
public void saludo(){
System.out.println("Hola Mundo");
}
public void saludo(String nombre){
System.out.println("Hola "+nombre+" que tal...");
}
public static void main (String[] args){
Class1 objClass1=new Class1();
objClass1.saludo(args[0]);
}
}
Código fuente 73
Y a continuación, en la Figura 20, se ven tres ejemplos de ejecución de nuestra aplicación, uno de
ellos nos da un error, ya que no le hemos indicado el parámetro del nombre y nuestro método está
intentando acceder a él sin que exista, nos da una excepción de array fuera de índices. Visual J++ nos
dará entonces la oportunidad de depurar nuestra clase, cosa que de momento no vamos a hacer.
Para evitar el error anterior, podemos verificar en nuestra clase que la longitud del array de cadenas
args es mayor que cero. Si es mayor que cero, utilizamos saludo() con el nombre recuperándolo del
argumento número cero, y en caso contrario utilizamos saludo() sin parámetros.
El código sería algo como lo que muestra el Código fuente 74.
123
Programación en Java
© Grupo EIDOS
Figura 20
public class Class1{
public void saludo(){
System.out.println("Hola Mundo");
}
public void saludo(String nombre){
System.out.println("Hola "+nombre+" que tal...");
}
public static void main (String[] args){
Class1 objClass1=new Class1();
if(args.length!=0)
objClass1.saludo(args[0]);
else
objClass1.saludo();
}
}
Código fuente 74
O también tenemos la posibilidad de atrapar la excepción ArrayIndexOutOfBoundsException, es
decir, la excepción que se produce al intentar acceder al índice de un elemento que no existe dentro de
un array.
En el bloque try intentaríamos llamar a saludo() con el parámetro nombre, y en el catch, que se ejecuta
si se produce la excepción, llamamos a saludo() sin parámetros. Este nuevo código se observa en el
Código fuente 75.
public class Class1{
public void saludo(){
System.out.println("Hola Mundo");
}
public void saludo(String nombre){
System.out.println("Hola "+nombre+" que tal...");
}
public static void main (String[] args){
Class1 objClass1=new Class1();
try{
objClass1.saludo(args[0]);
}
catch(ArrayIndexOutOfBoundsException ex){
124
© Grupo EIDOS
8. Aplicaciones Java
objClass1.saludo();
}
}
}
Código fuente 75
Las aplicaciones Java son más flexibles que el otro tipo de programas Java, es decir, los applets, ya
que no ofrecen restricciones de seguridad, como veremos en el capítulo correspondiente, y además
permiten acceder al sistema en el que se están ejecutando manteniendo la portabilidad gracias a la
clase System, que es la que vamos a comentar en el apartado siguiente.
La clase System
A la clase System sólo podemos acceder si el programa Java que estamos realizando es una aplicación,
los applets Java no pueden acceder a esta clase.
La clases System nos permite acceder a una serie de información y de recursos pertenecientes al
sistema sobre el cual se está ejecutando nuestra aplicación. Se pueden realizar llamadas directas al
sistema, aunque estas se desaconsejan, ya que entonces perderíamos la característica de independencia
de la plataforma del lenguaje Java.
Para conservar la independencia de la plataforma se ofrece la clase System, es como un intermediario
entre el sistema operativo sobre el que se está ejecutando nuestra aplicación, y la propia aplicación
Java.
La clase System ya la hemos utilizado en algunos ejemplos de nuestro curso, sobre todo para mostrar
información en la pantalla, a través de su atributo out. Pero para utilizar la clase System no hemos
tenido que instanciar ningún objeto de la clase, esto es así debido a que sus atributos y métodos son
estáticos, es decir, los podemos utilizar sin tener que instanciar un objeto de la clase.
Posiblemente unos de los elementos más utilizados de la clase System dentro de las aplicaciones Java
sean los canales de entrada/salida estándar. La clase System ofrece tres canales de entrada/salida
estándar a través de tres atributos estáticos, que son los siguientes:
•
System.in: representa el canal de entrada estándar, normalmente se utiliza para leer la entrada
del usuario realizada desde el teclado.
•
System.err: representa el canal de error estándar. Si se produce un error se muestra el mensaje
en este canal. Este canal suele ser la pantalla. Si retomamos la última versión del ejemplo del
apartado anterior, podremos utilizar el atributo err de la clase System para información de la
excepción que se ha producido en el bloque try. Para ello podemos añadir al principio del
bloque catch el Código fuente 76.
System.err.println("Se ha producido la excepción: "+ex);
Código fuente 76
•
System.out: representa el canal de salida estándar, ya lo hemos utilizado en ejemplos
anteriores y suele ser la pantalla.
125
Programación en Java
© Grupo EIDOS
A través de la clase System tenemos acceso a un conjunto de propiedades del sistema, que están
definidas como pares nombre/valor. Cuando se inicia el entorno de ejecución de una aplicación las
propiedades del sistema se inicializan, para reflejar los valores actuales de todas ellas.
Las propiedades que se ofrecen por defecto son las que se muestran en la Tabla 20.
Propiedad
Explicación
"file.separator"
Separador de ficheros
"java.class.path"
Ruta de clases
"java.class.version"
Versión de las clases de Java
"java.home"
Directorio de instalación de Java
"java.vendor"
Vendedor de Java
"java.vendor.url"
URL del vendedor
"java.version"
Versión de Java
"line.separator"
Separador de líneas
"os.arch"
Arquitectura del sistema operativo
"os.name"
Nombre del sistema operativo
"os.version"
Versión del sistema operativo
"path.separator"
Separador de rutas
"user.dir"
Directorio de trabajo actual del usuario
"user.home"
Directorio de trabajo del usuario
"user.name"
Nombre de la cuenta del usuario
Tabla 20
Las aplicaciones Java tienen acceso a todas ellas, pueden leer o escribir en ellas, mientras que el
acceso de los applets Java es mucho más limitado como veremos en el capítulo dedicado a los mismos.
Para leer las propiedades del sistema se dispone de dos métodos de la clase System, getProperty() y
getProperties(). Con el primer método obtenemos el valor de una propiedad determinada, indicando
como parámetro el nombre de la propiedad de la cual se quiere recuperar el valor. Si la propiedad no
existe se devuelve null.
El método getProperties(), no tiene parámetros, y nos devuelve todas las propiedades del sistema a
través de un objeto de la clase Properties. La clase Properties que nos permite manipular las
propiedades del sistema, se encuentra formando parte del paquete java.util. Para mostrar las todas las
propiedades del sistema, se ha implementado un método cuyo código es el Código fuente 77.
126
© Grupo EIDOS
8. Aplicaciones Java
public void propiedades(){
System.getProperties().list(System.out);
}
Código fuente 77
El objeto que se devuelve de la clase Properties posee un método llamado list() que escribe todas las
propiedades en el canal de salida que se le indique por parámetro, es este caso se ha elegido la salida
estándar, es decir, la pantalla, y el resultado que he obtenido en mi equipo de pruebas es el siguiente:
-- listing properties -user.dir=C:\Work\Proyecto7
line.separator=
java.vendor.url=http://www.microsoft.com/
path.separator=;
user.home=C:\WINNT\Java
java.version=1.1
java.class.path=C:\WINNT\java\trustlib\;C:\WINNT\java...
java.home=C:\WINNT\Java
com.ms.applet.enable.serversockets=false
com.ms.windir=C:\WINNT
os.version=4.0
com.ms.sysdir=C:\WINNT\System32
java.class.version=45.3
os.name=Windows NT
os.arch=x86
awt.toolkit=com.ms.awt.Wtoolkit
user.language=es
user.timezone=ECT
user.name=aesteban
awt.appletWarning=Advertencia: ventana de subprograma
user.region=ES
browser=ActiveX Scripting Host
java.vendor=Microsoft Corp.
file.encoding.pkg=sun.io
file.separator=\
file.encoding=Cp1252
http.agent=Mozilla/4.0 (compatible; MSIE 4.01; W...
Como se puede comprobar hay muchas propiedades que no aparecen en la tabla anterior, esto es
debido a que aquí se muestran propiedades específicas de la plataforma Windows, que es la plataforma
sobre la que he realizado los ejemplos. Por lo tanto, para que la aplicación sea completamente
portable, es recomendable utilizar únicamente las propiedades del sistema especificadas en la tabla.
Para crear y añadir nuestras propias propiedades del sistema utilizaremos el método setProperties() de
la clase System. A este método se le debe pasar como parámetro un objeto Properties que se ha
inicializado para contener las diferentes propiedades. Aunque estos cambios en las propiedades no son
persistentes entre diferentes aplicaciones Java.
En el capítulo dedicado a la descripción del lenguaje Java, comentábamos que Java realizaba las tareas
de liberación de memoria de forma automática a través de un hilo de ejecución paralelo denominado
recolector de basura. Sin embargo a través de la clase System podemos forzar la terminación de
nuestros objetos.
127
Programación en Java
© Grupo EIDOS
Antes de que un objeto sea tratado por el recolector de basura, el entorno de ejecución de Java le da la
oportunidad al objeto de que libere él sus propios recursos a través del método finalize() del objeto.
Para forzar la llamada a este método se lanza el método runFinalization() de la clase System, al lanzar
este método se llamará a todos los métodos finalize() de los objetos que estén pendientes de destruirse.
El recolector de basura se ejecutará siempre que el entorno de ejecución considere necesario y le sea
posible, pero si queremos forzar la ejecución del recolector de basura lanzaremos el método gc()
(garbage collector) de la clase System.
La clase System ofrece una serie de métodos que se pueden utilizar para diversas tareas, uno de estos
métodos es arraycopy(), este método permite copiar el contenido de un array en otro, la sintaxis de
este método es:
arraycopy(arrayOrigen, indiceOrigen, arrayDestino, indiceDestino,
numeroElementos)
Como se puede ver debemos especificar los dos arrays, el de destino y el de origen, y las posiciones en
la que se inicia la copia en cada uno de ellos, y por último el número de elementos que se desea copiar
de un array a otro.
Otro método que englobado bajo el epígrafe de funcionalidades varias que ofrece la clase System es
currentTimeMillis(), que devuelve la hora y fecha actual en milisegundos desde el 1 de Enero de 1970.
El último método que vamos a comentar de la clase System es el método exit(). Al lanzar este método
terminamos con la ejecución de la aplicación actual. A este método se le pasa un parámetro que será
un entero indicando el estado de finalización, por convenio si se ha producido una finalización errónea
se le pasa –1, en caso contrario 0.
La clase Runtime
Todas las aplicaciones Java poseen una instancia de la clase Runtime que permite interactuar a la
aplicación con el entorno en el que se está ejecutando. Esta clase representa el entorno de ejecución de
nuestra aplicación que comprende la Máquina Virtual de Java y el sistema operativo sobre el que se
está ejecutando.
De forma general esta clase ofrece dos funcionalidades, por un lado la comunicación con los
componentes del entorno de ejecución, es decir, invocar funciones y obtener información, y por otro
lado la interacción con capacidades dependientes del sistema operativo.
Se debe tener cuidado al utilizar la clase Runtime, ya que podemos perder la portabilidad entre
diferentes sistemas. Por lo general no se suele utilizar esta clase.
Para obtener el entorno de ejecución actual se utiliza el método getRuntime() de la clase Runtime.
Vamos a ver un ejemplo que permite lanzar la ejecución de la calculadora de Windows.
Para lanzar la ejecución de un proceso externo dentro de una aplicación Java se lanza el método exec()
de la clase Runtime sobre el objeto devuelto por el método getRuntime(). Al método exec() se le pasa
como parámetros el nombre de la aplicación que se desea ejecutar.
El método exec() devuelve un objeto de la clase Process, que representa un proceso externo en tiempo
de ejecución y a través de este objeto se puede controlar la ejecución del proceso externo. Para esperar
a que el proceso termine su ejecución se lanza el método waitFor() de la clase Process, al lanzar este
128
© Grupo EIDOS
8. Aplicaciones Java
método se parará la ejecución de la aplicación Java principal hasta que finalice su ejecución el proceso
externo.
En nuestro caso no nos interesa esperar a que el proceso que lanzamos finalice, así que el código de
nuestra aplicación Java que ejecuta la calculadora de Windows sería como se muestra en el Código
fuente 78.
public class Class1{
public void calculadora(){
try{
Runtime.getRuntime().exec("calc.exe");
}
catch(java.io.IOException ex){}
}
public static void main (String[] args){
Class1 objClass1=new Class1();
objClass1.calculadora();
}
}
Código fuente 78
A la vista de este código se deben hacer algunos comentarios. Primero, el método exec() de la clase
Runtime lanza una excepción de la clase IOException que debemos atrapar en un bloque try-catch o
bien lanzar con la cláusula throws, en este caso se ha elegido atraparla. Segundo, la clase IOException
se encuentra dentro del paquete java.io (paquete con las clases especializadas en la entrada/salida) y
como no lo hemos importado debemos hacer referencia a esta clase a través de su nombre completo.
Al ejecutar esta aplicación, una vez que se ha lanzado la ejecución de la calculadora, la aplicación se
sigue ejecutando y finaliza la ejecución de la misma, destruyendo todos los procesos asociados, en este
caso se incluye también la calculadora, es decir, al final no podemos utilizarla.
Una solución al problema anterior es la de utilizar el método waitFor() de la clase Process, para que la
aplicación Java no finalice hasta que no se cierre la calculadora. El método calculadora() quedaría
como indica el Código fuente 79.
public void calculadora(){
try{
Process proceso=Runtime.getRuntime().exec("calc.exe");
proceso.waitFor();
}
catch(InterruptedException ex){}
catch(java.io.IOException ex){}
}
Código fuente 79
A la vista de este código se deben realizar una serie de comentarios. Para poder tener una referencia al
proceso que representa a la calculadora debemos asignárselo a un objeto de la clase Process. Aquí no
creamos ningún objeto Process, sino que recuperamos uno ya existente que es creado por el método
exec() de la clase Runtime.
129
Programación en Java
© Grupo EIDOS
Como se puede observar en el código además de atrapar la excepción IOException se debe atrapar la
excepción InterruptedException, esta nueva excepción es lanzada por el método waitFor() de la clase
Process.
Algunas consideraciones sobre Visual J++ 6
En este apartado se van a comentar una serie de puntos interesantes acerca de la herramienta de
desarrollo que estamos utilizando para programar en Java. Como ya hemos repetido, y no nos
cansaremos de hacerlo, vamos a utilizar Visual J++ de forma que se genere código 100% Java, sin
ningún añadido de Microsoft, es decir, nuestro código fuente compilará de la misma forma en Visual
J++ que en la herramienta de Sun JDK (Java Development Kit).
Antes de nada vamos a comentar los elementos más importantes que podemos observar en el entorno
de desarrollo de Visual J++.
En la parte derecha de la pantalla parece el Explorador de proyectos. Desde esta ventana podemos ver
la estructura de nuestra solución, es decir, cuantos proyectos contiene y que ficheros contiene cada uno
de los proyectos de la solución. En esta ventana podemos crear carpetas, borrar ficheros, cambiar el
nombre a los proyectos, cortar, pegar, etc., todo al estilo del explorador de Windows.
Debajo de la ventana de proyectos aparece la ventana de Propiedades, que en ningún momento vamos
a utilizar, ya que se encarga de mostrar las propiedades de los componentes WFC (Windows
Foundation Classes). Así que esta ventana la podemos cerrar.
Al lado izquierdo nos encontramos con otra ventana que posee tres pestañas, la más interesante y la
que vamos a poder utilizar es la pestaña de Esquema de clases. En esta ventana se muestra de forma
gráfica toda la jerarquía y la estructura de la clase seleccionada en el Explorador de proyectos.
La estructura de la clase seleccionada se muestra como un árbol con distintos nodos (Figura 21). En un
primer nivel se muestra un nodo con el nombre de la clase, otro nodo con el nombre del paquete en el
que nos encontramos y otro llamado Importaciones, que contiene todos los paquetes que importa
nuestra clase.
Si abrimos el nodo que posee el nombre de la clase, parecerá toda la información sobre la estructura de
la clase: las clases de las que hereda, directa o indirectamente, los atributos y métodos que se heredan,
y los atributos y métodos propios.
Cada elemento que aparece en el Esquema de clases tiene un icono para distinguir el tipo de acceso
(público, privado, paquete) y el tipo de elemento. En la Figura 21 aparece un ejemplo de clase en la
que se muestra su esquema.
El Esquema de clases es una utilidad muy potente, ya que nos permite ver de una simple pasada toda
la estructura de la clase seleccionada, además la representación gráfica elegida ayuda mucho a la hora
de identificar cada elemento.
Otra utilidad que se ofrece dentro de Visual J++ y que permite ver la estructura de las clases es el
Examinador de objetos. Para acceder a la ventana del Examinador de objetos seleccionaremos la
opción de menú: Ver|Otras ventanas|Examinador de objetos. El aspecto de esta ventana se puede ver
en la Figura 22.
130
© Grupo EIDOS
8. Aplicaciones Java
Figura 21
Desde el Examinador de objetos se ofrece toda la estructura de las clases de Java, desde la vista de
paquete hasta la primera clase en la jerarquía de herencia. También nos ofrece las clases propias de
Microsoft y extensiones de Sun, es nuestro caso sólo nos van a interesar las clases que se encuentren
dentro de los paquetes que comiencen por java. Dejamos al lector que investigue esta útil herramienta,
ya que nos proporciona una visión general de la estructura de clases de Java.
En la zona central del entorno de desarrollo nos encontramos la ventana de código, que como su
nombre indica es dónde escribimos el código fuente de nuestras clases. El editor de código nos ofrece
muchas facilidades que ya hemos comentado: intellisense, verificación de sintaxis, resaltar palabras
reservadas, etc.
Debajo del editor de código encontramos la ventana Lista de tareas, que ya hemos comentado
anteriormente y que utilizaremos para mostrar las tareas pendientes y desde ella también se nos
indicarán los errores de compilación de nuestra clase.
A modo de resumen de lo visto hasta ahora de Visual J++, en la Figura 23 se muestra el entorno de
forma general y las diferentes partes del mismo.
131
Programación en Java
© Grupo EIDOS
Figura 22
Figura 23
El entorno que nos ofrece Visual J++ es bastante amigable si lo comparamos con el ofrecido por el
JDK de Sun. Una de las bonanzas que ofrece la herramienta de Microsoft es el entorno de depuración,
que es precisamente la característica de la que nos vamos a ocupar ahora mismo.
Dentro de la barra de herramientas estándar existe un menú llamado Depuración, que tiene una gran
cantidad de opciones: paso a paso por instrucciones, paso a paso por procedimientos, establecimiento
de puntos de ruptura, ejecutar hasta el cursor, etc., es decir, todo deseable para un entorno de
depuración completo.
132
© Grupo EIDOS
8. Aplicaciones Java
Se ofrece también la posibilidad de Agregar inspección, esta opción la vamos a utilizar para ir
visualizando en la ventana de inspección los valores de la expresión que indiquemos. Por ejemplo si
deseamos utilizar esta opción en la clase que veíamos en un apartado anterior, que consistía en mostrar
un saludo por la pantalla, saludando al nombre que nos pasaban por parámetro, debemos situar un
punto de interrupción al comienzo del método main() y pulsando con el botón derecho sobre la
ventana de código fuente seleccionamos la opción del menú contextual llamada Agregar inspección.
En este caso vamos a agregar a la inspección la expresión args[0], al seleccionar agregar inspección
aparece la ventana de inspección para poder escribir la expresión. Si ejecutamos nuestra clase desde
Visual J++, se detendrá la ejecución de nuestra aplicación en el punto de interrupción y podremos
consultar en la ventana Inspección el valor de nuestra expresión que en este caso es, como aparece en
la Figura 24, Error:array access out of range, ya que no nos han pasado el argumento. Para detener la
depuración pulsaremos el botón de Stop de la barra de herramientas.
Figura 24
Si deseamos repetir la depuración pero esta vez pasando un argumento a nuestra aplicación lo haremos
de la siguiente forma: accedemos a la opción de menú Proyecto|Propiedades de proyecto, y podremos
observar las hojas de propiedades que parecen en la Figura 25.
En nuestro caso vamos a seleccionar la opción Personalizar y vamos a añadir al final de los
argumentos que utiliza Visual J++ para lanzar nuestra aplicación, el nombre al que queremos saludar,
es decir, la caja de texto argumentos quedaría así /p /cp:p "<JAVAPACKAGES>" Class1 Pepe. Si
volvemos a ejecutar nuestra aplicación la ventana de inspección mostrará el valor "Pepe".
Figura 25
133
Programación en Java
© Grupo EIDOS
Si al depurar nuestra aplicación Java se produce algún error, la herramienta Visual J++ entrará en
modo de interrupción. Aparecerá una flecha verde que nos indicará dónde se ha producido el error, y
tendremos acceso a tres ventanas, dos de ellas llamadas Automático y Locales, nos permiten consultar
los valores de los atributos de los diferentes objetos de la aplicación. Y una tercera ventana llamada
Inmediato nos permite ejecutar expresiones válidas. Para abandonar el modo de interrupción
pulsaremos el botón de Play o el de Stop, según creamos conveniente. En la Figura 26 se puede
observar el aspecto de la ventana Locales. Y en la Figura 27 se puede observar el aspecto de la ventana
Inmediato en la que se ha consultado algunos atributos de la clase actual, en este caso el título y
anchura de la ventana, y también si el tamaño de la ventana es modificable.
Figura 26
Figura 27
Como vemos la herramienta Visual J++ es bastante amigable y fácil de utilizar, pero ofrece un par de
deficiencias bastante importantes.
La primera de ellas y la más grave es que soporta únicamente la versión 1.1 del lenguaje Java, es decir,
es compatible con el JDK 1.1 pero no con el JDK 1.3, por lo tanto se puede considerar que es una
herramienta un poco desfasada y además no contempla la posibilidad de utilizar nuevas versiones del
JDK, característica que si presenta la herramienta de desarrollo JBuilder 3.5. De esta forma, podemos
utilizar Visual J++ 6 si no vamos a utilizar las características ofrecidas por la plataforma Java 2.
La segunda deficiencia es que no ofrece una herramienta que permita construir un interfaz de usuario
gráfico (GUI Graphical User Interface) de forma sencilla al estilo de Visual Basic o Delphi, es decir,
arrastrando y soltando componentes.
134
© Grupo EIDOS
8. Aplicaciones Java
Consideraciones sobre JBuilder
Este apartado tiene la misma función que el anterior, pero aplicado a JBuilder, es decir, vamos a
realizar un comentario de las características más interesantes de la herramienta así como una
descripción general del entorno.
Este entorno es más complejo que Visual J++ ya que ofrece un mayor número de funciones.
El entorno de desarrollo JBuilder proporciona una visión única en la que se permite realizar la gestión
de ficheros y proyectos, diseño visual y compilación y depuración de aplicaciones.
El entorno de JBuilder consta de una ventana general que contiene varios paneles que permiten
realizar todas estas funciones, esta ventana recibe el nombre de visualizador de aplicaciones, en la
Figura 28 se puede observar cada uno de los paneles y elementos de del visualizador de aplicaciones.
Figura 28
Vamos a comentar brevemente cada uno de los elementos señalados en la Figura 28.
•
Panel de proyectos: muestra el contenido del proyecto seleccionado en la lista desplegable de
proyectos.
•
Panel de estructura: contiene iconos, opciones de clasificación y mensajes de error. El panel
de estructura muestra la estructura del archivo seleccionado en el panel de contenido. Para un
archivo Java esta estructura aparece en forma de un árbol que muestra todos los métodos y
atributos definidos en el archivo de código fuente. A través de este panel podemos ver las
clases padre de forma muy sencilla. En el caso de ser el fichero una página HTML vemos la
estructura del documento HTML en el que se muestran las etiquetas.
•
Panel de contenido: en este panel se muestran los ficheros abiertos, cada fichero abierto
dispone de una pestaña en la parte superior que muestra el nombre del fichero, así como
pestañas en la parte inferior para las distintas vistas disponibles.
135
Programación en Java
© Grupo EIDOS
•
Panel de mensajes: en este panel se muestran los mensajes de los subsistemas, tales como
diseñadores, resultados de búsquedas, procesos de compilación, depuración o ejecución.
•
Barra de estado: ofrece información actualizada de cualquier proceso y sus resultados. Existen
dos barras de estado, la principal y la de archivo. La principal se muestra en la parte inferior
de la ventana del visualizador de aplicaciones, y la de archivo aparece en la parte inferior de
cada fichero abierto en la ventana de código fuente del panel de contenido y muestra
información específica del fichero actual.
JBuilder presenta un gran número de asistentes. Si seleccionamos la opción de menú Archivo|Nuevo,
aparece la galería de objetos, cada uno de estos objetos tiene su asistente correspondiente, el asistente
de creación de nuevas clases y de nuevos proyectos ya los vimos en el anterior apartado dedicado a
JBuilder. En la Figura 29 se puede observar la galería de objetos.
Figura 29
Si seleccionamos por ejemplo el objeto Aplicación, se lanzará el asistente para aplicaciones. Este
asistente nos va a ayudar a construir una aplicación que muestra una ventana.
En la primera pantalla de este asistente indicamos el paquete al que pertenece la aplicación y el
nombre de la clase que va a representar la aplicación (Figura 30). También podemos indicar que se
generen comentarios de forma automática.
Si pulsamos el botón Siguiente podemos especificar una serie de opciones (Figura 31) que se aplican a
una clase denominada clase de marco. Esta clase va a heredar de la clase JFrame (componente Swing
que representa una ventana), ya que se va a tratar de una aplicación representada por una ventana.
136
© Grupo EIDOS
8. Aplicaciones Java
Figura 30
Figura 31
Las opciones que se aplican a la clase de marco, estas opciones son: la posibilidad de añadir una barra
de herramientas, una barra de menú y una barra de estado. También se permite generar la típica
ventana Acerca de y centrar la ventana en la pantalla.
También podemos indicar el nombre de la clase y el título que va aparecer en la ventana.
Si pulsamos el botón Finalizar comprobaremos que el asistente ha añadido tres nuevas clases a nuestro
proyecto. La clase principal que va a representar a la aplicación, una clase que hereda de la clase
JFrame que se corresponde con la ventana principal de la aplicación y otra clase más que hereda dela
clase JDialog (un componente Swing que representa una ventana de diálogo) y que se corresponde con
el diálogo Acerca de.
En el panel de proyectos también se puede observar que se han añadido varias imágenes (fichero .gif)
que se corresponden con las utilizadas para la barra de herramientas de nuestra aplicación.
Ahora si pulsamos el botón de ejecución se compilarán todas las clases y podremos ver nuestra
aplicación que presentará un aspecto similar al de la Figura 32.
137
Programación en Java
© Grupo EIDOS
Figura 32
Para cada proyecto contenido en JBuilder podemos definir una serie de propiedades a través de la
opción Proyecto|Propiedades de proyecto. Las propiedades del proyecto controlan y definen los
siguientes aspectos: vías de acceso, ejecución, depuración, compilación, generación y estilo de código.
Vamos a comentar cada unas de las pestañas que nos permiten configurar el proyecto.
•
Vías de acceso: podemos indicar las rutas a los ficheros fuente que se utilizan y a las los
ficheros de clase que se generan, también a la documentación y a las rutas de las clases
adicionales que son necesarias para el proyecto y que no pertenecen a las estándar de la
plataforma Java 2, como pueden ser clases de terceras partes o clases desarrolladas por
nosotros mismos. En esta pestaña podemos indicar la versión del JDK de Sun que vamos a
utilizar, siendo esta característica muy interesante ya que permite actualizar JBuilder con
futuras versiones del lenguaje de Sun. El aspecto de esta pantalla se puede ver en la Figura 33.
Figura 33
138
© Grupo EIDOS
•
8. Aplicaciones Java
Ejecutar: permite especificar el entorno de ejecución del proyecto actual. Esta pantalla a su
vez contiene varias pestañas que permiten configurar las aplicaciones, applets y páginas JSP
(Java Server Pages). Para la aplicación podemos indicar que parámetros queremos utilizar,
estos parámetros los recogeremos a través del parámetro args[] del método main() (como ya
hemos visto anteriormente), también se pueden indicar los parámetros que necesitemos pasar
al intérprete de Java o Máquina Virtual (MV). Y para los applets podemos definir también los
parámetros correspondientes. La forma en que utilizan los parámetros los applets lo veremos
con detenimiento en el capítulo correspondiente. Y la opción de configuración de las páginas
JSP (similares a las páginas ASP de Microsoft pero implementadas en Java) se sale del
contenido de este curso, simplemente comentaremos que permite configurar los parámetros
del servidor que va a alojar las páginas JSP. De forma general podemos indicar si queremos
compilar el proyecto antes de ejecutarlo o compilarlo antes de depurarlo.
Figura 34
•
Depurar (Figura 35): en esta pestaña podemos definir una serie de parámetros que se tendrán
en cuenta a la hora de depurar el proyecto. Para ejecutar un proyecto en modo de depuración
pulsaremos el botón de la barra de herramientas que se encuentra al lado del play de ejecución.
Para introducir un punto de interrupción en nuestro código fuente simplemente pulsamos con
el botón derecho del ratón sobre la línea correspondiente y seleccionamos la opción Conmutar
punto de interrupción. Una vez establecido el punto de interrupción podemos configurar sus
propiedades e indicar si queremos detener la ejecución de la aplicación o no. Al iniciar la
depuración de un proyecto determinado, el depurador aparecerá en el panel inferior, el que se
había denominado panel de mensajes.
•
Compilador(Figura 36): en este apartado se configuran las opciones por defecto del
compilador, como puede ser mostrar advertencias, excluir una clase determinada, mostrar
métodos que ya se han desaconsejado, etc. Como opciones generales podemos indicar si
queremos guardar en disco los ficheros antes de compilar.
139
Programación en Java
© Grupo EIDOS
Figura 35
Figura 36
•
140
Generar (Figura 37): permite configurar el compilador IDL utilizado para generar los
esqueletos de las clases necesarias para CORBA. Este es un tema avanzado de Java que no
vamos a tratar en nuestro curso.
© Grupo EIDOS
8. Aplicaciones Java
Figura 37
•
Estilo de código (Figura 38): en esta opción podemos indicar a JBuilder como queremos que
genere el código automático. Podemos indicarle la forma de utilizar las llaves de bloques de
instrucciones, como crear las clases adaptadoras para los eventos (los eventos y las clases
adaptadoras los trataremos en el tema de interfaces de usuario dedicado a los eventos) y la
visibilidad de los atributos de las clases que crea.
Figura 38
141
Programación en Java
© Grupo EIDOS
Una de las ventajas más interesantes que tiene JBuilder es que ofrece la posibilidad de crear los
interfaces de usuario en modo de diseño arrastrando y soltando los componentes. En este aspecto
JBuilder demuestra ser una herramienta mucho más potente que Visual J++ o el JDK. Vamos a
comentar brevemente el diseñador que nos ofrece esta herramienta.
Con la ayuda de las herramientas de diseño visual de JBuilder podemos crear de forma rápida y
sencilla el interfaz de usuario de una aplicación o de un applet de Java. Para construir el interfaz de
usuario utilizaremos distintos componentes que seleccionaremos de la paleta de componentes, una vez
seleccionado el elemento deseado podemos definir los valores sus propiedades y también el código
necesario para los eventos.
En la Figura 39 se muestra un esquema de la vista de diseño dentro del entorno de JBuilder.
Figura 39
142
•
Diseñador: es la superficie de diseño utilizada para colocar y modificar los paneles y otros
componentes del interfaz de usuario como pueden ser botones, etiquetas, listas, cajas de texto,
áreas de texto, etc., para acceder al diseñador únicamente se debe seleccionar la pestaña
Diseño de la parte inferior del panel de contenido. Este diseñador se puede utilizar para clases
que hereden de la clase Continer, como puede ser JFrame, JPanel, JDialog o JApplet (las
clases utilizadas en la construcción de interfaces de usuario las veremos con detenimiento en
los capítulos dedicados al tema). JBuilder mantiene la sincronización entre el diseño visual y
el código fuente de Java, al cambiar el diseño en diseñador de interfaces se actualiza
automáticamente el código fuente, y viceversa al cambiar el código fuente el cambio se refleja
en el diseñador de interfaces.
•
Paleta de componentes: está situada encima del diseñador de interfaces y contiene
componentes visuales y no visuales, estos componentes varían según la edición de JBuilder,
en este caso se trata de la versión empresarial (Enterprise).
•
Árbol de componentes: se localiza en el panel de estructura y ofrece una vista estructurada de
todos los componentes que contiene el fichero fuente y de las relaciones que existen entre
ellos.
•
Inspector: esta ventana se utiliza para inspeccionar y definir los valores de las propiedades
(atributos) de los componentes y los métodos para el tratamiento de eventos. Los cambios
© Grupo EIDOS
8. Aplicaciones Java
realizados en el inspector se reflejan en el diseño. Para ver el inspector de un componente
basta con seleccionar el componente deseado.
Para ver el diseñador de interfaces de JBuilder en acción simplemente podemos crear una nueva
aplicación con el asistente que ya habíamos visto anteriormente, ya que la clase generada hereda de la
clase JFrame, que es un contenedor.
Además de todas estas facilidades comentadas para la creación de interfaces, JBuilder ofrece un
diseñador de menús. Para utilizar el diseñador de menús tenemos que acceder a la vista de diseño (la
aplicación creada por el asistente puede servir) y añadir un componente menú (JMenuBar) desde la
paleta de componentes. El menú no se verá en el diseñador, pero haciendo doble clic sobre el
componente de menú en el árbol de componentes, aparecerá el diseñador de menús, como se puede
observar en la Figura 40.
Figura 40
El diseñador de menús es muy fácil de utilizar y muy intuitivo, podemos ir añadiendo elementos de
menú de manera sencilla.
De momento no vamos a añadir más comentarios ni explicaciones sobre JBuilder, a lo largo del curso
la volveremos a retomar al igual que haremos con Visual J++ y el JDK, para terminar este capítulo se
deben destacar los aspectos positivos y negativos de JBuilder.
Los dos aspectos o características más positivas de JBuilder es la posibilidad de diseñar interfaces de
usuario de forma visual y que implementa la última versión del lenguaje Java, permitiendo la
posibilidad de actualizar la versión del lenguaje de forma rápida y sencilla.
Y el aspecto más negativo es la excesiva memoria que requiere el producto así como los
requerimientos de sistema operativo (Windows NT Server o superior) y los numerosos (por lo menos
en mi caso) "cuelgues" de la herramienta, así como los diversos problemas de refresco de las ventanas
y de los interfaces en general de la herramienta.
143
Interfaces de usuario en Java:
componentes AWT
Introducción
A lo largo de este capítulo y los siguientes, vamos a abordar la construcción de interfaces de usuario
en el lenguaje Java, se ha querido crear varios capítulos para tratar este tema debido a su relevancia.
El interfaz de usuario tiene una gran importancia ya que es lo que realmente va a ver y utilizar el
usuario. Un interfaz de usuario debe ser coherente, fácil de utilizar, intuitivo, atractivo, amigable,
rápido, etc., pero sobretodo lo más importante es que el usuario pueda utilizarlo correctamente y se
sienta dentro de un entorno amigable.
No vamos a teorizar sobre la construcción de interfaces de usuario, sino que vamos a explicar
simplemente como crear sencillos interfaces dentro del lenguaje Java. Trataremos las bases de la
creación de interfaces de usuario comentando las clases más relevantes que Java pone a nuestra
disposición.
Este capítulo está dedicado a la utilización de componentes AWT.
El AWT (Abstract Window Toolkit)
El AWT es un conjunto de clases que Java pone a nuestra disposición para la creación de interfaces de
usuario, como pueden ser botones, listas, cajas de texto, ventanas, etc. Todas estas clases se
Programación en Java
© Grupo EIDOS
encuentran en el paquete JAVA.AWT. El otro conjunto de clases que ofrece Java para la construcción
de interfaces de usuario se denomina Swing y lo veremos en próximos capítulos.
El AWT está presente desde la primera versión del lenguaje Java, y todavía se sigue utilizando, tanto
en la versión 2 del lenguaje como en la 1.1, debido a esto lo hemos incluido en nuestro curso. Los
componentes Swing no se pueden utilizar dentro de Visual J++ pero sí con JBuilder.
Pero el AWT además de proporcionar clases que permiten la construcción de interfaces de usuario,
también ofrece un subpaquete llamado java.awt.event que contiene las clases especializadas en el
tratamiento de eventos, como puede ser la pulsación de un botón, la selección de un elemento de una
lista, cerrar una ventana etc.
En este capítulo sólo vamos a tratar la utilización de componentes AWT, en el siguiente veremos con
detenimiento el tratamiento de eventos que realiza Java, y también los gestores de diseño que nos
permiten tener un mayor control sobre la posición de los distintos componentes y elementos del
interfaz.
Mediante los programas Java se pueden construir interfaces de usuario muy completos, similares a los
que podríamos construir con otros lenguajes de programación como Visual Basic, aunque ya
avisamos, no de forma tan sencilla.
Para poder realizar interfaces que dispongan de botones, menús, etiquetas, campos de texto, listas y
otros componentes se diseñó el Abstract Window Toolkit (AWT). El AWT ofrece lo siguiente:
•
Un conjunto completo de accesorios de interfaz de usuario y otros componentes como
ventanas, menús, botones, casillas de verificación, campos de texto, barras de desplazamiento
y listas desplegables.
•
Soporte para contenedores de interfaces de usuario para que tengan a otros contenedores
dentro de él o accesorios como pueden ser botones, listas, cajas de texto, etc.
•
Un sistema de eventos para administrar los eventos del sistema y los del usuario cuando
interacciona con componentes del AWT. Lo que vamos a ver de eventos y gestores de diseño
en el próximo capítulo se aplica tanto a componentes AWT como a componentes Swing.
•
Mecanismos para distribuir los componentes de forma que permitan un diseño de interfaz de
usuario independiente de la plataforma.
La idea fundamental que sostiene el AWT es que una ventana Java es un conjunto de componentes
anidados que empiezan desde la ventana exterior hasta el más pequeño de los componentes que
forman parte del interfaz. Esta anidación de componentes dentro de contenedores crea una jerarquía de
componentes desde la casilla de verificación más pequeña hasta la ventana general de la pantalla.
Esta jerarquía determina la distribución de elementos en la pantalla y dentro de otros elementos
además del orden en el que se muestran. Los componentes principales con los que puede trabajar el
AWT son los siguientes:
•
146
Contenedores (Container): componentes AWT genéricos que incluyen a otros componentes.
La forma más común de éstos es la clase Panel, que representa a un contenedor que puede
desplegarse en pantalla, y también la clase Window que representa de forma genérica las
ventanas . Los applets, como veremos en el capítulo correspondiente, son un tipo de panel, de
hecho, la clase Applet es una subclase o clase hija de la clase Panel.
© Grupo EIDOS
9. Interfaces de usuario en Java: componentes AWT
•
Lienzos (Canvas): es una superficie de dibujo que permite dibujar imágenes y realizar otras
operaciones gráficas.
•
Componentes de interfaz de usuario: incluyen botones, listas, menús de aparición súbita,
casillas de verificación, campos de texto y otros elementos comunes de un interfaz de usuario.
•
Componentes de construcción de ventana: incluyen ventanas, cuadros, barras de menú y
diálogos.
Como ya habíamos comentado, las clases del AWT se encuentran organizadas en el paquete
JAVA.AWT, para recordar un poco el término diremos que los paquetes Java son una manera de
agrupar clases e interfaces relacionadas, permiten que grupos modulares de clases estén disponibles
sólo cuando se necesiten.
Volviendo al AWT, sus diferentes clases conforman una jerarquía y la raíz para la mayoría de los
componentes AWT es la clase Component, que ofrece características comunes para todos los
componentes. La muestra una jerarquía parcial de las clases AWT.
Uno de los principales problemas con los que nos podemos encontrar es la portabilidad del interfaz de
usuario, es decir, conseguir que la misma información (ventanas, controles, imágenes y otros objetos)
guarden sus proporciones en todas las plataformas en las que el sistema tiene que ejecutarse. Dicho de
otro modo, que la apariencia de lo que se ve sea la misma en distintos entornos.
La solución, sin ser nada fácil, es muy similar a la que se utiliza para escribir código portable. Para
hacer que las aplicaciones funcionen bajo distintas plataformas, en lugar de compilar el código fuente
de las mismas al código máquina de un microprocesador concreto, se compila a un pseudo-código, y
se ejecuta en una máquina virtual, es necesario escribir una máquina virtual para cada entorno, pero
una vez hecho esto, todos los programas escritos para ese compilador podrán ser ejecutados en todas
aquellas plataformas para las que exista máquina virtual. Del mismo modo, es necesario crear una
interfaz independiente del medio para poder portar las aplicaciones sin perder el aspecto que les
imprimió su creador.
Figura 41
147
Programación en Java
© Grupo EIDOS
Java resuelve este problema utilizando lo sus creadores han dado en llamar "Gestores de diseño"
(Layout Managers), que trataremos con detenimiento en el capítulo correspondiente.
Utilizando los componentes del AWT
En este apartado vamos a comentar algunos de los componentes básicos que podemos encontrar dentro
del AWT. Vamos a realizar una sencilla aplicación a la que vamos a ir añadiendo diferentes
componentes.
Frame
Lo primero que vamos a añadir a nuestra aplicación es una ventana, una ventana se representa
mediante la clase Frame.
La clase Frame es un tipo de contenedor, por lo que es un descendiente de la clase Container, y
además es una ventana, por lo que hereda directamente de la clase Window.
Para que nuestra aplicación posea una ventana tenemos dos opciones, creamos un atributo que sea un
objeto de la clase Frame o bien nuestra aplicación hereda de la clase Frame. Vamos a elegir la segunda
opción, ahora nuestra aplicación va a heredar todo el comportamiento de la clase Frame. El código con
el que vamos a empezar es el que muestra el Código fuente 80.
import java.awt.*;
public class Ventana extends Frame{
public static void main (String[] args){
}
}
Código fuente 80
El Código fuente 80 merece el siguiente comentario. Para utilizar las clases de creación de interfaces
de usuario con los componentes del AWT debemos importar el paquete JAVA.AWT, como hacemos
en este caso.
Vamos a añadir un constructor a nuestra clase para que al crear un objeto se instancie de una forma
determinada, es decir, con un título y dimensiones determinadas. El constructor podría ser como indica
el Código fuente 81.
public Ventana(String titulo,int x, int y){
super(titulo);
setSize(x,y);
show();
}
Código fuente 81
148
© Grupo EIDOS
9. Interfaces de usuario en Java: componentes AWT
En el constructor de nuestra clase Ventana, llamamos con super() al constructor de la clase padre, en
este caso la clase Frame. El constructor de la clase Frame se encuentra sobrecargado, y en una de sus
versiones acepta como parámetro el título de la ventana.
Una vez lanzado el constructor se utiliza el método setSize() de la clase Frame para establecer las
dimensiones de la ventana, y por último se muestra dicha ventana con el método show() de la clase
Frame.
Una vez que ya tenemos constructor, podemos utilizar nuestra clase dentro del método main() de
nuestra aplicación como muestra el Código fuente 82.
public static void main (String[] args){
Ventana miVentana=new Ventana("Una ventana",300,150);
}
Código fuente 82
Si ejecutamos la aplicación podremos ver un objeto de nuestra clase Ventana, más o menos debe ser
como el que aparece en la Figura 42.
Figura 42
Como el lector habrá comprobado, la ventana no se puede cerrar, más tarde en el siguiente capítulo
veremos como implementar este comportamiento ala hora de tratar los eventos en el lenguaje Java.
Cursor
Si queremos modificar el aspecto del cursor del ratón en nuestra ventana utilizaremos el método
setCursor(). Este método establece el aspecto que va a tener el cursor del ratón al moverse en el área
de la ventana. Como parámetro recibe un objeto de la clase Cursor.
Esta clase representa al cursor del ratón. En el Código fuente 83 se muestra como utilizar este método,
el constructor de la clase Cursor recibe como parámetro el tipo de cursor que se quiere crear. Los tipos
de cursores se encuentran definidos como constantes de la clase Cursor.
miVentana.setCursor(new Cursor(Cursor.HAND_CURSOR));
Código fuente 83
149
Programación en Java
© Grupo EIDOS
MenuBar
Para añadir una barra de menú a nuestra ventana utilizaremos el método setMenuBar() de la clase
Frame. Este método recibe como parámetro un objeto de la clase MenuBar, que representa una barra
de menú.
Menu
Una vez creado el objeto MenuBar vamos a ir añadiéndole los menús que sean necesarios, para ello
utilizaremos el método add() de la clase MenuBar. Este método recibe como parámetro objetos de la
clase Menu.
MenuItem
A su vez cada menú (objeto de la clase Menu) va a tener elementos de menú, que se van añadiendo
con el método add() de la clase Menu. A este método se le debe pasar como parámetro objetos de la
clase MenuItem, que representarán cada uno de los elementos de menú.
CheckboxMenuItem
En nuestro ejemplo vamos a crear un nuevo método llamado añadeMenu(), que va a añadir a nuestra
clase Ventana una barra de menú con dos menús, y uno de los elementos de menús va a ser una casilla
de verificación. Este tipo de elemento de menú especial se representa mediante la clase
CheckboxMenuItem.
Vamos a modificar el constructor de nuestra clase Ventana para que llame al método añadeMenu().
Finalmente el código quedaría como aparece en el Código fuente 84.
import java.awt.*;
public class Ventana extends Frame{
public Ventana(String titulo,int x, int y){
super(titulo);
setSize(x,y);
añadeMenu();
show();
}
public void añadeMenu(){
//barra de menú
MenuBar mb;
//menús
Menu m1, m2;
//elementos de menú
MenuItem mi1_1, mi1_2;
CheckboxMenuItem mi2_1;
//se crea y establece la barra de menú
mb = new MenuBar();
setMenuBar(mb);
//se crea y añade el primer menú
m1 = new Menu("Menú 1", true);
mb.add(m1);
//se añade los elementos de menú al primer menú
mi1_1 = new MenuItem("Menú item 1_1");
m1.add(mi1_1);
150
© Grupo EIDOS
9. Interfaces de usuario en Java: componentes AWT
mi1_2 = new MenuItem("Menú item 1_2");
m1.add(mi1_2);
//se crea y añade el segundo menú
m2 = new Menu("Menú 2");
mb.add(m2);
//se crea y añade el elemento de menú del segundo menú
mi2_1 = new CheckboxMenuItem("Menú item 2_1");
m2.add(mi2_1);
}
public static void main (String[] args){
Ventana miVentana=new Ventana("Una ventana",300,150);
}
}
Código fuente 84
Y el resultado de la ejecución de la aplicación es el que aparece en la Figura 43.
Figura 43
Todas las clases adicionales utilizadas en este ejemplo: MenuBar, Menu, MenuItem y
CheckboxMenuItem pertenecen todas ellas al paquete JAVA.AWT. Estas clases presentan una
jerarquía separada del resto de las clases del AWT, todas ellas heredan de MenuComponent, su
jerarquía se muestra en la Figura 44.
Figura 44
151
Programación en Java
© Grupo EIDOS
Otro tipo de ventanas que podemos crear con el AWT son los diálogos, este tipo de ventanas se
distinguen del resto en que son dependientes de otra ventana, al destruir la ventana principal se
destruyen todos los diálogos que dependan de ella y cuando se minimiza la ventana principal, todos
los diálogos dependientes desaparecen de la pantalla.
Dialog
Los diálogos se representan mediante la clase Dialog, que ofrece además una subclase llamada
FileDialog que permite al usuario abrir y guardar ficheros.
Un diálogo puede ser modal, es decir, hasta que no se cierre no se permite interactuar al usuario con la
ventana de la que depende.
Vamos a añadir a nuestra aplicación un método que muestre un diálogo modal que dependa de la
ventana creada anteriormente en este mismo apartado. Este método se llama muestraDialogo() y posee
el Código fuente 85.
public void muestraDialogo(String titulo,int x,int y){
Dialog dialogo=new Dialog(this,titulo,true);
dialogo.setSize(x,y);
dialogo.show();
}
Código fuente 85
Como se puede observar el método muestraDialogo() recibe tres parámetros, el título de la ventana de
diálogo y las dimensiones de la misma. En la primera línea creamos un objeto de la clase Dialog, el
constructor de esta clase, en una de sus versiones, recibe tres parámetros, la referencia a la ventana a la
que va a estar relacionada, el título y un valor booleano que indica si el diálogo es de tipo modal o no.
La ventana del que depende el diálogo es nuestra propia clase, debido a esto utilizamos la referencia
this.
La llamada al método muestraDialogo() la podemos situar dentro del método main(), una vez creado
un objeto de la clase Ventana. El resultado de la ejecución de la aplicación se puede observar en la
Figura 45.
Figura 45
Como se puede observar, al ser un diálogo modal, no podemos acceder al objeto de la clase Ventana.
152
© Grupo EIDOS
9. Interfaces de usuario en Java: componentes AWT
FileDialog
Si queremos mostrar un diálogo para la elección de un fichero utilizaremos la clase FileDialog. Esta
clase hereda de la clase vista anteriormente, Dialog.
Para mostrar un diálogo de este nuevo tipo se va a utilizar el método muestraDialogoFichero() cuyo
código es el Código fuente 86.
public void muestraDialogoFichero(int x, int y){
FileDialog dialogo=new FileDialog(this);
dialogo.setSize(x,y);
dialogo.show();
}
Código fuente 86
El constructor de la clase FileDialog que se ha utilizado, recibe como parámetro la referencia a la
ventana a la que se encuentra asociado este diálogo, que sigue siendo una instancia de nuestra clase
Ventana. El aspecto de la ejecución de nuestra aplicación es el que aparece en la Figura 46.
Figura 46
Como se puede comprobar, este tipo de diálogo va a ser siempre modal, y al pulsar cualquiera de los
botones se cierra el diálogo.
Una vez comentadas algunas clases del AWT vamos a definir el concepto de contenedor de una forma
un poco más extensa y relacionándolo con lo que hemos visto hasta ahora.
Container
Un objeto de la clase Container (que dicho sea de paso, es abstracta) es un componente que puede
contener otros componentes. Permite almacenar elementos GUI (controles, otros contenedores, etc.), y
es sobre estos elementos contenidos sobre los que actúa el gestor de diseño asociado al objeto
Container, para distribuirlos sobre la superficie abarcada por el Container según las indicaciones del
programador.
153
Programación en Java
© Grupo EIDOS
Pero además, los gestores de diseño permiten que esta distribución, o mejor dicho, la apariencia de la
distribución de los componentes, se mantenga similar a través de los distintos entornos gráficos en los
que se va a ejecutar la aplicación Java. En el siguiente capítulo, dentro del apartado dedicado a los
gestores de diseño veremos los diferentes tipos y como utilizarlos en cada caso.
De la clase Container derivan las siguientes:
•
Panel: No es más que una particularización de la clase Container. Tampoco tiene
representación gráfica y generalmente se utiliza para gestionar una agrupación lógica de
elementos. El caso más típico es el del teclado de una calculadora.
•
Window: Esta clase permite la definición de "ventanas" como áreas independientes capaces de
contener otros elementos, no tienen título, ni barra de menú, ni botones de tamaño, cierre, etc.
De este modo esta clase puede utilizarse para construir ventanas que se adapten a las
peculiaridades de cada uno de entornos para los que se va a construir una máquina Java
(JVM). De esta clase, heredan las siguientes.
•
Frame: Esta es clase que utilizamos para construir lo que normalmente entendemos por
ventanas o "forms" en algunos lenguajes.
•
Dialog: Esta clase se utiliza para crear cajas de diálogo.
•
FileDialog: Esta es un caso especial de la clase Dialog, de la cual hereda. Puesto que las tareas
de manejo de ficheros (guardar, guardar como y abrir) son muy comunes en todos los
entornos, esta clase proporciona cajas de diálogo para realizar estas y otras tareas relacionadas.
Button
Ya hemos visto algunos contenedores y también la creación de menús, ahora vamos a añadir un botón
a nuestra ventana. Los botones se encuentran representados por la clase Button. Para añadir el botón
vamos a definir un atributo llamado boton dentro de nuestra clase Ventana, es decir, en la declaración
de atributos escribiremos algo como lo que aparece en el Código fuente 87.
public Button boton;
Código fuente 87
El botón lo vamos a instanciar y añadir en el constructor de la clase Ventana, añadiendo para ello las el
Código fuente 88.
boton=new Button("Un botón");
this.add(boton);
Código fuente 88
Como se puede ver en el código, el constructor de la clase Button, en una de sus dos versiones, recibe
como parámetro la etiqueta que va a mostrar el botón, pero además de instanciar el botón, se debe
añadir a la ventana.
154
© Grupo EIDOS
9. Interfaces de usuario en Java: componentes AWT
Se debe recordar que la clase Frame es un tipo de contenedor, y para añadir elementos a un
componente contenedor se debe utilizar el método add() de la clase Container. Se utiliza la referencia
this para indicar que el botón se va a añadir a nuestra ventana, aunque en realidad no es necesario, se
ha puesto para una mayor claridad. El método add() en esta versión (está sobrecargado) recibe como
parámetro la instancia del componente que se quiere añadir al contenedor.
Si probamos nuestra aplicación, vemos que se ha añadido el botón, pero es un botón enorme que
ocupa toda la superficie de nuestra ventana. Para solucionar este problema vamos a utilizar un gestor
de diseño. Los gestores de diseño (LayoutManagers) los trataremos más adelante en el siguiente
capítulo, de momento simplemente vamos a añadir una línea de código para evitar el problema
anterior. El nuevo código del constructor es el Código fuente 89.
public Ventana(String titulo,int x, int y){
super(titulo);
setSize(x,y);
añadeMenu();
setLayout(new FlowLayout());
boton=new Button("Un botón");
this.add(boton);
show();
}
Código fuente 89
La nueva línea la vamos a comentar de forma somera, como ya hemos dicho más adelante
explicaremos los gestores de diseño. La línea de código que hemos añadido permite establecer un tipo
de gestor de diseño para evitar el efecto de la extensión del botón en toda la superficie del contenedor.
El aspecto final de nuestra ventana es el de la Figura 47.
Figura 47
Un objeto botón genera un evento cada vez que lo pulsamos, generalmente lo vamos a utilizar para
que el usuario con su pulsación nos indique que desea que se realice una acción determinada. Los
eventos los veremos más en detalle en el próximo capítulo. Ahora si pulsamos el botón no pasará
nada, el evento se lanza pero no hay nadie que lo trate.
La clase Button ofrece el método setLabel() para asignar la cadena que se le pasa como argumento a la
etiqueta del botón.
Al igual que añadimos botones a nuestra aplicación podemos añadir etiquetas, cajas de texto, áreas de
texto, listas, etc. Vamos a ir viendo cada uno de estos elementos.
155
Programación en Java
© Grupo EIDOS
Label
Las etiquetas son texto estático que se suele utilizar para mostrar información y dar indicaciones al
usuario. En Java las etiquetas se representan con la clase Label. En nuestro ejemplo vamos a añadir
tres etiquetas, pero en lugar de añadirlas directamente a nuestra ventana las vamos a añadir a un objeto
de la clase Panel, para agrupar todas ellas bajo un gestor de diseño diferente al establecido para la
ventana. Para añadir las etiquetas vamos a crear un nuevo método llamado añadeEtiquetas(), cuyo
código se puede ver en el Código fuente 90.
public void añadeEtiquetas(){
Panel panel=new Panel();
panel.setLayout(new GridLayout(0,1,1,1));
Label label1=new Label();
label1.setText("Etiqueta 1");
Label label2=new Label("Etiqueta 2");
label2.setAlignment(Label.CENTER);
Label label3=new Label("Etiqueta 3");
panel.add(label1);
panel.add(label2);
panel.add(label3);
add(panel);
validate();
}
Código fuente 90
En la primera línea instanciamos el objeto de la clase Panel que va a contener las etiquetas, y a
continuación le asignamos un gestor de diseño. Luego se van creando las etiquetas, como se puede ver
en el constructor utilizado, se le pasa como parámetro el texto que van a mostrar las diferentes
etiquetas. Utilizamos el método setAligment() de la clase Label para alinear el texto de la etiqueta,
utilizando como parámetro una constante definida por la clase Label, por defecto el texto de las
etiquetas se encuentra alineado a la izquierda.
Después añadimos las etiquetas al objeto de clase Panel creado, ya que es un tipo de contenedor,
utilizamos el método add(). Una vez añadidas las etiquetas al panel, debemos añadir el panel a nuestra
ventana, al igual que hacíamos con el botón.
El método validate() que aparece al final de nuestro código es utilizado para que la ventana muestre
los cambios que se han realizado sobre ella, ya que el panel que contiene las etiquetas se ha añadido
después de mostrar la ventana. El método validate() pertenece a la clase Container, y su función es la
de actualizar el aspecto de los contenedores. El aspecto que debe tener nuestra aplicación es el de la
Figura 48.
Figura 48
156
© Grupo EIDOS
9. Interfaces de usuario en Java: componentes AWT
Los métodos más relevantes de la clase Label son setText() y getText() que asignan y devuelven el
texto de la etiqueta, respectivamente.
List, Choice
Otro elemento del AWT son las listas y las listas desplegables, en ambos casos se trata de un conjunto
de elementos que podemos seleccionar de entre varias opciones. Las listas se encuentran representadas
por la clase List y las listas desplegables se encuentran representadas por la clase Choice.
Como viene siendo costumbre vamos a crear un método que añada los dos tipos de lista a nuestra
aplicación, este método se denomina añadeListas(). Y lo implementamos como indica el Código
fuente 91.
public void añadeListas(){
List lista1=new List(3, true);
Choice lista2=new Choice();
lista1.add("Elemento 1");
lista1.add("Elemento 2");
lista1.add("Elemento 3");
lista1.add("Elemento 4");
lista1.add("Elemento 5");
add(lista1);
lista2.addItem("Elemento 1");
lista2.addItem("Elemento 2");
lista2.addItem("Elemento 3");
lista2.addItem("Elemento 4");
lista2.addItem("Elemento 5");
add(lista2);
validate();
}
Código fuente 91
Lo que procede a continuación es comentar el código anterior. El constructor de la clase List recibe
dos parámetros en la versión del método utilizada, por un lado le indicamos el número de elementos
que queremos que sean visibles, y por otro lado le indicamos si se permite la selección múltiple, es
decir, si se tiene la posibilidad de seleccionar varios elementos a la vez. El constructor de la clase
Choice, no está sobrecargado y no recibe ningún parámetro.
Una vez instanciado un objeto de la clase List vamos a ir añadiendo cada uno de los elementos que va
a contener con el método add() de la clase List, y a continuación añadimos la lista a la ventana con el
método add().
Con el objeto Choice realizamos la misma tarea. Al igual que en el ejemplo de las etiquetas, es
necesario lanzar el método validate() de la clase Container. El método añadeListas() lo llamaremos, al
igual que ocurría en los ejemplos anteriores, en el método main() de nuestra clase.
Para simplificar el aspecto de nuestra aplicación, vamos a eliminar de nuestro constructor la creación
del botón. En la Figura 49 se puede ver el nuevo aspecto de la aplicación.
Las diferencias entre los objetos List y Choice son evidentes, el objeto List permite seleccionar varios
elementos y definir el número de elementos que se desea que permanezcan visibles, sin embargo el
objeto Choice, sólo permite seleccionar un objeto y tener uno sólo visible.
157
Programación en Java
© Grupo EIDOS
Figura 49
De los métodos que ofrece la clase List se destacan los siguientes:
•
int getItemCount(): devuelve el número de elementos de una lista.
•
replaceItem(String,int): reemplaza el elemento cuyo índice se especifica en el segundo
parámetro por el texto indicado en el primer parámetro. El primer elemento de la lista posee el
índice cero.
•
removeAll(): elimina todos los elementos de la lista.
•
remove(int): elimina el elemento de la lista cuyo índice coincida con el especificado.
•
String getSelectedItem(): devuelve el elemento que se encuentra seleccionado.
•
String[] getSelectedItems(): devuelve los elementos que se encuentran seleccionados.
•
boolean isIndexSelected(int): indica si el elemento cuyo índice se pasa por parámetro se
encuentra seleccionado o no.
Y de la clase Choice podemos destacar los siguientes:
•
int getItemCount(): devuelve el número de elementos de una lista.
•
String getItem(int): devuelve el elemento cuyo índice coincide con el se pasa por parámetro.
•
select(int): selecciona el elemento cuyo índice coincide con el que se pasa por parámetro.
TextField, TextArea
Ahora vamos a pasar a ver las cajas de texto y las áreas de texto. Normalmente estos dos tipos de
controles se utilizan para obtener una entrada del usuario. La diferencia principal entre ambos es que
las cajas de texto poseen una única línea, y las áreas de texto permiten tener más de una, reconociendo
los saltos de línea, además muestra barras de scroll si el texto no es visible completamente en el área
de texto.
El AWT representa las áreas de texto mediante la clase TextArea y las cajas de texto mediante la clase
TextField. Estas dos clases que heredan de la clase TextComponent las vamos a ver a continuación.
158
© Grupo EIDOS
9. Interfaces de usuario en Java: componentes AWT
Al igual que en el caso de las listas, al ser dos componentes muy parecidos vamos a implementar un
método llamado añadeTexto() que va a añadir a nuestra ya conocida aplicación una caja de texto y un
área de texto. El método añadeTexto() es como se muestra en el Código fuente 92.
public void añadeTexto(){
TextField caja=new TextField("Texto",10);
TextArea area=new TextArea(5,10);
area.setText("Primera línea.\n");
area.append("Segunda línea.\n");
area.append("Tercera línea que es más larga que las anteriores.\n");
add(caja);
add(area);
validate();
}
Código fuente 92
El constructor de la clase TextField recibe como argumentos en una de sus versiones, ya que se
encuentra sobrecargado, el texto que va a parecer en la caja de texto y el tamaño de la caja de texto. El
constructor utilizado para instanciar el objeto TextArea recibe como parámetros dos enteros que
indican el número de filas y de columnas del área de texto.
Para asignarle texto al objeto TextArea y al objeto TextField se utiliza el método setText() pasando
como parámetro un objeto de la clase String que va a representar el texto que va a contener el área o
campo de texto.
La clase TextArea ofrece el método append() para ir añadiendo al final del texto existente en el área de
texto el nuevo texto que se le pasa por parámetro. Se puede observar que para indicar un salto de línea
utilizamos el carácter de escape especial \n. Por último añadimos la caja de texto y el área de texto a
nuestra ventana y llamamos al método validate(), al igual que en los casos anteriores.
Si llamamos al método añadeTexto() de nuestra clase Ventana, se obtendrá el resultado mostrado en la
Figura 50.
Figura 50
A continuación vamos a comentar los métodos más interesantes de la clase TextComponent, y que por
lo tanto heredan las clases TextArea y TextField:
•
String getSelectedText(): devuelve el texto que se encuentra seleccionado.
•
boolean isEditable(): devuelve verdadero si es posible editar el texto.
159
Programación en Java
© Grupo EIDOS
•
selectAll(): selecciona todo el texto.
•
select(int,int): selecciona el texto comprendido entre los índices indicados como parámetros.
Cero es el primer carácter.
•
setCaretPosition(int): indica cual va a ser la posición del cursor (el caret) dentro del texto.
•
int getCaretPosition(): devuelve la posición en la que se encuentra el cursor dentro del texto.
•
setEditable(boolean): indicamos si el texto se va a poder modificar o no, dependiendo del
argumento booleano que le facilitemos.
•
String getText(): devuelve el texto que posea el componente.
La clase TextField añade una serie de métodos a los que hereda de la clase TextComponent, los más
interesantes son:
•
setEchoChar(char): establece en la caja de texto un carácter de eco, que ocultará el contenido
real del texto de la caja, se suele utilizar el carácter ‘*’.
•
char getEchoChar(): devuelve el carácter de eco que tiene asignada la caja de texto.
Por su parte la clase TextArea añade los siguientes métodos:
•
insertText(String, int): inserta el texto especificado, en la posición indicada
•
replaceText(String, int, int): reemplaza el texto comprendido entre las posiciones indicadas
por el texto especificado.
Checkbox, CheckboxGroup
Los siguientes componentes del AWT que vamos a tratar son las opciones o casillas de verificación.
Estos componentes poseen dos estados: activado o desactivado (verdadero o falso) y se utilizan para
que el usuario elija entre una serie de opciones disponibles. La clase que representa a las opciones es
Checkbox.
Hay dos tipos de opciones, las que son independientes y se pueden activar y desactivar sin tener en
cuenta al resto, y las que forman parte de un grupo de opciones y sólo puede estar activada una de las
opciones agrupadas. Para agrupar las opciones vamos a hacer uso de la clase CheckboxGroup. Esta
clase nos asegura que de todos los objetos Checkbox que contenga, sólo uno de ellos puede estar
activado en un mismo momento.
Vamos a añadir seis de opciones, tres de ellas independientes y otras tres agrupadas y por lo tanto
dependientes y excluyentes entre sí. Los dos conjuntos de opciones las vamos a añadir en dos objetos
de la clase Panel, y a su vez añadiremos estos dos objetos a nuestra ventana.
Al igual que en todos los casos anteriores vamos a mostrar el código del método (Código fuente 93)
que añade a nuestra aplicación los nuevos componentes y acto seguido comentaremos dicho código.
public void añadeOpciones(){
Panel p1, p2;
//casillas de verificación
160
© Grupo EIDOS
9. Interfaces de usuario en Java: componentes AWT
Checkbox cb1, cb2, cb3; //opciones excluyentes
Checkbox cb4, cb5, cb6;
//objeto que va a contener al grupo de opciones
CheckboxGroup cbg;
//Primer panel con las casillas independientes
//se crean las opciones
cb1 = new Checkbox();
cb1.setLabel("Opción 1");
cb2 = new Checkbox("Opción 2",true);
cb3 = new Checkbox("Opción 3");
cb3.setState(true);
p1 = new Panel();
p1.setLayout(new FlowLayout());
p1.add(cb1);
p1.add(cb2);
p1.add(cb3); //Segundo panel con las opciones agrupadas
cbg = new CheckboxGroup();
cb4 = new Checkbox("Opción 4", cbg, true);
cb5 = new Checkbox("Opción 5", cbg, false);
cb6 = new Checkbox("Opción 6", cbg, false);
p2 = new Panel();
p2.setLayout(new FlowLayout());
p2.add(cb4);
p2.add(cb5);
p2.add(cb6);
//utilizamos otro gestor de diseño para la ventana
setLayout(new GridLayout(0, 2));
//se añaden los paneles add(p1);
add(p2);
validate();
}
Código fuente 93
En este código se han utilizado varias versiones del constructor de la clase Checkbox para instanciar
los objetos correspondientes, vamos a ver cada una de ellas.
El primer constructor es el más sencillo y no posee ningún tipo de argumentos, el segundo constructor
ofrece un parámetro para indicar mediante un objeto de la clase String la etiqueta que va a acompañar
a la opción y otro parámetro para indicar el estado de la opción activada o desactivada (true o false), y
el tercer constructor permite asignar la etiqueta de la opción correspondiente.
Para activar una opción utilizamos el método setState() de la clase Checkbox, a este método le
pasamos un valor booleano para indicar si va a estar activada o no la opción. Una vez creadas las
opciones y configuradas de forma correcta, creamos el panel que las va a contener instanciando un
objeto de la clase Panel al que aplicaremos un gestor de diseño con el método setLayout(), y a
continuación añadimos los objetos Checkbox mediante el método add() de la clase Container.
Para crear el segundo grupo de opciones y para que permanezcan agrupadas de forma excluyente es
necesario instanciar un objeto de la clase CheckboxGroup. Este objeto se utilizará como parámetro en
otra nueva versión del constructor de la clase Checkbox. En este nuevo constructor vamos a indicar la
etiqueta que acompaña a la opción, el grupo al que pertenece y su estado. Sólo uno de estos objetos
Checkbox puede crearse como activado, ya que pertenecen todos al mismo objeto CheckboxGroup.
Creamos el nuevo panel y añadimos los objetos Checkbox correspondientes, se aplica un gestor de
diseño a nuestra ventana y se añaden los dos paneles que contienen las opciones. El aspecto de la
aplicación es el de la Figura 51.
161
Programación en Java
© Grupo EIDOS
Figura 51
ScrollPane
El último de los componentes que vamos a tratar del AWT es la clase ScrollPane. Esta clase hereda de
Container, y por lo tanto es un tipo de contenedor, y como todo contenedor se va a utilizar para
albergar otros componentes. La particularidad que ofrece este tipo de contenedor es que muestra
barras de desplazamiento horizontal y vertical que nos permiten desplazar el contenido de este
contenedor.
Vamos a crear un objeto de la clase ScrollPane en nuestra ventana, para que contenga a su vez un
objeto de la clase Panel que contiene cuatro botones en una fila, de esta forma podremos ver las barras
de desplazamiento horizontal. El código que nos permite hacer esto es el Código fuente 94.
public void añadePanelScroll(){
ScrollPane panel=new ScrollPane(ScrollPane.SCROLLBARS_AS_NEEDED);
Panel otroPanel=new Panel();
otroPanel.setLayout(new GridLayout(1,4));
otroPanel.add(new Button("botón 1"));
otroPanel.add(new Button("botón 2"));
otroPanel.add(new Button("botón 3"));
otroPanel.add(new Button("botón 4"));
panel.add(otroPanel);
add(panel);
validate();
}
Código fuente 94
El constructor de la clase ScrollPane recibe como argumento una constante de la clase ScrollPane que
indica cuando se van a mostrar las barras de scroll o de desplazamiento, en este caso indicamos que se
muestren sólo cuando sea necesario. El resto del código es bastante claro, creamos un panel que va a
contener los botones y vanos añadiendo los distintos botones.
Como se puede observar, al crear los botones no los guardamos en ninguna variable, esto lo hacemos
así porque no vamos a utilizarlos más, podemos crear así los componentes del AWT a los que después
no es necesario hacer referencia.
Si lanzamos sobre una instancia de nuestra clase Ventana el método añadePanelScroll() tenemos el
resultado de la Figura 52.
162
© Grupo EIDOS
9. Interfaces de usuario en Java: componentes AWT
Figura 52
163
Interfaces de usuario en Java: gestores
de diseño y eventos
Introducción
A lo largo de este capítulo, vamos a continuar con la creación de interfaces de usuario en Java, pero en
este caso no vamos a hablar de componentes como en el capítulo anterior, sino que vamos a tratar dos
características del interfaz de usuario en los programas Java, los gestores de diseño y los eventos.
Lo que vamos a ver en este capítulo es aplicable tanto a componentes AWT como a componentes
Swing (que los veremos en el siguiente capítulo), aunque muchas de las clases que implementan los
gestores de diseño y el tratamiento de eventos se encuentran dentro del paquete JAVA.AWT y del
subpaquete java.awt.events.
Los eventos y gestores de diseño específicos de los componentes Swing los veremos en los próximos
capítulos en los que se abordará Swing, en éste veremos lo común a los dos tipos de componentes.
Gestores de diseño
Los gestores de diseño (Layout Managers) tienen una gran importancia en el leguaje Java, para poder
incorporar controles en una ventana (ya sea de una aplicación o de un applet), es necesario que estos
estén contenidos en un gestor de diseño, de otro modo no funcionarán correctamente.
Comencemos por lo tanto, viendo qué es un gestor de diseño desde tres puntos de vista diferentes:
Programación en Java
© Grupo EIDOS
•
Desde el punto de vista del lenguaje, como ya hemos comentado, un gestor de diseño es un
"estilo de visualización", es decir, es el mecanismo que el lenguaje incorpora para conseguir
crear aplicaciones que tengan la misma apariencia a través de las diferentes plataformas en las
que éstas pueden ejecutarse.
•
Desde el punto de vista de la jerarquía de clases de Java, un gestor de diseño es un interfaz, y
deriva directamente de la clase Object, que es la que se encuentra en la raíz de la jerarquía de
clases. Sin embargo, es a través de la clase Container como podemos establecer el o los
gestores de diseño que la ventana va a utilizar para implementar los componentes que ésta
manejará. El gestor de diseño es parte del Container, de hecho, siempre que se crea un
Container, se puede especificar con cual o cuales de los gestores de diseño disponibles en Java
se quiere trabajar, o dicho de otro modo, qué gestor de diseño se asignará al contenedor.
•
Desde el punto de vista operativo, un Gestor de diseño es una propiedad del contenedor, que
puede almacenar cualquier tipo de objeto que derive de la clase Container (normalmente bien
controles o bien otros gestores de diseño). El Container actúa de intermediario entre la ventana
y los componentes que él almacena pasándoles a estos los mensajes que a él le llegan desde la
ventana.
El modo de interactuar con un gestor de diseño es muy simple: basta con especificar con cual de los
disponibles queremos trabajar y añadirle componentes. Veamos esto de una forma más detenida.
Puesto que existen diferentes gestores de diseño incluidos en el lenguaje Java y puesto que además
podemos construir nuestros propios gestores de diseño, se hace necesario indicar con cual de los
disponibles deseamos trabajar. Lo que se hace pasándole al método setLayout() de la clase Container
un objeto del tipo deseado de gestor de diseño con el que se desea trabajar. Veamos, por ejemplo, la
línea de código que muestra el Código fuente 95.
this.setLayout(new FlowLayout());
Código fuente 95
El operador new devuelve un gestor de diseño del tipo FlowLayout, éste objeto es recogido por el
método setLayout() de la clase Container, quien a partir de ahora ya sabe con qué gestor deseamos
trabajar.
La clase Container dispone del método add(), que permite añadir componentes. Como ya hemos
comentado, a un objeto de la clase Container le podemos añadir bien controles o bien otros gestores de
diseño. Para ello algo tan simple como lo que aparece en el Código fuente 96.
this.add( new Button( "Uno" ) );
Código fuente 96
A continuación vamos a comentar los distintos gestores de diseño que podemos encontrar dentro del
paquete JAVA.AWT. En los distintos ejemplos se han utilizado botones para poner de manifiesto las
diferentes distribuciones que los distintos gestores de diseño hacen del espacio ya que el gestor es
invisible.
Los cinco gestores de diseño son los que se comentan a continuación.
166
© Grupo EIDOS
10. Interfaces de usuario en Java: gestores de diseño y eventos
FlowLayout
El gestor de diseño FlowLayout, además de ser el gestor de diseño por defecto de los applets (que los
trataremos en el capítulo correspondiente), es también el más simple de todos.
Este gestor, distribuye los controles de izquierda a derecha llenando el ancho del contenedor de arriba
a abajo. Es decir, cuando acaba con una fila de controles, sigue con la siguiente, actuando con los
controles como lo hace un editor de textos con las palabras. Es decir, los controles fluyen (Flow).
Para ver cada uno de los ejemplos de los distintos tipos de gestores de diseño vamos a volver a utilizar
la aplicación de ejemplo del capítulo anterior, es decir, nuestra clase Ventana. Implementaremos un
método para cada tipo de gestor de diseño. En la Figura 53 se puede ver el ejemplo del gestor FlowLayout.
Figura 53
Y el método, que será llamado desde el método main() y que permite mostrar la ventana de la Figura
53 es el que se muestra en el Código fuente 97.
public void gestorFlowLayout(){
setLayout( new FlowLayout() );
for( int n = 0; n <= 15; n++ )
add( new Button( Integer.toString(n) ) );
validate();
}
Código fuente 97
Como se puede observar, se utiliza primero el método setLayout() pasándole por parámetro una
instancia del gestor de diseño que se va a utilizar en nuestra ventana. En el bucle se van añadiendo
botones a la ventana, en este caso se utiliza el método add() de la clase Container, en la versión que ya
conocemos, es decir pasándole por parámetro la instancia del componente AWT que queremos añadir.
Como se ha puede comprobar, los componentes se van incorporando al contenedor atendiendo al
orden en el que los vamos añadiendo.
BorderLayout
Este gestor, distribuye los componentes en cinco zonas: norte, sur, este, oeste y deja una amplia zona
central para el resto de los componentes. Si a alguna de estas áreas no se le asigna un componente, el
área vacía será ocupada por alguna de las áreas restantes.
167
Programación en Java
© Grupo EIDOS
Al igual que con el gestor anterior, vamos a ver en la Figura 54 el aspecto que muestra y a
continuación veremos y comentaremos el código de nuestra clase Ventana que nos permite crear este
gestor de diseño.
Figura 54
Y el método correspondiente de la clase Ventana es el que muestra el Código fuente 98.
public void gestorBorderLayout(){
setLayout( new BorderLayout() );
add("North",new Button("Norte"));
add("South", new Button("Sur"));
add(BorderLayout.EAST,new Button("Este"));
add(BorderLayout.WEST,new Button("Oeste"));
add("Center",new Button("Centro"));
validate();
}
Código fuente 98
Aquí se puede observar que el método setLayout() se sigue utilizando de la misma forma que para el
gestor anterior, pero vemos que el método add() antepone un parámetro al componente que vamos
añadir, este parámetro es para indicar la posición del componente dentro del contenedor. También se
puede observar que hay dos formas de indicar la posición del componente, o con una cadena indicando
la situación (North, Center, South...) o con una constante definida por la clase BorderLayout.
CardLayout
Este tercer gestor permite manejar varias "fichas intercambiables", de forma tal que sólo una esté
visible a la vez y ocupando todo el área del contenedor. Es similar a los controles de pestaña o "tabs"
de Windows.
El problema que este gestor de diseño tiene es que la implementación que en Java se ha hecho de él,
no es completa, y las fichas (o tarjetas si se prefiere) no tienen la correspondiente pestaña asociada,
por lo que no existe una forma preestablecida de interactuar con los controles para cambiar de unas
fichas a otras; siendo necesario crear este mecanismo programáticamente con los inconvenientes de
pérdida de tiempo y esfuerzo que esto supone. En el capítulo siguiente veremos que los componentes
Swing ofrecen una clase llamada JTabbedPane que representa un componente en el que se pueden
elegir las distintas fichas (paneles) a través de los paneles que presenta.
En la Figura 55 se puede observar el aspecto de nuestra aplicación.
168
© Grupo EIDOS
10. Interfaces de usuario en Java: gestores de diseño y eventos
Figura 55
Y el código correspondiente se puede ver en el Código fuente 99.
public void gestorCardLayout(){
CardLayout gestor=new CardLayout();
setLayout(gestor);
add("1",new Button("Card 1"));
add("2",new Button("Card 2"));
add("3",new Button("Card 3"));
add("4",new Button("Card 4"));
add("5",new Button("Card 5"));
validate();
}
Código fuente 99
El lector puede que esté un poco confundido, debido a que en el código se añaden cinco botones, pero
sin embargo al ejecutar la aplicación sólo podemos ver el primero, esto es así porque las demás fichas
con los botones correspondientes se encuentran debajo de la primera. La clase CardLayout ofrece una
serie de métodos para mostrar las distintas fichas que conforman el gestor de diseño. Estos métodos
son:
•
first(Container): nos movemos a la primera ficha. A este método le pasamos por parámetro el
contenedor sobre el que se ha aplicado el gestor de diseño. En el ejemplo anterior deberíamos
añadir la siguiente línea: gestor.first(this).
•
next(Container): nos movemos a la siguiente ficha, si estamos en la última, volveremos a la
primera, es decir, la disposición del orden de las fichas es circular. Al igual que en el método
anterior, debemos pasar por parámetro el contenedor que al que se le ha aplicado el gestor de
diseño.
•
previous(Container): nos movemos a la ficha anterior, si estamos en la primera ficha, nos
moveremos a la última.
•
last(Container): nos movemos a la última ficha.
•
show(Container, String): mostramos una ficha específica, la ficha se indica mediante un
parámetro que va a ser un objeto de la clase String, y que coincidirá con la cadena asignada a
la ficha que queremos mostrar. Esta cadena se asigna a la ficha a la hora de añadirla al gestor
de diseño, como se puede observar en el método add(). Por ejemplo, si queremos mostrar la
tercera ficha, teniendo en cuenta el ejemplo anterior, se escribiría: gestor.show(this,"3").
169
Programación en Java
© Grupo EIDOS
Si observamos el método gestorCardLayout(), comprobamos que el gestor de diseño se ha asignado de
una forma un poco diferente, en este caso se ha creado un variable llamada gestor que va a contener la
referencia al gestor de diseño de la clase CardLayout. Esto se ha hecho así para poder tener una
referencia del gestor de diseño sobre la que podemos lanzar los métodos de la clase CardLayout vistos
anteriormente.
Los componentes se añaden a las fichas utilizando una versión del método add() que tiene como
primer parámetro el objeto String que identifica la ficha a la que se va añadir el elemento, según se van
añadiendo los diferentes componentes se van añadiendo fichas al gestor de diseño, no es obligatorio
especificar el nombre que va a identificar a la ficha, es decir podemos utilizar al método add() con un
único parámetro que va a ser el componente a añadir.
GridLayout
Este gestor permite distribuir componentes basándose en una rejilla (grid) bidimensional, haciendo
que cada componente utilice una celda de la rejilla.
Para este gestor de diseño debemos indicar el número de filas y columnas que va a presentar. Sin
embargo, los componentes que se añaden al gestor no pueden especificarse bidimensionalmente, sino
que es obligatorio añadirlos ordinalmente, es decir, primero el primero, después el segundo en la
rejilla, y así sucesivamente.
Veamos la figura que muestra este gestor de diseño (Figura 56).
Figura 56
Como se puede apreciar en el ejemplo aquí expuesto, el flujo de los componentes ha ido de izquierda a
derecha y de arriba a abajo hasta completarse la rejilla (grid). Y el código del método correspondiente
se puede ver en el Código fuente 100.
public void gestorGridLayout(){
setLayout(new GridLayout(4,3));
add(new Button("Primero"));
add(new Button("Segundo"));
add(new Button("Tercero"));
add(new Button("Cuarto"));
add(new Button("Quinto"));
add(new Button("Sexto"));
add(new Button("Séptimo"));
add(new Button("Octavo"));
add(new Button("Noveno"));
add(new Button("Décimo"));
170
© Grupo EIDOS
10. Interfaces de usuario en Java: gestores de diseño y eventos
validate();
}
Código fuente 100
El constructor de la clase GridLayout es diferente del resto de gestores, se debe especificar dos
parámetros que indican el número de filas y columnas, respectivamente, que va a presentar el gestor de
diseño.
GridBagLayout
El quinto gestor de diseño es el más potente, y es también el más complicado de manejar.
Esta clase, trabaja en conjunción con otra llamada GridBagConstraints, que se encarga de almacenar
todos los parámetros de visualización necesarios para que los componentes puedan presentarse de una
forma versátil y flexible.
Quizás una de las mayores ventajas que incorpora este gestor es la posibilidad de que un componente
abarque más de una celda, lo que le da una gran potencia. Pero esta es sólo una de las muchas ventajas
que este gestor incorpora.
En la Figura 57 se puede ver el aspecto de un gestor de diseño GridBagLayout.
Figura 57
Y como viene siendo ya costumbre, se muestra el código del método correspondiente, en el Código
fuente 101.
public void gestorGridBagLayout(){
GridBagLayout gestor=new GridBagLayout();
GridBagConstraints constantes=new GridBagConstraints();
Button boton;
setLayout(gestor);
//primer botón en la posición (0,0) (columna,fila)
//se extiende hasta la última fila
boton=new Button("Primero");
constantes.gridwidth=1;
constantes.gridheight=GridBagConstraints.REMAINDER;
constantes.fill=GridBagConstraints.BOTH;
constantes.gridx=0;
constantes.gridy=0;
gestor.setConstraints(boton,constantes);
add(boton);
171
Programación en Java
© Grupo EIDOS
//segundo botón en la posición (1,0)
boton=new Button("Segundo");
constantes.gridx= GridBagConstraints.RELATIVE;
constantes.gridheight=1;
gestor.setConstraints(boton,constantes);
add(boton);
//tercer botón en la posición (2,0), se extiende dos columnas
boton = new Button("Tercero");
constantes.gridx=2;
constantes.gridwidth=2;
gestor.setConstraints(boton,constantes);
add(boton);
//cuarto botón en la posición (4,0)
boton=new Button("Cuarto");
constantes.gridx=4;
constantes.gridwidth=1
gestor.setConstraints(boton,constantes);
add(boton);
//quinto botón en la posición (5,0), se extiende 3 filas
boton = new Button("Quinto");
constantes.gridx=5;
constantes.gridheight=3;
gestor.setConstraints(boton,constantes);
add(boton);
//sexto botón, en la posición (1,1), se extiende 4 columnas
boton=new Button("Sexto");
constantes.gridx=1;
constantes.gridy++;
constantes.gridheight=1;
constantes.gridwidth=4;
gestor.setConstraints(boton,constantes);
add(boton);
//séptimo botón en la posición (1,2)
boton=new Button("Séptimo");
constantes.gridy++;
constantes.gridx=1;
constantes.gridwidth=1;
gestor.setConstraints(boton,constantes);
add(boton);
//octavo botón situado en la posición (2,2) se extiende 3 columnas
boton = new Button("Octavo");
constantes.gridx=2;
constantes.gridwidth=3;
gestor.setConstraints(boton, constantes );
add(boton);
//noveno botón en la posición (1,3) se extiende hasta la última
//columna
boton = new Button("Noveno");
constantes.gridx=1;
constantes.gridy++;
constantes.gridwidth=GridBagConstraints.REMAINDER;
gestor.setConstraints(boton,constantes);
add(boton);
validate();
}
Código fuente 101
Creamos el gestor de diseño GridBagLayout y también un objeto GridBagConstraints, el objeto
GirdBagConstraints, que en el código hemos llamado constantes, indica al gestor de diseño
GridBagLayout cómo se debe comportar.
Al gestor de diseño le indicamos el objeto GridBagConstraints que tiene asociado a través del método
setConstraints() de la clase GridBagLayout. A este método se le pasa como parámetros el componente
172
© Grupo EIDOS
10. Interfaces de usuario en Java: gestores de diseño y eventos
que se va añadir y se le va a aplicar lo definido por el objeto GridBagConstraints, y el propio objeto
GridBagConstraints.
El objeto GridBagConstraints lo podemos utilizar para ir añadiendo distintos componentes al
contenedor al que se le ha asignado el gestor de diseño, en nuestro ejemplo hemos ido reutilizando y
modificando los aspectos que nos convenían en cada caso.
Para definir el comportamiento que debe tener cada control al añadirse al contenedor, la clase
GridBagConstraints tiene una serie de atributos que permiten un gran control sobre cómo se sitúa cada
componente en el contenedor al que se le ha asignado el gestor de diseño GridBagLayout.
Los atributos más relevantes de la clase GridBagConstraints son los siguientes:
•
gridx, gridy: especifica las coordenadas en las que se va a situar el componente, de esta forma
la primera columna sería gridx=0 y la primera fila gridy=0. Para indicar que el componente se
va a situar a la derecha (para gridx) o debajo (para gridy) del último componente que se ha
añadido al contenedor utilizaremos la constante RELATIVE de la clase GridBagConstraints,
además, este es el valor por defecto.
•
gridwidth, gridheight: a través de estos dos atributos indicamos cuantas columnas y cuantas
filas, respectivamente, va a ocupar el componente. El valor por defecto en ambos casos es uno.
Para especificar que el componente se extienda hasta la última columna (para gridwidth) o
hasta la última fila (para gridheight) se utiliza la constante REMAINDER de la clase
GridBagConstraints.
•
fill: cuando el área en la que se va a mostrar el componente es mayor que el tamaño
especificado del componente, podemos determinar mediante este atributo si se va a
redimensionar el componente. Este atributo acepta como valores válidos varias constantes de
la clase GridBagConstraints. Por defecto el valor de esta propiedad es NONE, es decir, no se
redimiensionan los componentes, si queremos que se redimensione de forma horizontal o de
forma vertical utilizaremos las constantes HORIZONTAL y VERTICAL, respectivamente. Y
si queremos que se redimensione el componente de forma completa utilizaremos la constante
BOTH.
En el Código fuente 101 se puede observar la utilización de todos estos atributos, para seguir el código
se deben tener en cuenta los comentarios y fijarnos en el aspecto que ofrece al final nuestro
contenedor.
Con este gestor de diseño finalizamos el apartado dedicado a los mismos, ahora vamos a pasar a un
apartado que tiene una gran importancia dentro del tema de la construcción de interfaces de usuario, es
el apartado dedicado al tratamiento de eventos en Java.
Veremos como reaccionar ante eventos generados por la interacción del usuario con el interfaz de
usuario, como pueden ser pulsaciones de botón, selección de elementos de menú, selección de listas,
cambios en cajas de texto, cierre de una ventana, movimientos del ratón, etc.
Tratamiento de eventos en Java
En apartados y capítulos anteriores hemos estado viendo como construir interfaces de usuario a partir
de los componentes ofrecidos por el paquete JAVA.AWT, pero estos componentes por sí solos no
hacen mucho, para tener un interfaz de usuario completo es necesario que el usuario se comunique con
nuestra aplicación, y esta comunicación se produce a través del interfaz de usuario mediante el
tratamiento de eventos.
173
Programación en Java
© Grupo EIDOS
Como veremos a lo largo de este apartado, el lenguaje Java dentro de su paquete java.awt.event nos
ofrece una completa jerarquía de clases cuya función es la de representar los distintos tipos de eventos
que podemos tratar desde un interfaz de usuario.
El modelo de eventos que ofrece Java es denominado modelo de delegación de eventos (Delegation
Event Model). Los diferentes tipos de eventos se encuentran encapsulados dentro de una jerarquía de
clases que tienen como raíz a la clase java.util.EventObject.
La idea fundamental del modelo de eventos de Java es que un evento se propaga desde un objeto
"fuente" (source) hacia un objeto "oyente" (listener) invocando un método en el oyente al que se le
pasa como parámetro una instancia de la clase del evento que representa el tipo de evento generado.
Este método será el que trate al evento y actúe en consecuencia.
Los eventos son representados por toda una jerarquía de clases de eventos. Esta jerarquía de eventos se
muestra en la Figura 58, cada una de las líneas significa que la clase que se encuentra en la parte
inferior de la línea hereda de la clase que se encuentra en la parte superior. Se debe aclarar que las
clases en las que no aparece el nombre de su paquete pertenecen todas al paquete dedicado a los
eventos, java.awt.event.
Figura 58
Como ya hemos adelantado, en el tratamiento de eventos dentro del lenguaje Java hay dos entidades
bien diferenciadas y que son las necesarias para que se produzca el tratamiento de eventos en Java: la
fuente del evento y el oyente del mismo. La fuente va a ser el componente que genere el evento y el
oyente el componente o clase que lo trate.
Desde el punto de vista de la jerarquía de clases de Java, un oyente es un objeto que implementa un
interfaz java.util.EventListener específico, de esta clase raíz es de la que heredan todos los interfaces
del tipo <Evento>Listener. Implementará el interfaz que se corresponda con el tipo de evento que se
desea tratar en el oyente. Un interfaz EventListener define uno o más métodos que serán invocados
por la fuente del evento en respuesta a un tipo de evento específico.
174
© Grupo EIDOS
10. Interfaces de usuario en Java: gestores de diseño y eventos
Las características que debe tener un oyente es que debe implementar el interfaz que se corresponda
con el evento que desea tratar y debe registrarse como oyente de la fuente de eventos que va a disparar
los eventos a tratar. Cualquier tipo de objeto se puede registrar como un "oyente" de eventos. Estos
oyentes sólo reciben notificaciones de los eventos en los que están interesados, es decir, los eventos
que provienen de la fuente para cual se han registrado.
Una fuente de eventos es un objeto que origina o "dispara" eventos. La fuente define los eventos que
van a se emitidos ofreciendo una serie de métodos de la forma add<Tipo de Evento>(), que son usados
para registrar oyentes específicos para esos eventos. A los métodos add<Tipo de Evento>() se les pasa
por parámetro una instancia del oyente que va a tratar los eventos que genere la fuente.
Todos estos conceptos que estamos introduciendo acerca del tratamiento de eventos en Java, se verá
mucho más claro cuando se muestren algunos ejemplos más adelante.
Así por ejemplo, si tenemos el siguiente GUI: un botón y una caja de texto dentro de una ventana, y
queremos que al pulsar el botón aparezca un mensaje en la caja de texto, el oyente será la ventana que
contiene al botón y a la caja de texto, y la fuente será el botón que lanzará el evento correspondiente.
Este modelo de eventos ofrece las siguientes características:
•
Ofrece un estructura robusta para soportar programas Java complejos.
•
Es simple y fácil de aprender.
•
Ofrece una clara separación entre el código de la aplicación y del interfaz de usuario, en lo que
al tratamiento de eventos se refiere.
•
Facilita la creación de un código de tratamiento de eventos robusto y menos propenso a
errores.
Un oyente en lugar de implementar un interfaz, puede utilizar una clase que herede de una clase
adaptadora de eventos del paquete java.awt.event. Las clases adaptadoras permiten sobrescribir
solamente los métodos del interfaz en los que se esté interesado. Así por ejemplo, si queremos atrapar
un click del ratón, en lugar de implementar el interfaz MouseListener, heredamos de la clase
adaptadora de los eventos de ratón, es decir de la clase MouseAdapter, solamente deberemos
sobrescribir el método mouseClicked().
Esto es posible debido a que las clases adaptadoras implementan el interfaz correspondiente y
simplemente tienen implementaciones vacías de todos los métodos del interfaz EventListener. De esta
forma se consigue un código más claro y limpio. Estas clases se suelen utilizar cuando se quiere hacer
uso de un interfaz muy complejo del que sólo interesan un par de métodos.
Dentro de la jerarquía de clases de Java hay una serie de clases para representar todos los eventos y
otra serie de interfaces que definen una serie de métodos que deben implementar las clases que van a
tratar los eventos, es decir, lo que hemos llamado oyentes. Otras clases que también hemos comentado
y que se utilizan dentro del tratamiento de eventos en Java son las clases adaptadoras. Las clases
adaptadoras las utilizaremos para simplificar nuestro código, ya que implementan de forma vacía
todos los métodos de un interfaz de tratamiento de eventos determinado.
Cada conjunto de eventos tiene asociado un interfaz, y como ya hemos dicho cada uno de estos
interfaces declara una serie de métodos para cada uno de los eventos lógicos asociados al tipo de
evento de que se trate.
175
Programación en Java
© Grupo EIDOS
Cada componente del AWT genera un tipo de eventos distinto, que está representado mediante una
clase en el paquete java.awt.event.
En la Tabla 21 y en la Tabla 22 se indica que eventos lanza cada clase perteneciente a los
componentes del AWT. En las filas aparecen las clases de los componentes y en las columnas las
clases de los eventos que pueden lanzar.
ActionEvent
Adjustment Component ContainerEvent
Event
Event
FocusEvent
X
X
X
Canvas
X
X
Checkbox
X
X
Choice
X
X
Component
X
X
Container
X
X
X
Dialog
X
X
X
Frame
X
X
X
Label
X
X
X
X
Button
CheckBoxMenuItem
List
X
MenuItem
X
X
Panel
X
Scrollbar
X
X
X
Scrollpane
X
TextArea
X
X
TextComponent
X
X
X
X
TextField
X
X
Window
Tabla 21
176
X
X
X
X
X
© Grupo EIDOS
10. Interfaces de usuario en Java: gestores de diseño y eventos
ItemEvent KeyEvent
MouseEvent
TextEvent
WindowsEvent
Button
X
X
Canvas
X
X
X
X
X
X
Component
X
X
Container
X
X
Dialog
X
X
X
Frame
X
X
X
Label
X
X
X
X
Panel
X
X
Scrollbar
X
X
Scrollpane
X
X
TextArea
X
X
X
TextComponent
X
X
X
TextField
X
X
X
Window
X
X
Checkbox
X
CheckBoxMenuItem
X
Choice
X
List
X
MenuItem
X
Tabla 22
Los interfaces que definen los métodos de cada evento van a ser utilizados por los oyentes, o bien
implementándolos directamente o bien a través de las clases adaptadoras. Y las clases de los eventos
se van a utilizar para obtener información del evento que se ha producido, el cual se pasará como
parámetro a los métodos del interfaz correspondiente.
En la Tabla 23 se muestra una relación de interfaces con sus métodos correspondientes y la clase
adaptadora que le corresponde.
177
Programación en Java
© Grupo EIDOS
Interfaz
Métodos
Clase Adaptadora
ActionListener
AdjustmentListener
ComponentListener
actionPerformed(ActionEvent)
----ComponentAdapter
adjustmentValueChanged(AdjustmentEvent)
componentHidden(ComponentEvent)
componentMoved(ComponentEvent)
componentResized(ComponentEvent)
componentShown(ComponentEvent)
ContainerListener
componentAdded(ContainerEvent)
componentRemoved(ContainerEvent)
ContainerAdapter
FocusListener
focusGained(FocusEvent) focusLost(FocusEvent)
FocusAdapter
ItemListener
KeyListener
ItemStateChanged(ItemEvent)
KeyPressed(KeyEvent) keyReleased(KeyEvent)
keyTyped(KeyEvent)
--KeyAdapter
MouseListener
mouseClicked(MouseEvent)
mouseEntered(MouseEvent)
mouseExited(MouseEvent)
mousePressed(MouseEvent)
mouseReleased(MouseEvent)
MouseAdapter
MouseMotionListener mouseDragged(MouseEvent)
mouseMoved(MouseEvent)
MouseMotionAdapter
TextListener
WindowListener
--WindowAdapter
textValueChanged(TextEvent)
windowActivated(WindowEvent)
windowClosed(WindowEvent)
windowClosing(WindowEvent)
windowDeactivated(WindowEvent)
windowDeiconified(WindowEvent)
windowIconified(WindowEvent)
windowOpened(WindowEvent)
Tabla 23
Como se puede apreciar en la tabla anterior, los interfaces que únicamente poseen un método no tienen
clase adaptadora correspondiente, ya que no tiene ningún sentido, siempre que utilizamos un interfaz
con un único método vamos a implementarlo.
A continuación vamos a comentar los pasos genéricos que se deben seguir a la hora de realizar el
tratamiento de eventos en Java. Aunque los pasos son genéricos, para poder hacer referencia a un
interfaz concreto vamos a suponer que queremos realizar el tratamiento de eventos que se corresponde
con la pulsación de un botón. Lo primero es importar el paquete java.awt.event:
import java.awt.event.*;
Código fuente 102
178
© Grupo EIDOS
10. Interfaces de usuario en Java: gestores de diseño y eventos
A continuación escribiremos la declaración de la clase para que implemente el interfaz adecuado
(listener interface).
Por ejemplo si se está tratando de atrapar un evento ActionEvent (es decir, una pulsación de un botón)
generado por un botón, será necesario implementar el interfaz ActionListener.
public class MiClase extends ClasePadre implements ActionListener{
Código fuente 103
Debemos determinar que componentes van a generar los eventos. Se registra cada uno de ellos con el
tipo adecuado de oyente, si tenemos en cuenta el ejemplo anterior del botón, se debería escribir lo que
indica el Código fuente 104.
objetoBoton.addActionListener(this);
Código fuente 104
Una vez hecho esto debemos crear las implementaciones de todos los métodos del interfaz que la clase
debe implementar.
public void actionPerformed(ActionEvent evento){
//cuerpo del método
}
Código fuente 105
Una vez comentado el tratamiento de eventos en Java de forma más o menos teórica vamos a
comentar una serie de ejemplos para aplicar la teoría a la práctica. Estos ejemplos además nos van a
servir para repasar distintos puntos del lenguaje Java comentados hasta ahora.
Vamos a realizar una aplicación que vamos a ir modificando y complicando para mostrar las
diferentes facetas del tratamiento de eventos en Java.
No vamos a tratar todos los interfaces, sólo vamos a tratar tres de los más representativos, el que se
encarga de las pulsaciones de botones, ActionListener, el que se encarga del ratón, MouseListener, y
el que se encarga de las ventanas, WindowListener, estos tres nos ofrecen ejemplos bastante prácticos.
También veremos los adaptadores MouseAdapter y WindowAdapter.
Primero vamos a comenzar creando una aplicación sencilla que va a consistir simplemente en una
ventana que nos va a indicar si se encuentra minimizada o no, y cuando pulsemos sobre el aspa de
cerrar la ventana esta se cierre y finalice la ejecución de la aplicación.
En la Figura 59 se puede ver el aspecto que tendría esta aplicación.
179
Programación en Java
© Grupo EIDOS
Figura 59
El código de esta aplicación de ejemplo es el que aparece en el Código fuente 106.
import java.awt.*;
//paquete necesario para el tratamiento de eventos
import java.awt.event.*;
public class Ventana extends Frame implements WindowListener{
//constructor de nuestra clase
public Ventana(String titulo){
//constructor de la clase padre
super(titulo);
//tamaño de la ventana
setSize(150,150);
//se muestra la ventana
show();
//se registra nuestra clase como oyente
addWindowListener(this);
}
public void windowClosed(WindowEvent evento){
System.out.println("La ventana se ha cerrado");
//finaliza la aplicación
System.exit(0);
}
public void windowIconified(WindowEvent evento){
System.out.println("Estoy minimizada");
}
public void windowDeiconified(WindowEvent evento){
System.out.println("No estoy minimizada");
}
public void windowClosing(WindowEvent evento){
//se cierra la ventana
dispose();
}
public void windowDeactivated(WindowEvent evento){}
public void windowActivated(WindowEvent evento){}
public void windowOpened(WindowEvent evento){}
public static void main (String[] args){
Ventana miVentana=new Ventana("Eventos");
}
}
Código fuente 106
Vamos a comentar algunos puntos importantes acerca del código anterior. Vamos a comentar algunas
consideraciones de carácter general, en primer lugar, al ser una aplicación debe tener un método
180
© Grupo EIDOS
10. Interfaces de usuario en Java: gestores de diseño y eventos
main() de arranque, y como hemos dicho que es una ventana, hereda de la clase Frame. Como se
puede observar hemos creado también un método constructor para nuestra clase Ventana.
En lo concerniente al tratamiento de eventos, como se puede ver importamos el paquete
correspondiente e indicamos que nuestra clase implementa el interfaz WindowListener. En el
constructor de nuestra clase indicamos quien va a ser el oyente de nuestra clase, es decir, quien va a
tratar los eventos, en este caso el oyente es nuestra misma clase. Para ello utilizamos la línea de código
que aparece en el Código fuente 107.
addWindowListener(this);
Código fuente 107
Es decir, indicamos que el oyente de los eventos de la ventana va a ser nuestra propia clase, es decir,
estamos registrando nuestra clase como oyente mediante la referencia this. Al ser nuestra clase fuente
y oyente de los eventos, también debe implementar el interfaz que define los métodos que se van a
ejecutar atendiendo al evento que se produzca. En nuestro caso hay tres métodos que no nos interesan,
pero como implementamos el interfaz WindowListener debemos implementarlos aunque no tengan
contenido.
Los métodos windowIconified() y windowDeiconified() se ejecutarán cuando se minimice la ventana
y cuando se restaure, respectivamente, el método windowClosing() se ejecuta en el momento de cerrar
la ventana y el método windowClosed() cuando ya se ha cerrado.
El resto del código es bastante sencillo y considero que no necesita una mayor explicación.
Como ya hemos comentado hay tres métodos del interfaz WindowListener que no nos interesan y que
por lo tanto nos gustaría suprimir, en este momento entran en juego las clases adaptadoras. Como ya
debe saber el alumno, las clases adaptadoras realizan una implementación vacía de todos los métodos
del interfaz con el que se corresponden, y al heredar de ellas utilizaremos únicamente los métodos del
interfaz correspondiente que nos interese.
En nuestro caso no podemos heredar de una clase adaptadora, ya que heredamos de la clase Frame y la
herencia múltiple no se permite en Java. Por lo tanto deberemos crear y definir una nueva clase que
herede de la clase adaptadora del interfaz WindowListener, es decir, que herede de la clase
WindowAdapter.
Por lo tanto si queremos hacer uso de la clase adaptadora WindowAdapter, el código de nuestra
aplicación se debe modificar como indica el Código fuente 108.
import java.awt.*;
import java.awt.event.*;
//nuestra clase ya no implementa el interfaz WindowListener
public class Ventana extends Frame{
public Ventana(String titulo){
super(titulo);
setSize(150,150);
show();
//se registra como oyente la clase que hereda la clase adaptadora
addWindowListener(new AdaptadorVentana(this));
}
181
Programación en Java
© Grupo EIDOS
public static void main (String[] args){
Ventana miVentana=new Ventana("Eventos");
}
}
//clase que hereda de la clase adaptadora
class AdaptadorVentana extends WindowAdapter{
//atributo que se utiliza como referencia a la clase
//que es la fuente del evento que queremos tratar
private Ventana fuente;
//constructor de nuestra clase adaptadora
//recibe como parámetro la clase fuente del evento
public AdaptadorVentana(Ventana fuente){
this.fuente=fuente;
}
//contiene la implementación de los métodos que nos intersan
//del interfaz WindowListener
public void windowClosed(WindowEvent evento){
System.out.println("La ventana se ha cerrado");
System.exit(0);
}
public void windowIconified(WindowEvent evento){
System.out.println("Estoy minimizada");
}
public void windowDeiconified(WindowEvent evento){
System.out.println("No estoy minimizada");
}
public void windowClosing(WindowEvent evento){
fuente.dispose();
}
}
Código fuente 108
Los mayores cambios se aprecian en que existe una nueva clase que va a ser la encargada de tratar los
eventos, en este caso si que existe una separación clara entre clase fuente y clase oyente. El oyente
sería la clase AdaptadorVentana y la clase fuente sería la clase principal Ventana. Nuestra clase
Ventana, al ser sólo fuente de eventos no va a implementar el interfaz WindowListener y por lo tanto
únicamente va a tener en su cuerpo el método constructor y el método main().
Otra cosa que cambia también en el código de nuestra aplicación es la forma en la que se registra el
oyente. Se sigue utilizando el método addWindowListener() pero en este caso se pasa por parámetro
una instancia de la clase adaptadora que hemos creado nosotros.
addWindowListener(new AdaptadorVentana(this));
Código fuente 109
Ahora vamos a detenernos en la clase AdaptadorVentana. Esta clase hereda de la clase
WindowAdapter y posee un atributo denominado fuente que es de la clase Ventana.
Este atributo es necesario para tener una referencia a la clase fuente del evento. Esta referencia es
necesaria ,en este caso, dentro del método windowClosing(), a la hora de cerrar la ventana.
182
© Grupo EIDOS
10. Interfaces de usuario en Java: gestores de diseño y eventos
public void windowClosing(WindowEvent evento){
fuente.dispose();
}
Código fuente 110
Para conseguir la referencia a la fuente del evento se pasa una instancia de la clase Ventana actual al
constructor de la clase adaptadora. De esta forma la clase adaptadora puede manipular y acceder al
objeto de clase Ventana.
public AdaptadorVentana(Ventana fuente){
this.fuente=fuente;
}
Código fuente 111
Como se puede comprobar esta nueva clase contiene todos los métodos que nos interesan del interfaz
WindowListener.
Si queremos tratar distintos eventos mediante clases adaptadoras, deberemos crear una clase que
herede de la clase adaptadora de cada tipo de evento, siempre que exista una clase adaptadora para el
evento en cuestión.
También existe la posibilidad de utilizar la clases adaptadoras como clases internas (inner classes).
Una clase interna es una clase definida dentro de otra. El beneficio que podemos obtener de estas
clases adaptadoras, es que no tenemos porque llevar la referencia de la clase fuente. La clase interna va
a tener acceso a todos los métodos y atributos de la clase en la que está declarada, aunque estos sean
privados. Realmente una clase interna se sale fuera de los principios de la POO, pero puede ser
bastante práctica.
El nuevo código tendría el aspecto que muestra el Código fuente 112.
import java.awt.*;
import java.awt.event.*;
public class Ventana extends Frame{
public Ventana(String titulo){
super(titulo);
setSize(150,150);
show();
//ya no se indica la referencia de la clase fuente
addWindowListener(new AdaptadorVentana());
}
public static void main (String[] args){
Ventana miVentana=new Ventana("Eventos");
}
//clase adaptadora interna
class AdaptadorVentana extends WindowAdapter{
//Ya no es necesario el atributo fuente
public void windowClosed(WindowEvent evento){
System.out.println("La ventana se ha cerrado");
System.exit(0);
}
183
Programación en Java
© Grupo EIDOS
public void windowIconified(WindowEvent evento){
System.out.println("Estoy minimizada");
}
public void windowDeiconified(WindowEvent evento){
System.out.println("No estoy minimizada");
}
public void windowClosing(WindowEvent evento){
//llamamos directamente al método dispose()
dispose();
}
}//se cierra la clase interna
}
Código fuente 112
Y para rizar el rizo, vamos a ofrecer una posibilidad distinta a la hora de tratar los eventos en Java, se
puede utilizar una clase interna anónima, el código sería el Código fuente 113.
import java.awt.*;
import java.awt.event.*;
public class Ventana extends Frame{
public Ventana(String titulo){
super(titulo);
setSize(150,150)
show();
//Utilizamos una clase interna anónima
addWindowListener(new WindowAdapter(){
//métodos de la clase anónima
public void windowClosed(WindowEvent evento){
System.out.println("La ventana se ha cerrado");
System.exit(0);
}
public void windowIconified(WindowEvent evento){
System.out.println("Estoy minimizada");
}
public void windowDeiconified(WindowEvent evento){
System.out.println("No estoy minimizada");
}
public void windowClosing(WindowEvent evento){
dispose();
}
});//se cierra la clase interna anónima
}
public static void main (String[] args){
Ventana miVentana=new Ventana("Eventos");
}
}
Código fuente 113
A la vista del código se puede ver que se trata de instanciar una clase sin indicar un objeto que
contenga una referencia a la misma, ya que en realidad esta clase sólo se va a utilizar para el
tratamiento de eventos y no se va a querer hacer una referencia a ella.
El mecanismo es muy sencillo, se utiliza el nombre de la clase adaptadora correspondiente,
WindowAdapter en nuestro caso, y se implementan los métodos que se consideren necesarios, por lo
demás funciona exactamente igual a una clase interna.
184
© Grupo EIDOS
10. Interfaces de usuario en Java: gestores de diseño y eventos
Una vez comentados las distintas opciones que tenemos a la hora de tratar los eventos, ahora vamos a
añadir a nuestra aplicación un botón para que al pulsarlo escriba un mensaje en la pantalla. En este
caso deberemos implementar el interfaz ActionListener, ya que este interfaz no posee una clase
adaptadora, al tener un único método llamado actionPerformed(). El nuevo aspecto de nuestra
aplicación de ejemplo se puede observar en la Figura 60.
Figura 60
Y el nuevo código es el Código fuente 114.
import java.awt.*;
import java.awt.event.*;
//implementamos el interfaz para tratar la pulsación del botón
public class Ventana extends Frame implements ActionListener{
private Button boton;
public Ventana(String titulo){
super(titulo);
setSize(150,150);
setLayout(new FlowLayout());
boton=new Button("Púlsame");
add(boton);
show();
addWindowListener(new AdaptadorVentana());
//oyente del botón, en este caso la clase Ventana
boton.addActionListener(this);
}
public void actionPerformed(ActionEvent evento){
System.out.println("Buenas tardes");
}
public static void main (String[] args){
Ventana miVentana=new Ventana("Eventos");
}
class AdaptadorVentana extends WindowAdapter{
public void windowClosed(WindowEvent evento){
System.out.println("La ventana se ha cerrado");
System.exit(0);
}
public void windowIconified(WindowEvent evento){
System.out.println("Estoy minimizada");
}
public void windowDeiconified(WindowEvent evento){
System.out.println("No estoy minimizada");
}
public void windowClosing(WindowEvent evento){
dispose();
}
}
}
Código fuente 114
185
Programación en Java
© Grupo EIDOS
Como se puede observar la fuente del evento en este nuevo caso va a ser por un lado, el botón y su
oyente la clase Ventana, y por otro lado la fuente de eventos va a ser la clase Ventana y el oyente la
clase interna AdaptadorVentana. Las líneas utilizadas para añadir el botón se encuentran en el
constructor, la única línea digna de mención es la que registra el oyente del botón (Código fuente 115).
boton.addActionListener(this);
Código fuente 115
La clase Ventana al implementar el interfaz ActionListener debe facilitar el método actionPerformed(),
que se ejecutará al pulsar el botón. Al pulsar el botón veremos en pantalla el saludo "Buenas tardes".
public void actionPerformed(ActionEvent evento){
System.out.println("Buenas tardes");
}
Código fuente 116
Para finalizar el presente capítulo modificaremos de nuevo nuestra aplicación, añadiendo un área de
texto, en la que se escribirá si el puntero del ratón se encuentra en el área de texto, o por el contrario ha
salido.
Aquí vamos a tratar un nuevo tipo de evento, en este caso los eventos del ratón, y lo vamos a realizar a
través de la clase adaptadora MouseAdapter. La clase MouseAdapter es la clase adaptadora del
interfaz MouseListener. Utilizamos la clase adaptadora, porque sólo nos van a interesar un par de
métodos del interfaz MouseListener.
El nuevo aspecto de la aplicación de ejemplo es el de la Figura 61. Y el código actualizado de la
aplicación es el que aparece en el Código fuente 117.
Figura 61
import java.awt.*;
import java.awt.event.*;
186
© Grupo EIDOS
10. Interfaces de usuario en Java: gestores de diseño y eventos
public class Ventana extends Frame implements ActionListener{
private Button boton;
private TextArea area;
public Ventana(String titulo){
super(titulo);
setLayout(new FlowLayout());
boton=new Button("Púlsame");
area=new TextArea();
add(boton);
add(area);
show();
pack();
addWindowListener(new AdaptadorVentana());
boton.addActionListener(this);
//El oyente del ratón es una nueva clase interna
area.addMouseListener(new AdaptadorRaton());
}
public void actionPerformed(ActionEvent evento){
System.out.println("Buenas tardes");
}
public static void main (String[] args){
Ventana miVentana=new Ventana("Eventos");
}
//clase adaptadora para los eventos del ratón
class AdaptadorRaton extends MouseAdapter{
public void mouseEntered(MouseEvent evento){
area.setText("El ratón ha entrado");
}
public void mouseExited(MouseEvent evento){
area.setText("El ratón ha salido");
}
}
class AdaptadorVentana extends WindowAdapter{
public void windowClosed(WindowEvent evento){
System.out.println("La ventana se ha cerrado");
System.exit(0);
}
public void windowIconified(WindowEvent evento){
System.out.println("Estoy minimizada");
}
public void windowDeiconified(WindowEvent evento){
System.out.println("No estoy minimizada");
}
public void windowClosing(WindowEvent evento){
dispose();
}
}
}
Código fuente 117
En esta última versión de la aplicación de prueba tenemos una nueva fuente de eventos, que va a ser el
área de texto, y un nuevo oyente que es la clase adaptadora AdaptadorRaton. La clase Ventana ofrece
un nuevo atributo que va a representar el área de texto.
Para registrar el oyente del área de texto se utiliza el método addMouseListener(), a este método se le
pasa una instancia de la clase adaptadora que va a tratar los eventos del ratón, en este caso se trata de
la clase AdaptadorRaton. Esta clase implementa los métodos mouseEntered() y mouseExited() que se
lanzarán cuando en ratón entre en el área de texto o salga del mismo, respectivamente.
Como se puede apreciar cada oyente se encarga de los eventos para los cuales se ha registrado, sin
tener en cuenta el resto de los que se produzca y sin interferir entre sí.
187
Interfaces de usuario en Java:
componentes Swing / contenedores
Introducción
A lo largo de los siguientes tres capítulos vamos a tratar el segundo tipo de componentes que ofrece
Java para construir interfaces de usuario, es decir, los componentes Swing incluidos en el paquete
javax.swing.
Muchos de los conceptos vistos en capítulos anteriores, como pueden ser los eventos o los gestores de
diseño siguen siendo válidos para los componentes Swing, así por ejemplo, los gestores de diseño que
utilizan los componentes Swing son los mismos que los vistos en los componentes AWT, de hecho se
debe importar el paquete JAVA.AWT para utilizarlos, pero Swing aporta un nuevo gestor de diseño
(que veremos en el siguiente capítulo, también dedicado por completo al extenso mundo de los
componentes Swing) que se encuentra en el paquete javax.swing.
Lo mismo sucede con los eventos, los componentes Swing utilizan las clases de los eventos incluidas
en el paquete java.awt.event, y además añaden nuevos eventos que se pueden encontrar en el paquete
javax.swing.event.
Como hemos dicho vamos a tener tres capítulos dedicados a Swing. Swing es bastante extenso y aun
utilizando tres capítulos no lo vamos a ver completamente, sería necesario un curso de la misma
extensión de éste (o más) para tratar Swing de forma completa.
En este primer capítulo vamos a introducir una serie de conceptos de Swing y también se va a tratar un
tipo de componentes Swing, los contenedores Swing.
Programación en Java
© Grupo EIDOS
En el segundo capítulo trataremos el otro gran grupo de componentes Swing, los componentes
atómicos. Y en el último de esta serie de capítulos trataremos otras características de Swing como son
el nuevo gestor de diseño que incorpora (implementado por la clase BoxLayout) y la característica
Pluggable Look & Feel, es decir, aspecto y comportamiento configurables.
JFC y Swing
JFC (Java Foundation Classes) es el nombre que recibe el conjunto de un gran número de
características cuya función primordial es la de permitir la construcción de complejos interfaces de
usuario. El primer anuncio de JFC se realizó en la conferencia de desarrolladores JavaOne y se definió
mediante las características que iba a ofrecer:
•
Componentes Swing: comprenden todos los componentes utilizados para interfaces de usuario
desde botones, barras de menú, diálogos y ventanas hasta cajas de texto, barras de progreso,
paneles con pestañas y listas. A lo largo de este capítulo describiremos algunos de los
componentes Swing, no todos ya que Swing ofrece un gran número de clases agrupadas en 15
paquetes distintos.
•
Soporte para Pluggable Look and Feel: es decir, soporte para una apariencia y
comportamiento configurables. Permite a cualquier programa que utilice componentes Swing
definir un tipo de apariencia y comportamiento (Look and Feel). Así por ejemplo una misma
aplicación puede presentar una apariencia y comportamiento al estilo de Java o bien tipo
Windows. En el apartado correspondiente volveremos a retomar esta característica y la
trataremos en detalle.
•
API de accesibilidad: permite a tecnologías de rehabilitación tales como lectores de pantalla y
displays Braille obtener información acerca del interfaz de usuario. El API de accesibilidad se
encuentra formando parte de las características avanzadas del lenguaje Java y por lo tanto no
lo vamos a tratar en este curso.
•
API Java 2D: al igual que ocurría con la característica o funcionalidad anterior, se trata de un
conjunto de clases especializadas, en este caso, en el tratamiento de gráficos en dos
dimensiones, imágenes y texto. El API Java 2D se escapa también del alcance y pretensiones
de este curso.
•
Soporte para arrastrar y soltar (Drag and Drop): permite la posibilidad de arrastrar y soltar
componentes entra aplicaciones Java y aplicaciones en otros lenguajes.
En muchos casos se utilizan indistintamente los términos JFC y Swing sin hacer ningún tipo de
distinción, Swing fue el nombre en clave que recibió el proyecto de Sun encargado de desarrollar los
nuevos componentes para la construcción de interfaces de usuario. La denominación swing aparece
también en los paquetes correspondientes.
Los componentes Swing se encuentran disponibles de dos formas distintas, como parte de la
plataforma Java 2, tanto en la herramienta JDK 1.2 o como en el JDK 1.3, y como una extensión del
JDK 1.1 (versión de Java 1.1) denominada JFC 1.1. En nuestro caso vamos a utilizar la versión que se
encuentran integrada con la plataforma Java 2.
190
© Grupo EIDOS
11. Interfaces de usuario en Java: componentes Swing / contenedores
Componentes Swing frente a componentes AWT
En este apartado vamos a comentar las diferencias entre los dos grupos de componentes que ofrece
Java para la creación de interfaces de usuario, y el porqué de la existencia de dos grupos distintos de
componentes.
Los componentes AWT aparecieron en la versión 1.0 del JDK y eran los únicos componentes que se
encontraban disponibles para desarrollar interfaces de usuario. Los componentes AWT se utilizan
principalmente en las versiones 1.0 y 1.1 del lenguaje (que coincidían con las versiones de la
herramienta JDK), aunque la plataforma Java 2 soporta perfectamente los componentes AWT como
hemos podido comprobar en capítulos anteriores.
Desde Sun nos recomiendan que utilicemos si es posible los componentes Swing en lugar de los
componentes AWT.
Los componentes Swing los podemos identificar porque los nombres de sus clases suelen ir precedidos
por la letra J. Así por ejemplo, la clase Button del AWT, tiene su clase correspondiente en Swing
denominada JButton. Los componentes AWT se encuentran en el paquete JAVA.AWT y los Swing en
el paquete javax.swing.
La mayor diferencia entre los componentes AWT y los componentes Swing, es que los componentes
Swing se encuentran implementados sin utilizar ningún tipo de código nativo. Los componentes Swing
se denominan componentes ligeros (lightweight) y los componentes AWT componentes pesados
(heavyweight).
Otra diferencia importante es la gran potencia y funcionalidad que ofrecen los componentes Swing.
incluso los componentes Swing más sencillos ofrecen una serie de posibilidades que los componentes
AWT no recogen, algunas de estas ventajas de los componentes Swing frente a los AWT son las que
se enumeran a continuación:
•
Los botones Swing pueden mostrar imágenes y/o texto.
•
Es posible situar bordes en torno a los componentes Swing.
•
Los componentes Swing no tienen porque ser rectangulares, por ejemplo, los botones pueden
ser redondos.
•
Las tecnologías de rehabilitación pueden obtener información de los componentes Swing de
forma sencilla.
Swing nos permite especificar el aspecto y comportamiento (look and feel) del interfaz de usuario de
nuestra aplicación (más adelante veremos como), sin embargo los componentes AWT tienen el
aspecto y comportamiento de la plataforma nativa sobre la que se ejecutan.
Vamos a comentar brevemente algunas de las razones por las que nos puede interesar utilizar
componentes Swing en lugar de componentes AWT.
•
El rico conjunto de componentes que ofrece Swing: botones con imágenes, barras de
herramientas, imágenes, elementos de menú, selectores de color, etc.
•
La arquitectura Pluggable Look & Feel, es decir, aspecto y comportamiento configurable y
seleccionable para los elementos del interfaz de usuario.
•
Posiblemente en un futuro se ampliarán los componentes Swing disponibles.
191
Programación en Java
© Grupo EIDOS
Parece hasta ahora que con los componentes Swing todo son ventajas, e incluso que ni siquiera
debemos plantearnos que tipo de componentes debemos utilizar para construir interfaces de usuario,
siempre elegiremos componentes Swing. Pero este razonamiento no es correcto, ya que en la práctica
no resulta tan sencillo.
Se debe señalar que, desgraciadamente, todavía no existen navegadores Web que soporten Swing, más
claro todavía, los navegadores Web actuales, incluso en sus últimas versiones, no implementan la
máquina virtual correspondiente a la plataforma Java 2. Por lo tanto si queremos construir applets que
puedan ejecutarse en cualquier navegador deberemos construir su interfaz gráfica mediante
componentes AWT, aunque en uno de los temas dedicados a los applets veremos un pequeño parche
que utilizaremos para que los navegadores soporten componentes Swing.
Contenedores de alto nivel
Bajo esta nomenclatura se agrupan una serie de clases que se corresponden con componentes Swing
que realizan la función de contenedores de otros componentes y que constituyen la raíz en la jerarquía
de contenedores. Los componentes de alto nivel se basan en los siguientes principios:
•
Swing ofrece tres clases que representan a componentes de alto nivel: JFrame (ventana),
JDialog (diálogo) y JApplet. También dentro de los componentes Swing encontramos una
cuarta clase denominada JWindow que representa una ventana sin controles ni título y que se
encuentra siempre por encima de cualquier ventana, es el equivalente a la clase Window de los
componentes AWT. A lo largo de este capítulo iremos viendo como en los componentes
Swing encontramos equivalentes de los componentes AWT.
•
Para aparecer en pantalla todo componente de interfaz de usuario debe formar parte de una
jerarquía de contenedores. Cada jerarquía de contenedores posee un contenedor de alto nivel
como raíz.
•
Cada contenedor de alto nivel posee un panel de contenido (content pane) que contiene los
componentes visibles del contenedor de alto nivel del interfaz de usuario correspondiente.
•
Opcionalmente se puede añadir una barra de menú a un contenedor de alto nivel. La barra de
menú se sitúa en el contenedor de alto nivel, pero fuera del panel de contenido.
Veamos un sencillo ejemplo que muestra un contenedor de alto nivel. Se trata de un objeto de la clase
JFrame que contiene una barra de menú (JMenuBar) de color azul y una etiqueta (JLabel) de color
amarillo. En la Figura 62 se puede ver el aspecto del contenedor.
Figura 62
192
© Grupo EIDOS
11. Interfaces de usuario en Java: componentes Swing / contenedores
Y el código fuente completo de este ejemplo es el que aparece en el Código fuente 118.
import java.awt.*;
import javax.swing.*;
public class ContenedorAltoNivel {
public static void main(String s[]) {
//se crea la ventana raíz de la jerarquía de contenedores
JFrame ventana = new JFrame("Contenedor de alto nivel");
//se crea la etiqueta
JLabel etiquetaAmarilla = new JLabel("");
etiquetaAmarilla.setOpaque(true);
etiquetaAmarilla.setBackground(Color.yellow);
etiquetaAmarilla.setPreferredSize(new Dimension(200, 180));
//se crea la barra de menú
JMenuBar barraMenuCyan = new JMenuBar();
barraMenuCyan.setOpaque(true);
barraMenuCyan.setBackground(Color.cyan);
barraMenuCyan.setPreferredSize(new Dimension(200, 20));
//se añaden la etiqueta y la barra de menú a la ventana
ventana.setJMenuBar(barraMenuCyan);
ventana.getContentPane().add(etiquetaAmarilla, BorderLayout.CENTER);
//se muestra la ventana
ventana.pack();
ventana.setVisible(true);
}
}
Código fuente 118
A la vista de este código podemos realizar los siguientes comentarios. Se han importado los paquetes
JAVA.AWT y javax.swing. El paquete JAVA.AWT es necesario para utilizar las clases Dimension,
Color y BorderLayout, como se puede observar aunque utilicemos componentes Swing vamos a
necesitar de los componentes AWT.
También podemos comprobar la forma en la que se añade un componente a un panel de contenido de
un contenedor de alto nivel como puede ser un objeto de la clase JFrame, y también como añadir una
barra de menú, en este mismo apartado veremos en más detalle el código que realiza estas funciones.
La jerarquía de contenedores del ejemplo queda como muestra el esquema de la Figura 63
Figura 63
193
Programación en Java
© Grupo EIDOS
Veamos un ejemplo más para ilustrar las jerarquías de contenedores. Para ello nos debemos fijar en la
Figura 64.
Figura 64
En este ejemplo se han utilizado los siguientes componentes Swing:
•
Una ventana principal (JFrame).
•
Un panel (JPanel).
•
Un botón (JButton).
•
Una etiqueta (JLabel).
Al panel se le ha añadido un borde mediante la clase BorderFactory para mostrar el agrupamiento de
forma más clara entre el botón y la etiqueta. El código fuente de este ejemplo es el que aparece en el
Código fuente 119.
import javax.swing.*;
import java.awt.*;
public class Ventana{
public static void main(String[] args) {
//se establece el Look & Feel
try {
UIManager.setLookAndFeel(
UIManager.getCrossPlatformLookAndFeelClassName());
} catch (Exception e) { }
//se crean los componentes
JFrame ventana = new JFrame("Ventana");
JLabel etiqueta = new JLabel(" Soy una etiqueta");
JButton boton = new JButton("Soy un botón");
JPanel panel = new JPanel();
//se asigna un borde y un gestor de diseño al panel
panel.setBorder(BorderFactory.createLineBorder(Color.red,5));
panel.setLayout(new GridLayout(0, 1));
//se añaden al panel el botón y la etiqueta
panel.add(boton);
panel.add(etiqueta);
//se añade el panel al panel de contenido de la ventana
ventana.getContentPane().add(panel, BorderLayout.CENTER);
//se muestra la ventana
ventana.pack();
ventana.setVisible(true);
}
}
Código fuente 119
194
© Grupo EIDOS
11. Interfaces de usuario en Java: componentes Swing / contenedores
Este código adelanta algo que veremos en detalle en el apartado correspondiente, cómo asignar un
aspecto y comportamiento (Look & Feel) determinado a nuestro interfaz de usuario, esto aparece en
las primeras líneas del método main() y en este caso se establece el aspecto y comportamiento de Java.
Vamos a pasar a describir las funciones que presentan cada uno de los componentes Swing utilizados
en el ejemplo.
En este ejemplo la ventana representada mediante la clase JFrame es el contenedor de alto nivel,
igualmente podría tratarse de otro componente de alto nivel como podría ser un objeto de la clase
JApplet o JDialog.
El panel es un contenedor intermedio, su propósito es el de simplificar la situación del botón y la
etiqueta, para que sea visible se le ha asignado un borde de color rojo. Otros contenedores intermedios
pueden ser paneles con pestañas (JTabbedPane) y paneles de scroll (JScrollPane), que juegan un papel
más visible e interactivo dentro del interfaz de usuario.
Y por último y el botón y la etiqueta se denominan componentes atómicos, son componentes que no
contienen otros componentes Swing, como ocurría con los anteriores, sino que tienen entidad
suficiente para presentar por sí mismos información al usuario. A menudo los componentes atómicos
tienen como función obtener información del usuario. Swing ofrece un gran número de componentes
atómicos (en este capítulo veremos algunos de ellos) como pueden ser listas desplegables
(JComboBox), cajas de texto (JTextField) o tablas (JTable).
En la Figura 65 se muestra el diagrama con la jerarquía de contenedores que presenta este ejemplo.
Figura 65
Como se puede observar en la Figura 65, incluso el programa Swing más sencillo presenta múltiples
niveles en su jerarquía de contenedores, además, como ya hemos dicho la raíz de esta jerarquía
siempre es un contenedor de alto nivel.
Cada contenedor de alto nivel indirectamente contiene un contenedor intermedio llamado panel de
contenido (content pane), y normalmente el panel de contenido contiene, directa o indirectamente,
todos los componentes visibles de la ventana del interfaz de usuario. La excepción a esta regla son las
barras de menú, que se sitúan en un lugar especial fuera del panel de contenido, como vimos en el
primer ejemplo de este apartado.
195
Programación en Java
© Grupo EIDOS
Para añadir un componente a un contenedor se utiliza una de las distintas formas del método add(),
como ya vimos en el tema dedicado a los componentes AWT.
Como regla general, una aplicación Java con un interfaz de usuario basado en componentes Swing,
tiene al menos una jerarquía de contenedores con un objeto de la clase JFrame como raíz de la misma.
Por ejemplo, si una aplicación tiene una ventana principal y dos diálogos, la aplicación presentará tres
jerarquías de contenedores, y por lo tanto tres contenedores de alto nivel. Una de las jerarquías de
contenedores tiene a un objeto de la clase JFrame como raíz, y cada una de las otras dos jerarquías un
objeto de la clase JDialog como raíz.
Un applet basado en Swing tiene al menos una jerarquía de contenedores y su raíz es un objeto de la
clase JApplet. Por ejemplo un applet que muestra un diálogo tiene dos jerarquías de contenedores, los
componentes en la ventana del navegador tienen como raíz un objeto JApplet, y los que se encuentran
en el diálogo tienen como raíz de su jerarquía de componentes un objeto de la clase JDialog.
Hasta ahora hemos visto un par de ejemplos que por un lado nos han mostrado como funcionan las
jerarquías de contenedores, y por otro, nos ha mostrado como utilizar algunos de los componentes
Swing, aunque más adelante veremos algunos de ellos con más detalle.
Para obtener una referencia al panel de contenido de un componente de alto nivel debemos utilizar el
método getContentPane(), que devuelve un objeto de la clase Container. Por defecto el panel de
contenido es un contenedor intermedio que hereda de la clase JComponent y que tiene como gestor de
diseño un BorderLayout.
Una vez que tenemos una referencia al panel de contenido podremos añadir los componentes que
consideremos necesarios utilizando una sentencia similar a la que muestra el Código fuente 120.
ventana.getContentPane().add(componente,BorderLayout.CENTER);
Código fuente 120
Todos los contenedores de alto nivel pueden tener, en teoría, una barra de menú, sin embargo en la
práctica las barras de menú se utilizan únicamente en ventanas y en raras ocasiones en applets. Para
añadir una barra de menú a un contenedor de alto nivel, crearemos un objeto de la clase JMenuBar,
añadiremos los elementos de menú que se consideren necesarios y por último se lanzará sobre el
contenedor de alto nivel el método setMenuBar(), pasándole por parámetro el objeto JMenuBar
correspondiente.
ventana.setMenuBar(objMenuBar);
Código fuente 121
Todo contenedor de alto nivel además de poseer un panel de contenido, poseen otro panel llamado
panel raíz (root pane). Normalmente este panel intermedio no se suele utilizar, su función es la de
gestionar el panel de contenido y la barra de menú junto con otros dos contenedores.
Estos dos contenedores son el panel de capas (layered pane) y el panel de cristal (glass pane). El panel
de capas contiene directamente la barra de menú y el panel de contenido, y además permite ordenar los
componentes que se vayan añadir con detenimiento (Z-order), es decir, permite organizar los
196
© Grupo EIDOS
11. Interfaces de usuario en Java: componentes Swing / contenedores
componentes del interfaz de usuario en capas. Esto puede ser útil para mostrar menús de aparición
súbita (popup menus) por encima de otros componentes.
El panel de cristal suele permanecer oculto y se encuentra por encima de todos los elementos del panel
raíz. Este panel es útil para interceptar eventos o pintar sobre un área que ya contiene otros
componentes, se utiliza sobre todo para interceptar eventos de entrada que suceden sobre el
contenedor de alto nivel. En la Figura 66 se puede ver un esquema que muestra la disposición de los
distintos paneles que podemos encontrar en un componente de alto nivel.
Figura 66
A continuación vamos a comentar los distintos componentes de alto nivel: JFrame, JDialog y JApplet.
JFrame
Ya hemos visto este componente Swing realizando las labores de contenedor de alto nivel, un objeto
de la clase JFrame representa a una ventana con bordes, título y botones que permiten cerrar y
maximizar la ventana.
Las aplicaciones Java que poseen interfaz de usuario al menos utilizan un JFrame, y también lo hacen
a veces los applets.
Veamos la utilización de la clase JFrame mediante un sencillo ejemplo. Se trata de una ventana que
contiene una etiqueta y que al pulsar el cierre de la misma se finalizará la ejecución de la aplicación.
Su código es el Código fuente 122.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Ventana{
public static void main(String s[]) {
JFrame ventana = new JFrame("Ventana Sencilla");
ventana.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
JLabel etiqueta = new JLabel("Soy una etiqueta");
etiqueta.setPreferredSize(new Dimension(175, 100));
197
Programación en Java
© Grupo EIDOS
ventana.getContentPane().add(etiqueta, BorderLayout.CENTER);
ventana.pack();
ventana.setVisible(true);
}
}
Código fuente 122
Y el aspecto de la ventana es el que vemos en la Figura 67.
Figura 67
En la primera línea del método main() se utiliza el constructor de la clase JFrame que nos permite
indicar el título de la ventana mediante una cadena que pasamos como parámetro, otra versión de este
constructor es sin argumentos.
A continuación se añade un oyente para los eventos de la ventana, en este caso se implementa
únicamente el método windowClosing() para finalizar la ejecución de la aplicación. En las siguientes
líneas se crea y añade una etiqueta (JLabel) al panel de contenido de la ventana.
Por último se lanzan los métodos pack() y setVisible() sobre la instancia de la clase JFrame. El método
pack() de un tamaño a la ventana de forma que todos sus contenidos tengan el tamaño especificado o
superior, una alternativa al método pack() es le método setSize() en el que se puede especificar de
forma explícita las dimensiones de la ventana. Al método setVisible() se le pasa el parámetro true para
que muestre la ventana en la pantalla.
Por defecto cuando se pulsa el botón de cierre de la ventana, la ventana se oculta, sin la necesidad de
utilizar ningún tratamiento de eventos, para cambiar este comportamiento se puede utilizar el método
setDefaultCloseOperation() o bien implementar un tratamiento de eventos similar al del ejemplo.
El parámetro que se le pasa al método setDefaultCloseOperation() debe ser una de las siguientes
constantes:
198
•
DO_NOTHING_ON_CLOSE: en este caso cuando se pulsa el botón de cierre de la ventana
no ocurre nada, la ventana sigue visible.
•
HIDE_ON_CLOSE: es el valor por defecto, cuando se pulsa el botón de cierre la ventana se
oculta, pero sigue existiendo, teniendo la posibilidad de mostrarse la ventana de nuevo si así lo
indicamos en el programa.
•
DISPOSE_ON_CLOSE: al cerrar la ventana se elimina de la memoria y de la pantalla, es
decir, en este caso se liberan todos los recursos que estaban siendo utilizados por la ventana.
© Grupo EIDOS
11. Interfaces de usuario en Java: componentes Swing / contenedores
La clase JFrame hereda de la clase java.awt.Frame y por lo tanto hereda todos los métodos de la clase
Frame, como pueden ser setSize(), pack(), setTitle(), setVisible() y getTitle(). Además de los métodos
de la clase java.awt.Frame, la clase JFrame tiene los siguientes métodos propios:
•
void setDefaultCloseOperation(int): asigna el tipo de operación que va a realizar la ventana
cuando el usuario pulse el botón de cierre de la ventana. Ya hemos visto las posibles acciones.
•
int getDefaultCloseOperation(): devuelve la operación de cierre por defecto asignada a la
ventana.
•
void setContentPane(Container): asigna a la ventana un panel de contenido, que va a ser el que
contenga todos los componentes visibles del interfaz de usuario de la ventana. Este método
permite utilizar nuestro propio panel de contenido, lo normal es utilizar un objeto de la clase
JPanel para crear el panel de contenido, en el Código fuente 123 se puede observar como se
crea un objeto JPanel que luego va a ser el panel de contenido de una ventana.
JPanel panelContenido=new JPanel();
panelContenido.setLayout(new BorderLayout());
panelContenido.add(componente,BorderLayout.CENTER);
panelContenido.add(otroComponente,BorderLayout.SOUTH);
ventana.setContentPane(panelContenido);
Código fuente 123
•
Container getContentPane(): devuelve el panel de contenido de la ventana, este método se
suele utilizar para una vez que tenemos una referencia al panel de contenido de la ventana
añadir componentes a la misma.
•
void setMenuBar(MenuBar): asigna a la ventana una barra de menú determinada representada
por un objeto de la clase MenuBar.
•
JMenuBar getMenuBar(): devuelve la barra de menú que tiene asignada la ventana.
•
void setGlassPane(Component): permite asignar un panel de cristal a la ventana.
•
Component getGlassPane(): devuelve el panel de cristal de la ventana.
JDialog, JOptionPane
Vamos a pasar ahora a comentar el segundo tipo de contenedores de alto nivel, los diálogos, que son
un tipo de ventana más limitada que las ventanas representadas por la clase JFrame. Hay varias clases
que ofrecen diálogos:
•
JOptionPane: permiten crear sencillos diálogos estándar.
•
ProgressMonitor: muestra un diálogo que muestra el progreso de una operación.
•
JColorChooser: ofrece un diálogo estándar para la selección de colores.
•
JFileChooser: ofrece un diálogo estándar para la selección de un fichero.
199
Programación en Java
•
© Grupo EIDOS
JDialog: permite crear directamente diálogos completamente personalizados.
Como se indica al principio de esta sección nosotros únicamente nos vamos a encargar de las clase
JOptionPane y JDialog.
Todo diálogo es dependiente de una ventana determinada, si la ventana se destruye también sus lo
harán sus diálogos asociados. Cuando la ventana se transforma en icono sus diálogos desaparecen de
la pantalla, cuando se maximiza la ventana los diálogos vuelven a aparecer.
Los diálogos pueden ser modales, cuando un diálogo modal se encuentra visible se bloquea la entrada
del usuario en todas las demás ventanas del programa. Los diálogos que ofrece la clase JOptionPane
son modales, para crear un diálogo no modal se debe hacer uso de la clase JDialog que permitirá
indicar si el diálogo que se crea va a ser modal o no.
La clase JDialog hereda de a clase AWT java.awt.Dialog, añade a la clase Dialog un panel raíz (root
pane) y soporte para la operación por defecto de cierre. Como se puede observar son las mismas
características que ofrecía la clase JFrame sobre la clase Frame del AWT.
Cuando se utiliza la clase JOptionPane en realidad también estamos haciendo uso de la clase JDialog
de forma indirecta, ya que la clase JOptionPane es un contenedor que puede crear de forma automática
una instancia de la clase JDialog y añadirse al panel de contenido de ese diálogo. A continuación
vamos a comentar las distintas características de la clase JOptionPane.
Mediante la clase JOptionPane podemos crear distintos tipos de diálogos, JOptionPane ofrece soporte
para mostrar diálogos estándar, indicar los iconos, especificar el título y texto del diálogo y los textos
de los botones que aparecen.
En cuanto a los iconos que se muestran en el diálogo facilitado por JOptionPane, podemos utilizar
iconos personalizados, no utilizar ningún tipo de icono, o utilizar uno de los cuatro iconos estándar que
ofrece la clase JOptionPane, estos iconos son los de pregunta, información, advertencia y error. El
aspecto de estos iconos variará según el Look & Feel (aspecto y comportamiento) que se aplique.
Para mostrar diálogos modales sencillos se utilizará directamente uno de los métodos
showXXXDialog() de la clase JOptionPane. Este conjunto de métodos son métodos estáticos y por lo
tanto se lanzarán sobre una instancia concreta de la clase, sino que se lanzaran de forma directa sobre
la clase.
Si lo que necesitamos es controlar el comportamiento del diálogo cuando se cierre o si el diálogo no
debe ser modal entonces instanciaremos un objeto de la clase JOptionPane y lo añadiremos a un objeto
de la clase JDialog. Veamos a continuación los principales métodos showXXXDialog() de la clase
JOptionPane.
El primero de estos métodos es el método showMessageDialog(), que muestra un diálogo modal con
un botón de aceptar. Se puede especificar el mensaje, el icono y el título que muestra el diálogo. Este
método se encuentra sobrecargado y ofrece tres versiones distintas:
200
•
void showMessageDialog(Component componentePadre, Object mensaje): el primer
parámetro indica el componente padre del que depende, puede ser una ventana, un
componente dentro de una ventana o nulo y el segundo el mensaje que se muestra, suele ser
una cadena de texto que podemos dividir en varias líneas utilizando el carácter de nueva línea
(\n).
•
void showMessageDialog(Component componentePadre, Object mensaje, String título, int
tipoMensaje): en este caso se especifica también el título, que será una cadena, y el tipo de
© Grupo EIDOS
11. Interfaces de usuario en Java: componentes Swing / contenedores
mensaje a mostrar. Este último parámetro determinará el icono que se va a mostrar en el
diálogo, para ello se utilizan una serie de constantes definidas en la clase JOptionPane, estas
constantes son: PLAIN_MESSAGE (sin icono), ERROR_MESSAGE (icono de error),
INFORMATION_MESSAGE (icono de información), WARNING_MESSAGE (icono de
advertencia) y QUESTION_MESSAGE (icono de pregunta).
•
void showMessageDialog(Component componentePadre, Object mensaje, String título, int
tipoMensaje, Icon icono): en esta última versión del método podemos indicar un icono
personalizado para que se muestre en el diálogo.
Veamos algunos ejemplos con el método showMessageDialog(). Podemos añadir las distintas
sentencias de creación de diálogos que vamos a ver ahora al ejemplo de la sección anterior en la que
tratábamos la clase JFrame, la instancia de la clase JFrame, llamada ventana va a ser el componente
padre de los diálogos.
Si añadimos la línea que muestra el Código fuente 124, a nuestra clase Ventana de la sección anterior,
obtendremos el resultado que aparece en la Figura 68.
JOptionPane.showMessageDialog(ventana,"Esto es un mensaje",
"Título",JOptionPane.WARNING_MESSAGE);
Código fuente 124
Figura 68
Otras variaciones sobre la sentencia anterior se muestran a continuación con sus correspondientes
resultados.
JOptionPane.showMessageDialog(ventana,"Esto es un mensaje");
Código fuente 125
En este caso se muestra el título y tipo de mensaje por defecto.
Figura 69
201
Programación en Java
© Grupo EIDOS
ImageIcon icono = new ImageIcon("icono.gif");
JOptionPane.showMessageDialog(ventana,"Esto es un mensaje","Título",
JOptionPane.INFORMATION_MESSAGE,icono);
Código fuente 126
En este otro caso indicamos un icono personalizado para que se muestre en el diálogo.
Figura 70
JOptionPane.showMessageDialog(ventana,"Esto es un mensaje","Título",
JOptionPane.ERROR_MESSAGE);
Código fuente 127
Figura 71
Como se puede comprobar en todos los ejemplos, si pulsamos el botón etiquetado como OK, el
diálogo se cerrará.
Otro método de la clase JOptionPane que muestra un diálogo es el método showConfirmaDialog(). En
este caso este método muestra un diálogo de confirmación, para que el usuario seleccione entre los
botones correspondientes. Este método, al igual que el anterior, se encuentra sobrecargado y por lo
tanto ofrece tres versiones distintas, que pasamos a comentar a continuación.
202
•
int showConfirmDialog(Component componentePadre, Object mensaje): este método muestra
un diálogo modal con los botones, que representan las opciones disponibles, si, no y cancelar,
y además con el título por defecto.
•
int showConfirmDialog(Component componentePadre, Object mensaje, String título, int
tipoOpción): en este caso podemos especificar el título del diálogo de confirmación y el tipo
de opciones que se van a mostrar, para esto último utilizaremos las siguientes constantes
ofrecidas por la clase JOptionPane: DEFAULT_OPTION, YES_NO_OPTION,
YES_NO_CANCEL_OPTION y OK_CANCEL_OPTION.
© Grupo EIDOS
11. Interfaces de usuario en Java: componentes Swing / contenedores
•
int showConfirmDialog(Component componentePadre, Object mensaje, String título, int
tipoOpción, int tipoMensaje): en esta versión del método podemos indicar además el tipo de
mensaje que se muestra en el diálogo, de la misma forma que lo hacíamos en el método
showMessageDialog().
•
int showConfirmDialog(Component componentePadre, Object mensaje, String título, int
tipoOpción, int tipoMensaje, Icon icono): en esta última versión especificamos un icono
personalizado que se va a mostrar en el diálogo de conformación correspondiente.
Como se puede observar en todas las versiones del método showConfirmDialog() se devuelve un
entero (int), este entero va a recoger la selección que ha realizado el usuario, es decir, indicará el botón
que ha sido pulsado. El entero que devuelve este método se corresponde con uno de los valores de las
siguientes constantes de la clase JOptionPane: YES_OPTION, NO_OPTION, CANCEL_ OPTION,
OK_OPTION y CLOSED_OPTION (el usuario cierra el diálogo sin pulsar ninguna de las opciones
disponibles).
Al igual que ocurría con el método anterior, vamos a ver ejemplos de utilización del método showConfirmDialog.
JOptionPane.showConfirmDialog(ventana,"¿Desea formatear el disco?");
Código fuente 128
En este caso se muestra el diálogo de confirmación por defecto.
Figura 72
JOptionPane.showConfirmDialog(ventana,"¿Desea formatear el disco?","Confirme
operación",
JOptionPane.YES_NO_OPTION,JOptionPane.WARNING_MESSAGE);
Código fuente 129
Figura 73
203
Programación en Java
© Grupo EIDOS
El siguiente método que vamos a tratar de la clase JOptionPane va a ser el método
showOptionDialog().
Este método es mucho más potente que los vistos anteriormente, ya que permite una mayor
personalización del diálogo. La función de este método es la de mostrar un diálogo modal con los
botones, iconos, mensaje y títulos especificados para que el usuario seleccione entre las distintas
opciones que se le ofrecen, con este método podremos indicar los botones que queremos aparezcan en
el diálogo.
El método showOptionDialog() se diferencia del método showConfirmDialog() principalmente en que
permite especificar las etiquetas de los botones que aparecen y además permite especificar la opción
seleccionada por defecto.
La sintaxis de este método es la siguiente:
int showOptionDialog(Component componentePadre, Object mensaje,
String título, int tipoOpción,
int tipoMensaje, Icon icono, Object[]
opciones, Object valorInicial)
Vamos a comentar los distintos parámetros de este nuevo método. Los tres primeros parámetros son
los mismos que los vistos en el método showMessageDialog() y además tienen el mismo significado.
En el tipo de opción se especifica el conjunto de opciones que se van a presentar al usuario, y por lo
tanto se corresponderán con las constantes vistas en el método showConfirmDialog().
Los dos siguientes parámetros, tipo de mensaje y el icono personalizado, tiene el mismo cometido que
el método showMessageDialog(). El siguiente parámetro es un array de objetos que se va a
corresponder con un array de cadenas que se van a mostrar en cada uno de los botones del diálogo, es
decir, podemos utilizar nuestras propias etiquetas pera mostrar en los botones. El último parámetro
indica cual es la opción que se encuentra seleccionada por defecto.
Como se puede observar en la sintaxis del método showOptionDialog() se devuelve un entero (int),
este entero tiene la misma función que el que devolvía el método showConfirDialog(), es decir, va a
recoger la selección que ha realizado el usuario, es decir, indicará el botón que ha sido pulsado. Se
debe indicar que aunque utilicemos etiquetas personalizadas para nuestros botones, se siguen
devolviendo los mismos valores de las constantes, así por ejemplo un diálogo del tipo
YES_NO_OPTION siempre devolverá los valores: YES_OPTION, NO_OPTION o
CLOSED_OPTION.
Ahora se va a mostrar ejemplos de uso del método showOptionDialog(). Estos ejemplos son
únicamente unas cuentas sentencias que podemos incluir, como ocurría con los métodos anteriores, en
nuestra ya conocida clase Ventana.
Object[] opciones={"Vale", "Ni hablar"};
ImageIcon icono = new ImageIcon("icono.gif");
JOptionPane.showOptionDialog(ventana,"¿Desea formatear el disco?","Confirme
operación",
JOptionPane.YES_NO_OPTION,JOptionPane.WARNING_MESSAGE,
icono, opciones, opciones[1]);
Código fuente 130
204
© Grupo EIDOS
11. Interfaces de usuario en Java: componentes Swing / contenedores
Se muestra un diálogo con las opciones si/no pero con unas etiquetas de botones personalizadas, así
como con un icono personalizado, además es el segundo botón el que se encuentra seleccionado por
defecto.
Figura 74
El último método que vamos a ver de la clase JOptionPane es el método showInputDialog(), este
método va a permitir obtener información de entrada del usuario a través del diálogo, ya sea a través
de una caja de texto o a través de un una lista de opciones. El diálogo que se muestra va a tener dos
botones, uno de aceptar y otro de cancelar, si el usuario pulsa el botón de aceptar indicará que ha
introducido una información que podemos recuperar. Este método se encuentra sobrecargado, y ofrece
las siguientes versiones:
•
Object showInputDialog(Component componentePadre, Object mensaje): muestra un diálogo
con el mensaje correspondiente que requiere una entrada del usuario a través de una caja de
texto.
•
Object showInputDialog(Component componentePadre, Object mensaje, String título, int
tipoMensaje): en esta caso nos permite especificar un título y un tipo de mensaje (ya
conocemos las constantes correspondientes).
•
Object showInputDialog(Component componentePadre, Object mensaje, String título, int
tipoMensaje, Icon icono, Object opciones, Object opciónSeleccionada): en este método se
permite indicar un icono personalizado y un conjunto de opciones en forma de lista
desplegable para que el usuario seleccione una de ellas además se permite seleccionar una
opción por defecto.
•
String showInputDialog(Object mensaje): en este caso no se indica nada más que el mensaje
que se va a mostrar al usuario, sin utilizar ningún componente padre, por lo que el diálogo se
situará en el centro de la pantalla, ya que en los otros casos se centra siempre con respecto al
componente padre, aunque de todas forma sigue bloqueando a la ventana que lo ha generado.
Como se puede comprobar siempre se devuelve un objeto de la clase String, que se va a corresponder
con una cadena que va a representar la información indicada por el usuario, ya sea a través de una caja
de texto una de una lista de opciones disponibles. En los siguientes ejemplos se muestra la utilización
del método showInputDialog().
En este caso se va a recoger el dato facilitado por el usuario y mostrarlo en la salida estándar de la
aplicación.
String respuesta=JOptionPane.showInputDialog(ventana,"¿Cuál es tu nombre?");
System.out.println("El nombre del usuario es: "+respuesta);
Código fuente 131
205
Programación en Java
© Grupo EIDOS
Figura 75
Object[] valores={"Rojo","Verde","Azul","Negro","Amarillo"};
ImageIcon icono = new ImageIcon("icono.gif");
Object respuesta=JOptionPane.showInputDialog(ventana,"¿Cuál es tu color favorito?",
"Selección de
color",JOptionPane.QUESTION_MESSAGE,
icono,valores,valores[2]);
System.out.println("El color favorito del usuario es el: "+respuesta);
Código fuente 132
En este caso se muestra una lista con las opciones disponibles, para que el usuario seleccione la que
desee. También se ha utilizado un icono personalizado.
Figura 76
Hasta ahora hemos visto los métodos que nos ofrece la clase JOptionPane para mostrar distintos tipos
de diálogo. Estos diálogos tienen en común una serie de características: al pulsar alguno de los botones
que contienen se cierran, también se cierran cuando el usuario pulsa el cierre del diálogo y por último
son todos modales. En algunos casos estos diálogos nos servirán, ya que su comportamiento se puede
adecuar a nuestras necesidades, pero en otros casos esto puede no ser así.
En algunos casos nos puede interesar validar la información ofrecida por el usuario en un diálogo, o
también nos puede interesar utilizar un diálogo que no sea modal. En estos casos en los que deseamos
personalizar al máximo los diálogos utilizaremos la clase JDialog conjuntamente con la clase
JOptionPane. Para ello necesitamos crear una instancia de la clase JOptionPane para añadirla al objeto
JDialog correspondiente.
La clase JOptionPane presenta múltiples constructores que permiten especificar el mensaje que se va a
mostrar, el tipo de mensaje, el tipo de opciones, el icono personalizado, las opciones disponibles y la
opción seleccionada por defecto.
Una vez creado el objeto JOptionPane, deberemos crear el objeto JDialog que lo va a contener,
algunos de los constructores ofrecidos por la clase JDialog son:
206
© Grupo EIDOS
11. Interfaces de usuario en Java: componentes Swing / contenedores
•
JDialog(): crea un diálogo no modal, sin título definido y sin ninguna ventana propietaria.
•
JDialog(Frame ventanaPropietaria): se indica la ventana a la que se encuentra asociado el
diálogo.
•
JDialog(Frame ventanaPropietaria, boolean modal): se indica si el diálogo va a ser modal o
no.
•
JDialog(Frame ventanaPropietaria, boolean modal, String título): se especifica además el
título que va a tener el diálogo.
Si ya tenemos instanciados el objeto JOptionPane y el objeto JDialog, únicamente nos queda asignar al
diálogo (JDialog) su contenido (JOptionPane), para ello utilizamos el método setContentPane() de la
clase JDialog, y que es común, como ya hemos comentado, a todos los contenedores de alto nivel.
A continuación se muestra este proceso con un ejemplo que consiste en una sencilla aplicación Java
que muestra una ventana y un diálogo asociada a la misma.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Dialogo{
public static void main(String s[]) {
JFrame ventana = new JFrame("Ventana Sencilla");
ventana.setSize(175,100);
ventana.setVisible(true);
JOptionPane contenido=new JOptionPane("Esto es un mensaje",
JOptionPane.INFORMATION_MESSAGE,JOptionPane.YES_NO_CANCEL_OPTION);
JDialog dialogo=new JDialog(ventana,"Esto es un diálogo",true);
dialogo.setContentPane(contenido);
dialogo.setLocationRelativeTo(ventana);
dialogo.pack();
dialogo.setVisible(true);
}
}
Código fuente 133
El resultado es un diálogo modal como el de la Figura 77.
Figura 77
Como se puede comprobar la pulsación de los botones del diálogo no tiene el efecto que tenían
anteriormente, si queremos que realicen alguna operación cuando se pulsen, deberemos hacerlo a
través de una gestión de eventos a través del código de nuestra aplicación. Aunque si pulsamos el
207
Programación en Java
© Grupo EIDOS
cierre del diálogo éste se sigue cerrando, ya que la operación tiene asignada por defecto para el cierre
es HIDE_ON_CLOSE, es decir, ocultarse en el cierre, como ocurría con la clase JFrame.
En el código se puede observar que se utiliza el método setLocationRealtiveTo(), este método centra el
diálogo de forma relativa al componente que le pasamos por parámetro.
Otros métodos de la clase JDialog son los siguientes:
•
Container getContentPane(): devuelve el panel de contenido del diálogo.
•
int getDefaultCloseOperation(): devuelve la operación de cierre por defecto que tiene asignada
el diálogo.
•
void setDefaultCloseOperation(int operacion): asigna una operación de cierre por defecto al
diálogo.
JApplet
Este es el tercero de los contenedores de alto nivel. Esta clase hereda de la clase java.applet.Applet, es
por lo tanto la versión Swing de la clase Applet.
La clase JApplet aporta dos características esenciales a los applets, ofrece soporte para tecnologías de
rehabilitación y ofrece un panel raíz, con todo lo que ello supone, es decir, añadir componentes al
panel de contenido, posibilidad de tener una barra de menú, etc.
Todavía no hemos visto con detenimiento la utilización de applets, únicamente los hemos definido de
manera sencilla, por lo tanto en el presente capítulo no vamos a comentar nada más de la clase
JApplet, será en los capítulos dedicados a los applets dónde se trate la misma.
Contenedores intermedios
Los contenedores intermedios son contenedores Swing, que aunque no son contenedores de alto nivel,
su labor principal es la de contener otros componentes. Estos contenedores se siguen basando en la
jerarquía de contenedores de Swing que ya hemos visto anteriormente.
Swing ofrece una serie de contenedores de propósito general:
208
•
JPanel: es le más flexible y utilizado de todos ellos. Se utiliza normalmente para agrupar
componentes, se le puede asignar gestores de diseño y bordes. Los paneles de contenido de los
contenedores de alto nivel suelen ser de la clase JPanel.
•
JScrollPane: ofrece una vista de scroll de un componente, suele contener componentes
grandes o que pueden crecer. Se utilizan debido a las limitaciones del tamaño de la pantalla.
•
JSplitPane: este panel agrupa dos componentes, uno al lado del otro. El usuario puede ajustar
la línea divisora que separa ambos componentes arrastrándola. El aspecto es similar al que
ofrece, por ejemplo, el explorador de Windows, la vista del árbol de directorios se encuentra
separada de la vista de contenidos de un directorio.
© Grupo EIDOS
11. Interfaces de usuario en Java: componentes Swing / contenedores
•
JTabbedPane: contiene muchos componentes pero sólo puede mostrar un conjunto de ellos a
la vez, el usuario puede ir cambiando entre los distintos conjuntos de manera sencilla. Un
ejemplo podrían ser las distintas pestañas de una hoja de propiedades.
•
JToolBar: grupa una serie de componentes (normalmente botones) en una fila o en una
columna, pueden permitir al usuario arrastrar la barra a distintos lugares. Un ejemplo pueden
ser las barras de herramientas de un procesador de textos como MS Word.
Además también existen los siguientes contenedores intermedios que se encuentran más
especializados:
•
JInternalFrame: permite mostrar una ventana dentro de otra. Parece una ventana y ofrece toda
su funcionalidad pero debe parecer siempre dentro de otra ventana (contenedor de alto nivel)
que la contiene.
•
JLayeredPane: este panel ofrece una tercera dimensión, la profundidad, para poder posicionar
componentes de esta forma. Esta tercera dimensión se denomina también Z-order. Al añadir
un componente a un panel de este tipo se especifica su profundidad mediante un entero,
cuanto mayor sea el entero especificado mayor será la profundidad en la que se sitúa el
componente.
•
JRootPane: esta clase representa el panel raíz de un contenedor de alto nivel, como ya vimos
en el apartado anterior el panel raíz esta formado por las siguientes partes: panel de capas,
panel de contenido, panel de cristal y barra de menú.
A continuación vamos a comentar algunos de estos paneles intermedios, no vamos a tratar todos ya
nos excederíamos en la extensión del presente curso, no se debe olvidar que Swing tienen un gran
número de componentes.
JPanel
Esta clase permite construir paneles de propósito general para contener componentes Swing. Por
defecto un panel no muestra nada en pantalla a excepción de su fondo, también por defecto los paneles
son opacos, aunque se pueden hacer transparentes mediante el método setOpaque() pasándole el valor
false por parámetro.
La clase JPanel es la versión Swing de la clase Panel de AWT. La clase JPanel permite asignar un
gestor de diseño al panel para indicar la forma en la que se van añadiendo los distintos componentes al
mismo, por defecto el gestor de diseño de un objeto JPanel es un FlowLayout.
Swing utiliza los mismos gestores de diseño que vimos para los componentes AWT, de hecho se debe
importar el paquete JAVA.AWT para poder utilizarlos, pero se añade un nuevo gestor mediante la
clase BoxLayout, que veremos en el apartado correspondiente.
Para añadir componentes lo haremos de la misma forma que veíamos en los paneles AWT, es decir,
con las distintas versiones de los métodos add(). En el Código fuente 134 se muestra la forma de
utilizar esta clase.
import
import
import
public
java.awt.*;
java.awt.event.*;
javax.swing.*;
class Panel{
209
Programación en Java
© Grupo EIDOS
public static void main(String s[]) {
JFrame ventana = new JFrame("Ventana Sencilla");
JPanel panel=new JPanel();
panel.setLayout(new BorderLayout());
JLabel etiqueta1 = new JLabel("Soy una etiqueta",JLabel.CENTER);
etiqueta1.setPreferredSize(new Dimension(175, 100));
JLabel etiqueta2 = new JLabel("Soy otra etiqueta",JLabel.CENTER);
etiqueta2.setPreferredSize(new Dimension(175, 100));
panel.add(etiqueta1, BorderLayout.NORTH);
panel.add(etiqueta2, BorderLayout.SOUTH);
ventana.getContentPane().add(panel, BorderLayout.CENTER);
ventana.pack();
ventana.setVisible(true);
}
}
Código fuente 134
Se trata simplemente de añadir dos etiquetas a un panel (JPanel) y añadir este panel al panel de
contenido de una ventana (JFrame), el resultado se puede apreciar en la Figura 78.
Figura 78
La clase JPanel hereda de la clase JComponent, y debido a ello permite utilizar una nueva
funcionalidad ofrecida por Swing, los bordes.
Cada objeto de la clase JComponent puede tener uno o más bordes. Los bordes no son realmente
componentes, sino que se utilizan para delimitar visualmente una serie de componentes de otros. Para
asignar un borde a un componente, en este caso un objeto de la clase JPanel, se utiliza el método
setBorder().
Para crear los distintos bordes que ofrece Swing se suele utilizar la clase BorderFactory. Esta clase
ofrece un gran número de métodos que permiten crear distintos tipos de bordes: de líneas, elevados,
hundidos, marcados, con títulos, una combinación de dos bordes, etc.
En el Código fuente 135 se puede observar como se asigna distintos bordes a distintos paneles dentro
de una ventana.
import java.awt.*;
import java.awt.event.*;
210
© Grupo EIDOS
11. Interfaces de usuario en Java: componentes Swing / contenedores
import javax.swing.*;
public class Panel{
public static void main(String s[]) {
JFrame ventana = new JFrame("Ventana Sencilla");
ventana.getContentPane().setLayout(new GridLayout(7,1));
((JPanel)ventana.getContentPane()).setBorder(
BorderFactory.createEmptyBorder(10,10,10,10));
JPanel panel1=new JPanel();
JLabel etiqueta1 = new JLabel("Borde tipo línea",JLabel.CENTER);
panel1.setBorder(BorderFactory.createLineBorder(Color.black));
panel1.add(etiqueta1);
ventana.getContentPane().add(panel1);
JPanel panel2=new JPanel();
JLabel etiqueta2 = new JLabel("Borde elevado",JLabel.CENTER);
panel2.setBorder(BorderFactory.createRaisedBevelBorder());
panel2.add(etiqueta2);
ventana.getContentPane().add(panel2);
JPanel panel3=new JPanel();
JLabel etiqueta3 = new JLabel("Borde hundido",JLabel.CENTER);
panel3.setBorder(BorderFactory.createLoweredBevelBorder());
panel3.add(etiqueta3);
ventana.getContentPane().add(panel3);
JPanel panel4=new JPanel();
JLabel etiqueta4 = new JLabel("Borde decorado",JLabel.CENTER);
panel4.setBorder(BorderFactory.createMatteBorder(5,5,5,5,Color.red));
panel4.add(etiqueta4);
ventana.getContentPane().add(panel4);
JPanel panel5=new JPanel();
JLabel etiqueta5 = new JLabel("Borde con título",JLabel.CENTER);
panel5.setBorder(BorderFactory.createTitledBorder(
BorderFactory.createLineBorder(Color.blue),"Título"));
panel5.add(etiqueta5);
ventana.getContentPane().add(panel5);
JPanel panel6=new JPanel();
JLabel etiqueta6 = new JLabel("Borde grabado",JLabel.CENTER);
panel6.setBorder(BorderFactory.createEtchedBorder());
panel6.add(etiqueta6);
ventana.getContentPane().add(panel6);
JPanel panel7=new JPanel();
JLabel etiqueta7 = new JLabel("Borde compuesto",JLabel.CENTER);
panel7.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(Color.yellow),
BorderFactory.createRaisedBevelBorder()));
panel7.add(etiqueta7);
panel7.setPreferredSize(new Dimension(175,50));
ventana.getContentPane().add(panel7);
ventana.pack();
ventana.setVisible(true);
}
}
Código fuente 135
Y el resultado se muestra en la Figura 79.
El Código fuente 135 merece una serie de comentarios. El primero de ellos es referente al tipo de
borde utilizado en el panel de contenido, como se puede observar al recuperar el panel de contenido
para asignarle un borde vacío se ha tenido que hacer un "cast" con la clase JPanel, este borde vacío se
ha utilizado para crear un margen entre el panel de contenido y el contenedor, en este caso una
instancia de la clase JFrame.
211
Programación en Java
© Grupo EIDOS
Figura 79
Al panel de contenido de la ventana se le ha asignado un gestor de diseño GridLayout con siete filas y
una única columna. Algunos de los métodos de la clase BorderFactory se encuentran sobrecargados,
para permitir una mayor personalización de los distintos tipos de bordes que queremos asignar.
A cada panel se le ha asignado un borde distinto y se le ha añadido una etiqueta (JLabel) con la
descripción del tipo de borde correspondiente.
JTabbedPane
Gracias a este contenedor intermedio podemos tener distintos componentes, normalmente otros
paneles, compartiendo un mismo espacio. El usuario puede visualizar los componentes que desea ver
seleccionando la pestaña correspondiente.
Para crear un contenedor de este tipo primero debemos instanciar el objeto correspondiente de la clase
JTabbedPane, luego se van creando los distintos componentes y se van añadiendo al contenedor
mediante el método addTab().
Antes de seguir con esta nueva clase vamos a verla en acción mediante un sencillo ejemplo. El
ejemplo consiste simplemente en un objeto JTabbedPane al que se le van añadiendo distintos paneles,
cada uno con sus componentes. El código se puede ver en el Código fuente 136.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
212
© Grupo EIDOS
11. Interfaces de usuario en Java: componentes Swing / contenedores
public class PanelTab{
public static void main(String s[]) {
JFrame ventana = new JFrame("Ventana Sencilla");
ImageIcon icono = new ImageIcon("icono.gif");
JTabbedPane panelTab = new JTabbedPane();
//tratamiento de eventos para el cierre de la ventana
ventana.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
//se van creando paneles con componentes y se añaden al objeto
JTabbedPane
JPanel panel1=new JPanel();
panel1.add(new JButton("Botón",icono));
//se crea una nueva pestaña con el nuevo componente
panelTab.addTab("Uno",icono,panel1,"Soy la primera pestaña");
panelTab.setSelectedIndex(0);
JPanel panel2=new JPanel();
panel2.add(new JLabel("Soy una etiqueta",JLabel.CENTER));
panelTab.addTab("Dos",icono,panel2,"Soy la segunda pestaña");
JPanel panel3=new JPanel();
panel3.add(new JLabel("Soy otra etiqueta",JLabel.CENTER));
panel3.setBorder(BorderFactory.createLineBorder(Color.red));
panelTab.addTab("Tres",icono,panel3,"Soy la tercera pestaña");
JPanel panel4=new JPanel();
panel4.add(new JToggleButton("Otro botón",icono));
panelTab.addTab("Cuatro",icono,panel4,"Soy la cuarta pestaña");
JPanel panel5=new JPanel();
panel5.add(new JSlider(JSlider.HORIZONTAL,0,30,10));
panelTab.addTab("Cinco",icono,panel5,"Soy la última pestaña");
//se le da un tamaño al contenedor de pestañas
panelTab.setPreferredSize(new Dimension(400,200));
//se añade a la ventana
ventana.getContentPane().add(panelTab);
ventana.pack();
ventana.setVisible(true);
}
}
Código fuente 136
Y el resultado de este código se puede apreciar en la Figura 80.
Figura 80
213
Programación en Java
© Grupo EIDOS
Si se prueba el ejemplo anterior se puede comprobar que no se ha escrito ningún código para realizar
el tratamiento de eventos, la clase JTabbedPane realiza este tratamiento (mostrar los componentes de
la pestaña seleccionada) de forma automática.
En el ejemplo se ha utilizado un constructor de la clase JTabbedPane que no utiliza ningún parámetro,
pero existe otro constructor que permite, mediante una constante que recibe como parámetro,
especificar la localización de las pestañas. Estas constantes se encuentran definidas en la clase
JTabbedPane y son las siguientes TOP, BOTTOM, LEFT y RIGHT. Así si modificamos el ejemplo
anterior cambiando el constructor utilizado para el panel JTabbedPane mediante la línea de código que
aparece en el Código fuente 137.
JTabbedPane panelTab = new JTabbedPane(JTabbedPane.BOTTOM);
Código fuente 137
Obtenemos el resultado que vemos en la Figura 81.
Figura 81
Para añadir una nueva pestaña ya sabemos que debemos utilizar el método addTab(), este método
presenta las siguientes versiones:
•
addTab(String texto, Component componente): el primer argumento indica el texto que va a
parecer en la pestaña y el componente que va a contener.
•
addTab(String texto, Icon icono, Component componente): en este caso además se indica el
icono que se va a mostrar en la pestaña.
•
addTab(String texto, Icon icono, Component componente, String ayuda): en la última versión
del método addTab() se permite especificar el texto que aparecerá a modo de ayuda (tooltip)
cuando situemos el puntero del ratón sobre la pestaña.
En el ejemplo se ha utilizado el método setSelectedIndex() para indicar la pestaña que por defecto se
encuentra seleccionada, que será la que mostrará sus componentes. Las pestañas comienzan a
numerarse en cero.
214
© Grupo EIDOS
11. Interfaces de usuario en Java: componentes Swing / contenedores
Para manipular las pestañas la clase JTabbedPane ofrece los siguientes métodos:
•
insertTab(String texto, Icon icono, Component componente, String ayuda, int índice): inserta
una nueva pestaña en la posición indicada por el parámetro índice.
•
removeTabAt(int índice): elimina la pestaña cuya posición coincida con la indicada.
•
remove(Component componente): elimina la pestaña que contenga el componente
especificado como argumento.
•
removeAll(): elimina todas las pestañas.
•
int indexOfTab(String texto): devuelve el índice de la pestaña que posea el texto indicado por
parámetro.
•
int getSelectedIndex(): devuelve el índice de la pestaña seleccionada actualmente.
También es posible modificar la apariencia de las pestañas del panel JTabbedPane. Podemos indicar el
icono que va a mostrar la pestaña según se encuentre habilitada o deshabilitada, también el color de
fondo y el del texto de la pestaña.
Así si una vez creado el panel de nuestro ejemplo añadimos las líneas de código que se muestran en el
Código fuente 138 y obtenemos el resultado que aparece en la Figura 82.
//color de fondo cuando no está seleccionada la pestaña
panelTab.setBackgroundAt(0,Color.red);
//color del texto
panelTab.setForegroundAt(0,Color.green);
ImageIcon otroIcono = new ImageIcon("icono2.gif");
//icono que se muestra cuando la pestaña está desactivada
panelTab.setDisabledIconAt(0,otroIcono);
//se modifica el título
panelTab.setTitleAt(2,"Otro texto");
JPanel panelNuevo=new JPanel();
panelNuevo.add(new JTextArea(5,30));
//se cambia el componente que posee la pestaña
panelTab.setComponentAt(3,panelNuevo);
Código fuente 138
Figura 82
215
Programación en Java
© Grupo EIDOS
Curioso resultado ya que no se muestra el icono que se utiliza para indicar que la pestaña está
deshabilitada.
Si utilizamos cualquiera de los métodos anteriores ,que tiene como parámetro el índice de la pestaña, y
la pestaña no existe se producirá una excepción.
JToolBar
Esta clase representa una barra de herramientas, normalmente este tipo de contenedor va a contener
botones con iconos dentro de una fila o columna. Estos botones cumplen las mismas funciones que las
opciones de menú.
Por defecto el usuario puede arrastrar la barra de herramientas y situarla en los diferentes bordes del
contenedor o bien como una ventana independiente. Para que este funcionamiento de arrastre de la
barra sea correcto, el contenedor en el que se sitúa la barra de herramientas debe tener un gestor de
diseño BorderLayout. Normalmente la barra de herramientas se añade en el norte del gestor de diseño
y el componente al que afecta en el centro, no debe existir ningún componente más en el centro del
contenedor.
En el Código fuente 139 se puede observar como se utiliza un objeto de la clase JToolBar. En nuestro
caso vamos a tener una ventana (JFrame) que va a tener un panel de contenido al que se va a añadir la
barra de herramientas (JToolBar) y un área de texto (JTextArea). Como se puede ver la barra de
herramientas puede contener otros tipos de componentes, no sólo botones.
import
import
import
public
216
java.awt.*;
java.awt.event.*;
javax.swing.*;
class BarraHerramientas{
public static void main(String s[]) {
JFrame ventana = new JFrame("Ventana con barra de herramientas");
//tratamiento de eventos para el cierre de la ventana
ventana.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
//iconos
ImageIcon icono1=new ImageIcon("icono1.gif");
ImageIcon icono2=new ImageIcon("icono2.gif");
ImageIcon icono3=new ImageIcon("icono3.gif");
//botones
JButton boton1=new JButton(icono1);
JButton boton2=new JButton(icono2);
JButton boton3=new JButton(icono3);
//lista con elementos
JComboBox combo=new JComboBox();
combo.addItem("uno");
combo.addItem("dos");
combo.addItem("tres");
//caja de texto
JTextField caja=new JTextField("caja de texto");
//barra de herramientas
JToolBar barra=new JToolBar();
//se añaden los botones
barra.add(boton1);
barra.add(boton2);
barra.add(boton3);
© Grupo EIDOS
11. Interfaces de usuario en Java: componentes Swing / contenedores
//se añade el separador a la barra de herramientas
barra.addSeparator();
barra.add(combo);
barra.add(caja);
//se añade la barra al panel de contenido
ventana.getContentPane().add(barra,BorderLayout.NORTH);
//se crea un área de texto
JTextArea areaTexto = new JTextArea(5,30);
//se añade a un panel de scroll
JScrollPane scrollPane = new JScrollPane(areaTexto);
//se añade el panel de scroll al panel de contenido
ventana.getContentPane().add(scrollPane,BorderLayout.CENTER);
//se asigna un tamaño preferido a la vetana
((JPanel)ventana.getContentPane()).setPreferredSize(new Dimension(400,
100));
ventana.pack();
ventana.setVisible(true);
}
}
Código fuente 139
El resultado se muestra en la Figura 83.
Figura 83
Como se puede comprobar en el código anterior, para añadir un separador a la barra de herramientas
se utiliza el método addSeparator(). Si queremos que la barra de herramientas permanezca fija se
puede lanzar sobre el objeto de la clase JToolBar el método setFloatable() pasando como argumento el
valor false.
Para posicionar los distintos elementos que contiene, la clase JToolBar utiliza el gestor de diseño
BoxLayout, que veremos más adelante en el apartado dedicado al mismo.
JLayeredPane
Este contenedor ofrece una tercera dimensión que permite posicionar los componentes que contiene
especificando una profundidad. La profundidad de un determinado componente se especifica mediante
un entero, cuanto mayor sea este entero mayor será la profundidad del componente correspondiente. Si
los componentes que contiene el panel de capas se superponen los componentes que se encuentran a
una mayor profundidad se muestran encima de los de una menor profundidad.
Vimos que los contenedores de alto nivel de Swing contienen un panel de raíz que a su vez contiene
un panel de capas (layered pane), normalmente no se suele utilizar el JLayeredPane del JRootPane,
sino que se crea un objeto JLayeredPane distinto para utilizarlo dentro de otro panel. Esto mismo
ocurre en nuestro ejemplo, que se sitúa un objeto de la clase JLayeredPane dentro del panel de
contenido de una ventana (JFrame).
217
Programación en Java
© Grupo EIDOS
Para añadir un componente a un JLayeredPane se utiliza el método add(), en este método se debe
indicar la profundidad del componente, es decir, la capa en la que se encuentra. En el siguiente
ejemplo se van añadiendo a una instancia de la clase JLayeredPane etiquetas con color de fondo a
distintas profundidades. Veamos el Código fuente 140.
import
import
import
public
java.awt.*;
java.awt.event.*;
javax.swing.*;
class PanelCapas{
static private String[] textosCapa = { "Amarillo(0)", "Magenta (1)",
"Azul (2)", "Rojo (3)",
"Verde (4)" };
static private Color[] coloresCapa = { Color.yellow, Color.magenta,
Color.blue, Color.red,
Color.green };
public static void main(String s[]) {
JFrame ventana = new JFrame("Ventana con panel de capas");
JLayeredPane panelCapas=new JLayeredPane();
//tratamiento de eventos para el cierre de la ventana
ventana.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
//punto de origen
Point origen = new Point(10, 20);
//separación entre etiquetas
int separacion = 35;
//se van creando las etiquetas de color a distinta profundidad
for (int i = 0; i < textosCapa.length; i++) {
JLabel etiqueta =
creaEtiqueta(textosCapa[i],coloresCapa[i],origen);
//se añade la etiqueta al panel de capas
panelCapas.add(etiqueta,new Integer(i));
origen.x += separacion;
origen.y += separacion;
}
//se indica un tamaño para le panel
panelCapas.setPreferredSize(new Dimension(300, 310));
//se añade el panel creado al panel de contenido de la ventana
ventana.getContentPane().add(panelCapas);
ventana.pack();
ventana.setVisible(true);
}
//método para la creación de etiquetas con un texto y un color en un punto
//de origen.
private static JLabel creaEtiqueta(String texto,Color color,Point origen) {
JLabel etiqueta = new JLabel(texto);
//se alinea el texto en la etiqueta
etiqueta.setVerticalAlignment(JLabel.TOP);
etiqueta.setHorizontalAlignment(JLabel.CENTER);
etiqueta.setOpaque(true);
etiqueta.setBackground(color);
//se asigna un borde a la etiqueta
etiqueta.setBorder(BorderFactory.createLineBorder(Color.black));
etiqueta.setBounds(origen.x, origen.y, 140, 140);
return etiqueta;
}
}
Código fuente 140
218
© Grupo EIDOS
11. Interfaces de usuario en Java: componentes Swing / contenedores
El aspecto de esta ventana es el de la Figura 84.
Figura 84
Como se puede comprobar a la vista del código se han creado dos arrays que contienen por un lado los
textos de las etiquetas y por otro los colores de las etiquetas. Se ha utilizado un bucle for para ir
recorriendo estos arrays y crear las etiquetas mediante el método crearEtiqueta(). Como se puede ver
el método y los atributos utilizado son estáticos, ya que los utilizamos directamente en el método
main() si lanzarlos sobre una instancia de una clase.
Dentro de una capa, es decir, a una profundidad determinada, se puede especificar la posición de un
componente. Es posible por lo tanto definir la posición de un componente respecto al resto de
componentes dentro de la misma capa, para ello existe una versión del método add() que posee un
tercer parámetro para indicar esta posición dentro de la capa.
El valor de esta posición va desde -1 hasta n-1, dónde n es el número de componentes dentro de la
capa. Al contrario que las capas, cuanto menor es el número mayor es la profundidad del componente
dentro de la capa. Utilizar -1 es equivalente a utilizar n-1, indica la posición más en el fondo. Si
utilizamos 0 el componente se encontrará por encima del resto.
Si al ejemplo anterior le añadimos las líneas que muestra el Código fuente 141, una vez creadas las
etiquetas en las distintas capas.
ImageIcon icono = new ImageIcon("imagen.gif");
JLabel nuevaEtiqueta=new JLabel(icono);
nuevaEtiqueta.setBounds(180,75,icono.getIconWidth(),icono.getIconHeight());
panelCapas.add(nuevaEtiqueta,new Integer(2),0);
Código fuente 141
219
Programación en Java
© Grupo EIDOS
Lo que se hace es añadir una nueva etiqueta con un icono en la capa 2 de modo que quede por encima
de la etiqueta de color que ya existía en esa misma capa. El nuevo aspecto del panel de capas es el que
aparece en la Figura 85.
Figura 85
También es posible mover un componente de una capa a otra, para ello se utiliza el método setLayer().
Así si queremos mover la etiqueta con el icono a la capa 4 escribiremos lo que indica el Código fuente
142.
panelCapas.setLayer(nuevaEtiqueta,4,0);
Código fuente 142
El último argumento del método setLayer() es la posición del componente dentro de la nueva capa.
Para mover un componente dentro de una capa a la primera posición se utiliza el método
moveToFront(), y para enviarlo al fondo el método moveToBack(), ambos métodos tiene como
parámetro el componente al que se quiere cambiar de posición dentro de una capa. Si utilizamos el
método moveToBack() en nuestro código anterior como indica el Código fuente 143, el nuevo aspecto
del ejemplo es el de la Figura 86.
panelCapas.moveToBack(nuevaEtiqueta);
Código fuente 143
220
© Grupo EIDOS
11. Interfaces de usuario en Java: componentes Swing / contenedores
Con este último tipo de contenedor intermedio damos por terminado este apartado y este capítulo, en
el siguiente capítulo seguimos tratando componentes Swing, en este caso los componentes atómicos, y
también veremos otras aportaciones de Swing.
Figura 86
221
Interfaces de usuario en Java:
componentes atómicos de Swing
Introducción
Dentro de este capítulo vamos a tratar un gran grupo de componentes Swing, los componentes
atómicos, no vamos a tratar todos ellos, ya que son muy numerosos y necesitaríamos como mínimo un
par de capítulos más.
Veremos que estos componentes atómicos se agrupan a su vez en tres categorías, veremos ejemplos
representativos de cada una de estas categorías.
Componentes atómicos
Este grupo de componentes se corresponde con aquellos componentes cuya función es presentar o
recibir información, en lugar de contener otros componentes, aunque es posible encontrar
componentes atómicos que son la combinación de distintos componentes. Un ejemplo de componente
atómico podría ser un botón, una caja de texto, una casilla de verificación, etc.
Todos los componentes atómicos heredan de la clase JComponent, debido a esto todos ellos soportan
características estándar de los componentes Swing, como pueden ser bordes y tooltips.
Los componentes atómicos se subclasifican atendiendo a la labor que realizan.
Componentes que cuya misión principal es la de obtener información relativa a la entrada del usuario.
Programación en Java
© Grupo EIDOS
•
JButton: un botón común.
•
JCheckBox: una casilla de verificación.
•
JRadioButton: un botón de opción que suelen utilizarse en grupos.
•
JMenuItem: un elemento de un menú.
•
JCheckBoxMenuItem: un elemento de menú que contiene una casilla de verificación.
•
JRadioButtonMenuItem: un elemento de menú que contiene un botón de opción.
•
JMenuBar: una barra de menú.
•
JMenu: una opción de menú.
•
JToggleButton: representa un botón con dos estados (pulsado/no pulsado).
•
JComboBox: una lista desplegable.
•
JList: una lista con elementos.
•
JSlider: permite seleccionar al usuario un valor dentro de un rango determinado.
•
JTextField: una caja de texto en la que el usuario puede escribir.
Componentes que existen únicamente para mostrar información:
•
JLabel: etiqueta que puede mostrar texto, un icono o ambos.
•
JProgressBar: barra que muestra el progreso de un proceso.
•
JToolTip: muestra una breve descripción de un componente, aunque veremos en los distintos
ejemplos que nunca vamos a utilizar directamente esta clase.
Componentes que muestran una información estructurada o que permiten editarla:
•
JColorChooser: permite realizar la selección de un color determinado.
•
JFileChooser: permite seleccionar ficheros y directorios.
•
JTable: muestra la información en formato de tabla.
•
JTextComponent: de esta clase heredan distintas clases especializadas en el tratamiento de
textos.
•
JTree: muestra datos organizados de forma jerárquica.
A lo largo de los siguientes apartados se van a mostrar algunos de estos componentes con sus
respectivos ejemplos.
224
© Grupo EIDOS
12. Interfaces de usuario en Java: componentes atómicos de Swing
Componentes para obtener información
Vamos a comenzar con algunos de los componentes atómicos de Swing cuya misión es la de obtener
información a través de la entrada del usuario.
JButton
Esta clase hereda de la clase AbstractButton, al igual que lo hacen otras clases como pueden ser
JCheckbox, JMenuItem o JToggleButton. Por lo tanto la clase JButton posee una serie de
funcionalidades que son comunes a todas las clases que heredan de la clase AbstractButton.
Un botón puede contener texto o imágenes o ambos elementos. El texto que contiene un botón se
puede alinear con respecto a la imagen, también se pueden especificar teclas de teclado alternativas,
que se indicarán mediante el subrayado de la letra del texto correspondiente.
Cuando un botón se encuentra deshabilitado el Look and Feel correspondiente genera de forma
automática el aspecto del botón. Sin embargo, se puede indicar una imagen para que se muestre
cuando el botón se encuentre deshabilitado.
Para ver la clase JButton en acción vamos a utilizar un ejemplo muy sencillo que consiste en mostrar
tres botones con imágenes y ayudas (tooltips), la pulsación de dos de los tres botones activará o
desactivará el botón central.
En el Código fuente 144 se muestra el código que crea estos botones y los añade a una ventana
(JFrame), y además registra los oyentes de eventos correspondientes. Se han utilizado tres métodos a
demás del método main(). El método creaBotones() instancia todas las instancias de los botones y los
configura convenientemente, el método añadeBotones() añade los botones al panel de contenido de la
ventana y el método actionPerformed() se encarga de los eventos de pulsación de los botones.
import
import
import
public
java.awt.*;
java.awt.event.*;
javax.swing.*;
class Botones extends JFrame implements ActionListener{
private JButton botonIzq;
private JButton botonDer;
private JButton botonCentro;
private ImageIcon iconoIzq=new ImageIcon("icono3.gif");
private ImageIcon iconoDer=new ImageIcon("icono1.gif");
private ImageIcon iconoCentro=new ImageIcon("icono2.gif");
public Botones (){
super("Ventana con botones");
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
public void creaBotones(){
//se instancia el botón indicando la imagen
botonIzq=new JButton("Desactiva botón central",iconoIzq);
//se da formato al texto
botonIzq.setVerticalTextPosition(AbstractButton.CENTER);
botonIzq.setHorizontalTextPosition(AbstractButton.LEFT);
//se indica la tecla asociada al botón
botonIzq.setMnemonic(KeyEvent.VK_D);
225
Programación en Java
© Grupo EIDOS
//se indica el comando de acción que se utiliza cuando se pulsa el
botón
botonIzq.setActionCommand("desactiva");
//se asigna un tooltip
botonIzq.setToolTipText("Desactivo el botón central");
botonIzq.setEnabled(false);
botonDer=new JButton("Activa botón central",iconoDer);
botonDer.setVerticalTextPosition(AbstractButton.CENTER);
botonDer.setHorizontalTextPosition(AbstractButton.RIGHT);
botonDer.setMnemonic(KeyEvent.VK_A);
botonDer.setActionCommand("activa");
botonDer.setToolTipText("Activo el botón central");
botonCentro=new JButton("Botón central",iconoCentro);
botonCentro.setEnabled(false);
botonCentro.setToolTipText("No hago nada");
//se registran los oyentes, que son la misma clase.
botonIzq.addActionListener(this);
botonDer.addActionListener(this);
}
public void añadeBotones(){
//creamos un panel para añadir los botones
JPanel panelContenido=new JPanel();
panelContenido.add(botonIzq);
panelContenido.add(botonCentro);
panelContenido.add(botonDer);
//establecemos este panel como panel de contenido de la ventana
setContentPane(panelContenido);
}
public void actionPerformed(ActionEvent evento){
if (evento.getActionCommand().equals("desactiva")){
//se desactiva el botón central y se actualiza
//el estado de los otros botones
botonCentro.setEnabled(false);
botonIzq.setEnabled(false);
botonDer.setEnabled(true);
}else{
//se activa el botón central
botonCentro.setEnabled(true);
botonIzq.setEnabled(true);
botonDer.setEnabled(false);
}
}
public static void main(String s[]) {
Botones ventana = new Botones();
ventana.creaBotones();
ventana.añadeBotones();
ventana.pack();
ventana.setVisible(true);
}
}
Código fuente 144
El aspecto que muestra la ventana se puede apreciar en la Figura 87.
Figura 87
226
© Grupo EIDOS
12. Interfaces de usuario en Java: componentes atómicos de Swing
Se puede comprobar si pulsamos las teclas Alt+D o Alt+A tienen el mismo efecto que pulsar el botón
correspondiente, además esta combinación de teclas aparece descrita en el tooltip.
Podemos indicar el botón activo por defecto de un contenedor de alto nivel mediante el método
setDefaultButton() de la clase JRootPane. El botón activo por defecto aparece destacado del resto y si
el usuario pulsa la tecla Enter es equivalente a pulsar este botón. Podemos modificar el ejemplo
anterior para que el botón activo por defecto sea el botón de la derecha.
getRootPane().setDefaultButton(botonDer);
Código fuente 145
Otra característica que nos ofrece la clase JButton es la posibilidad de utilizar etiquetas HTML dentro
del texto del botón, para ello se debe poner la etiqueta <html> al inicio del texto, y a continuación se
pueden utilizar las etiquetas HTML que se consideren necesarias para dar el formato conveniente al
texto.
Así por ejemplo, si retomamos nuestro código anterior y modificamos las líneas en las que se crean los
botones, escribiendo el Código fuente 146.
botonIzq=new JButton("<html><b><i><u>D</u>esactiva botón central</i><b>",iconoIzq);
..........
botonDer=new JButton("<html><font color='red' size='4'>"+
"<u>A</u>ctiva botón central</font>",iconoDer);
...........
botonCentro=new JButton("<html><small><i>Botón central</i></small>",iconoCentro);
Código fuente 146
Obtenemos la Figura 88.
Figura 88
Como se puede apreciar aunque los botones se encuentran desactivas, ahora el texto no aparece en
color gris atenuado. Además hemos tenido que utilizar la etiqueta de subrayado de HTML (<u></u>)
para indicar la tecla que se corresponde con cada botón.
JCheckbox
Esta clase representa a las casillas de verificación, también se pueden utilizar estas casillas de
verificación dentro de elementos de menú, mediante la clase JCheckBoxMenuItem. Debido a que la
clase JCheckBox hereda de la clase AbstractButton, presenta una serie de características comunes a
227
Programación en Java
© Grupo EIDOS
todos los tipos de botones, que ya hemos visto en la sección anterior con la clase JButton. Estas
características son la posibilidad de utilizar imágenes, tooltips, teclas alternativas, etc.
Las casillas de verificación se suelen agrupar y es posible seleccionar, una, algunas o ninguna de ellas.
En el siguiente ejemplo se muestra la utilización de la clase JCheckBox para configurar el aspecto de
un botón. Se dispone de tres casillas de verificación, una para indicar que el botón va a tener un icono,
otra para indicar que tiene un tooltip y otra para indicar que tiene texto. El código se muestra en el
Código fuente 147.
import
import
import
public
java.awt.*;
java.awt.event.*;
javax.swing.*;
class Casillas extends JFrame implements ItemListener{
//cada una de las casillas
private JCheckBox chkIcono;
private JCheckBox chkTexto;
private JCheckBox chkToolTip;
//el botón del que indicamos el aspecto
private JButton boton;
//panel de las casillas
private JPanel panelCasillas;
//panel del botón
private JPanel panelBoton;
private ImageIcon icono=new ImageIcon("icono2.gif");
public Casillas (){
super("Ventana con casillas de verificación");
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
getContentPane().setLayout(new GridLayout(1,2));
}
public void creaCasillas(){
panelCasillas=new JPanel();
panelCasillas.setLayout(new GridLayout(3,1));
chkIcono=new JCheckBox("Icono");
chkIcono.setMnemonic(KeyEvent.VK_I);
chkIcono.setSelected(true);
chkIcono.addItemListener(this);
panelCasillas.add(chkIcono);
chkTexto=new JCheckBox("Texto");
chkTexto.setMnemonic(KeyEvent.VK_T);
chkTexto.setSelected(true);
chkTexto.addItemListener(this);
panelCasillas.add(chkTexto);
chkToolTip=new JCheckBox("Tooltip");
chkToolTip.setMnemonic(KeyEvent.VK_L);
chkToolTip.setSelected(true);
chkToolTip.addItemListener(this);
panelCasillas.add(chkToolTip);
getContentPane().add(panelCasillas);
}
public void creaBoton(){
panelBoton=new JPanel();
panelBoton.setLayout(new FlowLayout());
boton=new JButton("Soy un botón",icono);
boton.setToolTipText("Soy un botón");
panelBoton.add(boton);
getContentPane().add(panelBoton);
}
228
© Grupo EIDOS
12. Interfaces de usuario en Java: componentes atómicos de Swing
public void itemStateChanged(ItemEvent evento) {
Object fuente=evento.getSource();
int estado=evento.getStateChange();
if (fuente==chkIcono){
if(estado==ItemEvent.DESELECTED)
boton.setIcon(null);
else
boton.setIcon(icono);
}
if (fuente==chkTexto){
if(estado==ItemEvent.DESELECTED)
boton.setText("");
else
boton.setText("Soy un botón");
}
if (fuente==chkToolTip){
if(estado==ItemEvent.DESELECTED)
boton.setToolTipText("");
else
boton.setToolTipText("Soy un botón");
}
}
public static void main(String s[]) {
Casillas ventana = new Casillas();
ventana.creaCasillas();
ventana.creaBoton();
ventana.pack();
ventana.setVisible(true);
}
}
Código fuente 147
Y el resultado es el de la Figura 89.
Figura 89
Como se puede observar la clase JButton lanza un evento del tipo ItemEvent cuando se modifica el
estado de un objeto. En el método itemStateChanged() debemos averiguar primero la fuente del
evento, es decir, la casilla que ha visto modificado su estado, y a continuación si la casilla en cuestión
ha sido seleccionada o no.
JRadioButton
Los botones de opción se suelen encontrar en grupos en los que, por convención, únicamente uno de
los botones puede encontrarse seleccionado a un mismo tiempo. También podemos utilizar botones de
opciones en elementos de menú mediante la clase JRadioButtonMenuItem.
229
Programación en Java
© Grupo EIDOS
La clase JRadioButton también tiene como superclase o clase padre a la clase AbstractButton, por lo
tanto presentará el comportamiento común a todas las clases que heredan de AbstractButton, por
ejemplo podemos indicar que un objeto JRadioButton muestre una imagen o un tooltip.
Para esta clase vamos a mostrar un ejemplo que ofrecerá un dibujo de un animal distinto según el
botón de opción que se encuentre seleccionado en cada momento. Para agrupar los botones se utiliza
la clase ButtonGroup.
Nuestro ejemplo va a contener tres botones de opción agrupados para mostrar tres dibujos distintos. En
el Código fuente 148 se va a mostrar el fuente correspondiente.
import
import
import
public
java.awt.*;
java.awt.event.*;
javax.swing.*;
class Opciones extends JFrame implements ActionListener{
//cada una de las Opciones
private JRadioButton opGato;
private JRadioButton opCerdo;
private JRadioButton opConejo;
//imagen
private JLabel imagen;
//panel de las Opciones
private JPanel panelOpciones;
//panel de la imagen
private JPanel panelImagen;
public Opciones (){
super("Ventana con opciones");
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
getContentPane().setLayout(new GridLayout(1,2));
}
public void creaOpciones(){
panelOpciones=new JPanel();
panelOpciones.setLayout(new GridLayout(3,1));
opGato=new JRadioButton("Gato");
opGato.setMnemonic(KeyEvent.VK_G);
opGato.addActionListener(this);
opGato.setActionCommand("gato.gif");
panelOpciones.add(opGato);
opCerdo=new JRadioButton("Cerdo");
opCerdo.setMnemonic(KeyEvent.VK_C);
opCerdo.setSelected(true);
opCerdo.addActionListener(this);
opCerdo.setActionCommand("cerdo.gif");
panelOpciones.add(opCerdo);
opConejo=new JRadioButton("Conejo");
opConejo.setMnemonic(KeyEvent.VK_J);
opConejo.addActionListener(this);
opConejo.setActionCommand("conejo.gif");
panelOpciones.add(opConejo);
//se agrupan las opciones
ButtonGroup grupo=new ButtonGroup();
grupo.add(opGato);
grupo.add(opCerdo);
grupo.add(opConejo);
getContentPane().add(panelOpciones);
}
public void creaImagen(){
panelImagen=new JPanel();
230
© Grupo EIDOS
12. Interfaces de usuario en Java: componentes atómicos de Swing
panelImagen.setLayout(new BorderLayout());
imagen = new JLabel(new ImageIcon("cerdo.gif"));
imagen.setPreferredSize(new Dimension(177, 122));
panelImagen.add(imagen,BorderLayout.CENTER);
getContentPane().add(panelImagen);
}
public void actionPerformed(ActionEvent evento){
String comando=evento.getActionCommand();
imagen.setIcon(new ImageIcon(comando));
}
public static void main(String s[]) {
Opciones ventana = new Opciones();
ventana.creaOpciones();
ventana.creaImagen();
ventana.pack();
ventana.setVisible(true);
}
}
Código fuente 148
El resultado se puede comprobar en la Figura 90.
Figura 90
La clase JRadioButton, en lo que a tratamiento de eventos se refiere, funciona igual que la clase
JButton, el botón que se pulse (seleccione) va a lanzar un evento de la clase ActionEvent, para
diferenciar entre los distintos botones se utiliza el ActionCommand del evento ActionEvent. Para
mostrar la imagen se utiliza una etiqueta (JLabel) con un icono (ImageIcon).
JComboBox
Esta clase representa una lista desplegable de opciones, que puede ser editable o no. Cuando el usuario
pulsa la lista, el objeto JComboBox muestra un menú de elementos para elegir.
Una lista desplegable editable es similar a una caja de texto (JTextField) con un pequeño botón. El
usuario puede escribir un valor en la caja de texto o elegir un valor del menú.
En nuestro caso vamos a ver un ejemplo de una lista desplegable no editable. El ejemplo va a tener la
misma funcionalidad que el utilizado para la clase JRadioButton, es decir, vamos a tener distintas
opciones que van a mostrar un dibujo determinado.
El código fuente completo del ejemplo es el Código fuente 149, y el resultado se muestra en la Figura
91.
231
Programación en Java
import
import
import
public
© Grupo EIDOS
java.awt.*;
java.awt.event.*;
javax.swing.*;
class Combo extends JFrame implements ActionListener{
//lista desplegable
private JComboBox listaOpciones;
//opciones de la lista
String[] opciones={"Gato","Cerdo","Conejo"};
//imagen
private JLabel imagen;
public Combo(){
super("Ventana con lista desplegable");
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
public void creaCombo(){
listaOpciones=new JComboBox(opciones);
listaOpciones.setSelectedIndex(0);
getContentPane().add(listaOpciones,BorderLayout.NORTH);
listaOpciones.addActionListener(this);
}
public void creaImagen(){
imagen = new JLabel(new ImageIcon("gato.gif"));
imagen.setPreferredSize(new Dimension(177, 122));
getContentPane().add(imagen,BorderLayout.CENTER);
}
public void actionPerformed(ActionEvent evento){
JComboBox fuente=(JComboBox)evento.getSource();
//se obtiene el elemento seleccionado
String seleccion=(String)fuente.getSelectedItem();
//se cambia la imagen
imagen.setIcon(new ImageIcon(seleccion+".gif"));
}
public static void main(String s[]) {
Combo ventana = new Combo();
ventana.creaCombo();
ventana.creaImagen();
ventana.pack();
ventana.setVisible(true);
}
}
Código fuente 149
Figura 91
232
© Grupo EIDOS
12. Interfaces de usuario en Java: componentes atómicos de Swing
Al seleccionar un elemento del objeto JComBox se lanza un evento ActionEvent, es decir, el mismo
evento que se lanza cuando se pulsa un botón.
El constructor utilizado para instanciar un objeto de la clase JComboBox recibe como argumento un
array de cadenas (String), que representa las opciones que muestra la lista desplegable.
Para indicar la opción seleccionada por defecto utilizamos el método setSelectedIndex() sobre el
objeto de la clase JComboBox, y para obtener la opción seleccionada y así mostrar la imagen
correspondiente se utiliza el método getSelectedItem() de la clase JComboBox.
JMenu
Un menú permite elegir al usuario entre múltiples opciones disponibles. Los menús parecen
normalmente dentro de barras de menú o como menús contextuales (menú popup).
Un elemento de menú hereda también de la clase AbstractButton, como muchas de las clases que
hemos visto hasta ahora, veamos en la Figura 92, la jerarquía que presentan los distintos componentes
Swing relacionados con la creación de menús.
Figura 92
A continuación se comentan cada una de estas clases.
•
JMenuBar: representa la barra de menú que va a contener los distintos elementos de menú,
que serán objetos de la clase JMenu.
•
JMenu: es una opción de menú determinada, que contiene varios elementos de menú, que
serán objetos de la clase JMenuItem, y también puede contener submenús, es decir, objetos de
la clase JMenu.
•
JMenuItem: es un elemento de menú.
•
JCheckBoxMenuItem: elemento de menú que posee una casilla de verificación.
233
Programación en Java
© Grupo EIDOS
•
JRadioButtonMenuItem: elemento de menú que posee un botón de opción.
•
JSeparator: elemento de menú especial que ofrece una separación entre elementos de menú de
una misma opción de menú.
•
JPopupMenu: representa un menú contextual o de aparición súbita, contiene elementos de
menú (JMenuItem).
En el Código fuente 150 se muestra la utilización de todas estas clases, además se va mostrando en un
área de texto (JTextArea) los distintos eventos que van generando los distintos componentes de menú
junto con la fuente que ha producido dichos eventos. Como ocurre en estos casos primero se muestra
el código fuente del ejemplo.
import
import
import
public
java.awt.*;
java.awt.event.*;
javax.swing.*;
class Menus extends JFrame implements ActionListener, ItemListener{
JMenuBar barraMenu;
JMenu menu, submenu;
JMenuItem elementoMenu;
JRadioButtonMenuItem rbElementoMenu;
JCheckBoxMenuItem cbElementoMenu;
JTextArea texto;
JScrollPane panelScroll;
public Menus(){
super("Ventana con múltiples menús");
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
public void creaMenu(){
//Se crea la barra de menú
barraMenu = new JMenuBar();
setJMenuBar(barraMenu);
//se crea el primer menú
menu = new JMenu("Un Menú");
menu.setMnemonic(KeyEvent.VK_E);
barraMenu.add(menu);
//unos cuantos elementos de menú
elementoMenu=new JMenuItem("Elemento de menú de texto",KeyEvent.VK_E);
elementoMenu.addActionListener(this);
menu.add(elementoMenu);
//se asigna tecla de acceso rápido
elementoMenu.setAccelerator(KeyStroke.getKeyStroke(
KeyEvent.VK_1, ActionEvent.ALT_MASK));
elementoMenu=new JMenuItem("Texto e icono",new
ImageIcon("icono2.gif"));
elementoMenu.setMnemonic(KeyEvent.VK_T);
elementoMenu.addActionListener(this);
menu.add(elementoMenu);
elementoMenu=new JMenuItem(new ImageIcon("icono2.gif"));
elementoMenu.setMnemonic(KeyEvent.VK_D);
elementoMenu.addActionListener(this);
menu.add(elementoMenu);
//se añade un separador
menu.addSeparator();
//un grupo de elementos de menú de botones de opción.
ButtonGroup grupo=new ButtonGroup();
rbElementoMenu=new JRadioButtonMenuItem("Botón de opción");
234
© Grupo EIDOS
12. Interfaces de usuario en Java: componentes atómicos de Swing
rbElementoMenu.setSelected(true);
rbElementoMenu.setMnemonic(KeyEvent.VK_O);
grupo.add(rbElementoMenu);
rbElementoMenu.addActionListener(this);
menu.add(rbElementoMenu);
rbElementoMenu= new JRadioButtonMenuItem("Otro botón de opción");
rbElementoMenu.setMnemonic(KeyEvent.VK_B);
grupo.add(rbElementoMenu);
rbElementoMenu.addActionListener(this);
menu.add(rbElementoMenu);
//Un grupo de casillas de verificación
menu.addSeparator();
cbElementoMenu=new JCheckBoxMenuItem("Casilla de verificación");
cbElementoMenu.setMnemonic(KeyEvent.VK_C);
cbElementoMenu.addItemListener(this);
menu.add(cbElementoMenu);
cbElementoMenu=new JCheckBoxMenuItem("Otro más");
cbElementoMenu.setMnemonic(KeyEvent.VK_M);
cbElementoMenu.addItemListener(this);
menu.add(cbElementoMenu);
//un submenú
menu.addSeparator();
submenu=new JMenu("Un submenú");
submenu.setMnemonic(KeyEvent.VK_S);
elementoMenu=new JMenuItem("Un elemento de menú del submenú");
elementoMenu.setAccelerator(KeyStroke.getKeyStroke(
KeyEvent.VK_2, ActionEvent.ALT_MASK));
elementoMenu.addActionListener(this);
submenu.add(elementoMenu);
elementoMenu= new JMenuItem("Otro elemento de menú");
elementoMenu.addActionListener(this);
submenu.add(elementoMenu);
menu.add(submenu);
//Segúndo menú de la barra de menú
menu = new JMenu("Otro Menú");
menu.setMnemonic(KeyEvent.VK_M);
barraMenu.add(menu);
}
public void creaTexto(){
texto= new JTextArea(10, 50);
texto.setEditable(false);
panelScroll= new JScrollPane(texto);
getContentPane().add(panelScroll, BorderLayout.CENTER);
}
public void actionPerformed(ActionEvent evento){
JMenuItem fuente=(JMenuItem)(evento.getSource());
String mensaje= "ActionEvent detectado.\n"
+ "
Fuente del evento: " + fuente.getText()+"\n";
texto.append(mensaje);
}
public void itemStateChanged(ItemEvent evento) {
String estado="";
JMenuItem fuente =(JMenuItem)(evento.getSource());
if (evento.getStateChange()==ItemEvent.SELECTED)
estado="Seleccionado";
else
estado="No seleccionado";
String mensaje="ItemEvent detectado.\n"
+ "
Fuente del evento: " + fuente.getText()+"\n"
+ "
Nuevo estado: "+estado+"\n";
texto.append(mensaje);
}
public static void main(String s[]) {
Menus ventana = new Menus();
ventana.creaMenu();
ventana.creaTexto();
ventana.pack();
235
Programación en Java
© Grupo EIDOS
ventana.setVisible(true);
}
}
Código fuente 150
Y a continuación un ejemplo de ejecución.
Figura 93
En la Figura 94 se puede ver más clara toda la estructura del menú.
Figura 94
Los elementos de menú lanzan eventos ActionEvent, y los elementos de menú del tipo
JCheckBoxMenuItem lanzan eventos de la clase ItemEvent.
El área de texto (JTextArea) encargada de ir mostrando los eventos que se producen se añade a un
panel de scroll (ScrollPane).
Como se puede ver se han añadido dos teclas rápidas mediante el método setAccelerator().
236
© Grupo EIDOS
12. Interfaces de usuario en Java: componentes atómicos de Swing
Un contextual o de aparición súbita (popup) se encuentra representado por la clase JPopupMenu y
debe registrarse un oyente de ratón en cada componente que tenga asociado el menú popup, el oyente
debe detectar que el usuario a pulsado el botón derecho del ratón para mostrar el menú contextual
correspondiente.
El oyente que se ocupa de mostrar el menú contextual lanza el método show() sobre la instancia
correspondiente de la clase JPopupMenu. El método show() recibe como parámetros el componente al
que se asocia el menú y las coordenadas de la pantalla en la que se quiere mostrar el menú contextual.
Vamos a modificar el ejemplo anterior en el que utilizábamos la barra de menú y vamos a añadir un
menú contextual (JPopupMenu) que se asociará al área de texto (JTextArea) en la que se muestran los
eventos lanzados por los distintos elementos de menú.
Se va añadir un nuevo atributo a nuestra clase, llamado menuPopup que pertenece a la clase
JPopupMenu. También se va a añadir un nuevo método llamado creaPopup() y cuyo código se
muestra en el Código fuente 151.
public void creaPopup(){
menuPopup=new JPopupMenu();
//se crean y añadden los distinos elementos de menú de la misma manera
elementoMenu=new JMenuItem("Primer elemento del popup",KeyEvent.VK_P);
elementoMenu.addActionListener(this);
menuPopup.add(elementoMenu);
elementoMenu=new JMenuItem("Segundo del popup",new
ImageIcon("icono2.gif"));
elementoMenu.setMnemonic(KeyEvent.VK_S);
elementoMenu.addActionListener(this);
menuPopup.add(elementoMenu);
menuPopup.addSeparator();
cbElementoMenu=new JCheckBoxMenuItem("Tercer elemento");
cbElementoMenu.setMnemonic(KeyEvent.VK_T);
cbElementoMenu.addItemListener(this);
menuPopup.add(cbElementoMenu);
//se crea el oyente y se registra para el área de texto
PopupListener oyente=new PopupListener();
texto.addMouseListener(oyente);
}
Código fuente 151
Este método se puede lanzar una vez utilizado el método creaTexto().
Como se puede observar se crea una instancia de un objeto de la clase PopupListener, esta clase es una
clase interna que hereda de la clase MouseAdapter y que va a ser el oyente de nuestro menú contextual
y el que va a mostrarlo. El código de esta clase adaptadora interna es el Código fuente 152.
class PopupListener extends MouseAdapter{
public void mousePressed(MouseEvent evento){
menuPopup.show(evento.getComponent(),evento.getX(),evento.getY());
}
}
Código fuente 152
237
Programación en Java
© Grupo EIDOS
Y un ejemplo de la utilización del menú contextual se puede observar en la Figura 9.
Figura 9
Como se puede comprobar a la vista de la figura anterior, seguimos recogiendo los eventos de los
distintos elementos de menú, incluso del menú contextual.
Pero si el lector prueba este ejemplo comprobará que el menú contextual parecerá también cuando se
pulse el botón izquierdo del ratón, y no sólo cuando se pulse el botón derecho, que sería lo deseable.
La clase MouseEvent ofrece el método isPopupTrigger() para averiguar si debemos mostrar el menú
popup o no.
El método isPopupTrigger() devolverá verdadero si la pulsación del ratón se corresponde con un
evento que debe mostrar un menú contextual, en el caso de Windows se corresponde con el botón
derecho del ratón. Así nuestro código fuente del adaptador del ratón quedaría como se muestra en el
Código fuente 10.
class PopupListener extends MouseAdapter{
public void mousePressed(MouseEvent evento){
if (evento.isPopupTrigger()){
menuPopup.show(evento.getComponent(),evento.getX(),evento.getY());
}
}
}
Código fuente 10
La sorpresa es que ahora no se muestra nunca el menú contextual, no con le botón derecho ni con el
botón derecho del ratón, la verdad no he conseguido discernir porque no aparece. No vamos a mostrar
de nuevo como quedaría todo el código fuente completo del ejemplo modificado, pero en el siguiente
enlace se puede obtener.
JSlider
Este componente permite al usuario seleccionar un valor numérico entre un rango determinado, este
componente se utiliza para restringir los valores que puede ofrecer el usuario y así evitar errores y el
tratamiento de los mismos.
238
© Grupo EIDOS
12. Interfaces de usuario en Java: componentes atómicos de Swing
Para mostrar el funcionamiento de este componente Swing se ha utilizado un ejemplo en el que según
se indique en el selector (JSlider) se dará un tamaño determinado a un botón (JButton), el botón se
redimensionará según se indique en el selector.
Ahora vamos a ver el código, en el Código fuente 154, y más tarde lo comentaremos.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
//eventos nuevos de Swing
import javax.swing.event.*;
public class Selector extends JFrame implements ChangeListener{
//selector de valores
private JSlider selector;
private JButton boton;
public Selector(){
super("Ventana con selector (JSlider)");
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
public void creaSelector(){
selector=new JSlider(JSlider.HORIZONTAL,0,300,50);
selector.addChangeListener(this);
selector.setMajorTickSpacing(100);
selector.setMinorTickSpacing(10);
selector.setPaintTicks(true);
selector.setPaintLabels(true);
getContentPane().add(selector,BorderLayout.NORTH);
}
public void creaBoton(){
boton=new JButton("Soy un botón");
JPanel panel=new JPanel();
panel.setLayout(new FlowLayout());
panel.add(boton);
getContentPane().add(panel,BorderLayout.CENTER);
boton.setSize(50,50);
}
public void stateChanged(ChangeEvent evento){
JSlider fuente=(JSlider)evento.getSource();
if (!fuente.getValueIsAdjusting()){
boton.setSize((int)fuente.getValue(),(int)fuente.getValue());
}
}
public static void main(String s[]) {
Selector ventana = new Selector();
ventana.creaSelector();
ventana.creaBoton();
ventana.setSize(310,310);
ventana.setVisible(true);
}
}
Código fuente 154
Un ejemplo de la ejecución de esta sencilla aplicación la tenemos en la Figura 96.
239
Programación en Java
© Grupo EIDOS
Figura 96
A la vista del código se pueden hacer los siguientes comentarios y apreciaciones.
El constructor utilizado para crear el objeto de la clase JSlider, posee un argumento para indicar la
orientación del selector correspondiente, se corresponde con las constantes HORIZONTAL y
VERTICAL de la clase JSlider. Los siguientes argumentos, especifican respectivamente, el valor
mínimo, el valor máximo y el valor seleccionado inicialmente del objeto JSlider. Si no indicamos
estos valores, el valor mínimo del selector será 0, el máximo 100 y el seleccionado inicialmente 50.
Para configurar inicialmente el objeto de la clase JSlider, hemos utilizado una serie de métodos:
•
setMajorTickSpacing(int espaciado): este método nos permite indicar las separaciones
mayores entre el máximo y el mínimo del selector.
•
setMinorTickSpacing(int espaciado); este método indica las separaciones menores entre el
rango indicado para el componente Swing JSlider.
•
setPaintTicks(boolean): mediante este método indicamos si deseamos o no que parezcan las
separaciones del objeto JSlider.
•
setPaintLabels(boolean): nos permite indicar si queremos que se pinten las diferentes etiquetas
del rango.
Para registrar el oyente del selector hemos utilizado el método addChangeListener(), ya que la clase
JSlider lanza eventos de la clase ChangeEvent. Este es un nuevo tipo de evento que se incluye en el
paquete javax.swing.event y se lanza cuando se modifica el valor actual de un objeto de la clase
JSlider.
El método que debe implementar un oyente de eventos ChangeEvent es el método stateChanged(). En
el ejemplo anterior hemos visto que se ha utilizado el método getValueIsAdjusting() de la clase
JSlider, este método devuelve verdadero mientras se esté seleccionando el valor del objeto JSlider, de
esta forma no se redimensionará el botón hasta que el usuario no haya finalizado de arrastrar el tirador
del selector, es decir, cuando el usuario ha finalizado con el proceso de selección.
240
© Grupo EIDOS
12. Interfaces de usuario en Java: componentes atómicos de Swing
Las etiquetas que aparecen en el selector las podemos personalizar con la ayuda del método
setLabelTable() de la clase JSlider y de un objeto de la clase Hashtable. Para utilizar la clase Hashtable
debemos importar el paquete
Primero crearemos un objeto que representa una tabla hash y mediante el método put() vamos
indicando la posición que queremos que ocupe la etiqueta y el objeto JLabel que va a mostrar el texto
correspondiente. Veamos, en el Código fuente 155, un sencillo código que podemos añadir a nuestro
ya conocido ejemplo para mostrar cuatro etiquetas personalizadas.
public void creaSelector(){
selector=new JSlider(JSlider.HORIZONTAL,0,300,50);
selector.addChangeListener(this);
selector.setMajorTickSpacing(100);
selector.setPaintTicks(true);
//tabla de etiquetas
Hashtable etiquetas=new Hashtable();
etiquetas.put(new Integer(0),new JLabel("Minúsculo"));
etiquetas.put(new Integer(100),new JLabel("Pequeño"));
etiquetas.put(new Integer(200),new JLabel("Mediano"));
etiquetas.put(new Integer(300),new JLabel("Grande"));
//asignamos las etiquetas al selector
selector.setLabelTable(etiquetas);
selector.setPaintLabels(true);
getContentPane().add(selector,BorderLayout.NORTH);
}
Código fuente 155
Si utilizamos esta nueva versión del método creaSelector() obtenemos el resultado que se muestra en
la Figura 97.
Figura 97
241
Programación en Java
© Grupo EIDOS
Con la clase JSlider se da por terminado el apartado dedicado al grupo de componentes Swing cuya
función es la de recoger la entrada del usuario, a continuación vamos a comentar algunos componentes
atómicos cuya función es la de simplemente mostrar información al usuario, algunos de los
representantes de este grupo que vamos a tratar a continuación ya los conoceremos de ejemplos
anteriores, como ocurre con el componente JLabel.
Componentes para mostrar información
Se ofrecen distintos ejemplos de componentes Swing encargados de mostrar información.
JLabel
Ya hemos utilizado numerosas veces este útil y sencillo componente de Swing, como ya sabemos su
labor es la de mostrar una información al usuario, ya sea textual o con imágenes o con ambos
elementos.
En el siguiente ejemplo se muestra la utilización de la clase JLabel creando cuatro objetos distintos de
esta clase, se ha añadido un borde a cada etiqueta para que se distingan claramente unas de otras. El
código es el que aparece en el Código fuente 156.
import
import
import
public
java.awt.*;
java.awt.event.*;
javax.swing.*;
class Etiquetas extends JFrame{
private JLabel etiqueta1;
private JLabel etiqueta2;
private JLabel etiqueta3;
private JLabel etiqueta4;
private ImageIcon icono;
public Etiquetas(){
super("Ventana con etiquetas");
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
public void creaEtiquetas(){
icono=new ImageIcon("icono2.gif");
etiqueta1=new JLabel("Imagen y texto",icono,JLabel.CENTER);
etiqueta1.setVerticalTextPosition(JLabel.BOTTOM);
etiqueta1.setHorizontalTextPosition(JLabel.CENTER);
etiqueta1.setBorder(BorderFactory.createLineBorder(Color.red));
etiqueta2=new JLabel("Sólo texto");
etiqueta2.setHorizontalAlignment(JLabel.RIGHT);
etiqueta2.setVerticalAlignment(JLabel.TOP);
etiqueta2.setBorder(BorderFactory.createLineBorder(Color.blue));
etiqueta3=new JLabel(icono);
etiqueta3.setBorder(BorderFactory.createLineBorder(Color.green));
etiqueta4=new JLabel("Imagen y texto",icono,JLabel.CENTER);
etiqueta4.setVerticalTextPosition(JLabel.TOP);
etiqueta4.setHorizontalTextPosition(JLabel.CENTER);
etiqueta4.setBorder(BorderFactory.createLineBorder(Color.pink));
}
public void añadeEtiquetas(){
getContentPane().setLayout(new GridLayout(1,4,5,5));
242
© Grupo EIDOS
12. Interfaces de usuario en Java: componentes atómicos de Swing
getContentPane().add(etiqueta1);
getContentPane().add(etiqueta2);
getContentPane().add(etiqueta3);
getContentPane().add(etiqueta4);
}
public static void main(String s[]) {
Etiquetas ventana = new Etiquetas();
ventana.creaEtiquetas();
ventana.añadeEtiquetas();
ventana.pack();
ventana.setVisible(true);
}
}
Código fuente 156
Y el resultado se puede comprobar en la Figura 98.
Figura 98
Como se puede observar en el ejemplo anterior es posible especificar la alineación del texto respecto a
la imagen que contiene la etiqueta utilizando el método setVerticalTextPosition() y
setHorizontalTextPostion(), y mediante las constantes LEFT, CENTER, RIGHT, TOP y BOTTOM. El
valor por defecto es centrado (CENTER).
También se puede indicar la alineación de los componentes dentro de la etiqueta con los métodos
setHorizontalAlignment() y setVerticalAlignment().
Al igual que sucedía con la clase JButton, con los componentes JLabel podemos utilizar código
HTML para indicar el formato del texto que se va a mostrar. Veamos un ejemplo, en el Código fuente
157, que hace uso de esta característica. El aspecto de las etiquetas es el que muestra la Figura 99.
import
import
import
public
java.awt.*;
java.awt.event.*;
javax.swing.*;
class Etiquetas extends JFrame{
private JLabel etiqueta1;
private JLabel etiqueta2;
private JLabel etiqueta3;
private JLabel etiqueta4;
private ImageIcon icono;
public Etiquetas(){
super("Ventana con etiquetas");
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
public void creaEtiquetas(){
icono=new ImageIcon("icono2.gif");
243
Programación en Java
© Grupo EIDOS
etiqueta1=new JLabel("<html><font size='4' color='red'><b><i>"+
"Imagen y texto</b></i></font>",icono,JLabel.CENTER);
etiqueta1.setVerticalTextPosition(JLabel.BOTTOM);
etiqueta1.setHorizontalTextPosition(JLabel.CENTER);
etiqueta1.setBorder(BorderFactory.createLineBorder(Color.red));
etiqueta2=new JLabel("<html><ul><li>Elemento 1<li>Elemento2</ul>");
etiqueta2.setBorder(BorderFactory.createLineBorder(Color.blue));
etiqueta3=new JLabel("<html><table border='1'><tr><td>Celda 1</td>"+
"<td>Celda2</td></tr><tr><td align='center' colspan='2'>Celda
3</td>"+"
</tr></table>", JLabel.CENTER);
etiqueta3.setBorder(BorderFactory.createLineBorder(Color.green));
etiqueta4=new JLabel("<html><h2><i>Imagen y texto</i></h2><hr>",
icono,JLabel.CENTER);
etiqueta4.setVerticalTextPosition(JLabel.TOP);
etiqueta4.setHorizontalTextPosition(JLabel.CENTER);
etiqueta4.setBorder(BorderFactory.createLineBorder(Color.pink));
}
public void añadeEtiquetas(){
getContentPane().setLayout(new GridLayout(4,1,15,15));
getContentPane().add(etiqueta1);
getContentPane().add(etiqueta2);
getContentPane().add(etiqueta3);
getContentPane().add(etiqueta4);
}
public static void main(String s[]) {
Etiquetas ventana = new Etiquetas();
ventana.creaEtiquetas();
ventana.añadeEtiquetas();
ventana.setSize(300,450);
ventana.setVisible(true);
}
}
Código fuente 157
Figura 99
244
© Grupo EIDOS
12. Interfaces de usuario en Java: componentes atómicos de Swing
JToolTip
Dentro del grupo de componentes atómicos de Swing encargados exclusivamente de mostrar
información, también podemos encontrar la clase JToolTip, esta clase representa las pequeñas ayudas
que se muestran en forma de pequeño texto explicativo (tooltip) cuando nos situamos sobre un
componente determinado.
Aunque realmente no vamos a utilizar nunca directamente la clase JToolTip, sino que utilizaremos los
métodos setToolTipText() y getTootTipText() de la clase JComponent, ya hemos visto la utilización
de estos métodos en ejemplos anteriores, con el primero asignamos un tooltip a un componente Swing
y con el segundo obtenemos el tooltip que tiene asignando un componente Swing determinado.
JProgressBar
Este componente Swing muestra gráficamente el progreso de una operación determinada, la barra de
progreso va mostrando el porcentaje de tarea que se ha realizado, nos va informando de la evolución
de un proceso.
Al crear una instancia de la clase JProgressBar podemos indicar la orientación que va a tener la barra
de progreso, para ello se utilizan las constantes HORIZONTAL y VERTICAL de la clase
JProgressBar. También podemos indicar el máximo y el mínimo de la barra de progreso. Los máximos
y mínimos los podemos manipular con los métodos getMinumum()/setMinimum() y
getMaximum()/setMaximum().
Para incrementar el valor actual de la barra de progreso, que se corresponde con el proceso actual,
disponemos del método setValue().
A continuación se ofrece un sencillo ejemplo que consiste en ir pulsando un botón que incrementará
por cada pulsación el valor actual de la barra de progreso. Al llegar al máximo se emitirá un sonido y
después se volverá la barra de progreso a su estado inicial.
El código que presenta este ejemplo es el Código fuente 158.
import
import
import
public
java.awt.*;
java.awt.event.*;
javax.swing.*;
class BarraProgreso extends JFrame implements ActionListener{
private JProgressBar barra;
//valor que se va incrementando
private int valor;
private JButton boton;
public BarraProgreso(){
super("Ventana con barra de progreso");
this.valor=0;
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
public void creaBarra(){
//una barra de mínimo 0 y máximo 100
barra=new JProgressBar(JProgressBar.HORIZONTAL,0,100);
//valor inicial
245
Programación en Java
© Grupo EIDOS
barra.setValue(0);
//indicamos que aparecen las etiquetas del progreso
barra.setStringPainted(true);
getContentPane().add(barra,BorderLayout.NORTH);
}
public void creaBoton(){
boton=new JButton("Incrementar");
JPanel panel=new JPanel();
panel.setLayout(new FlowLayout());
panel.add(boton);
boton.addActionListener(this);
getContentPane().add(panel,BorderLayout.CENTER);
}
public void actionPerformed(ActionEvent event){
//se incrementa el valor
valor=valor+10;
if (valor>barra.getMaximum()){
//hemos sobrepasado el máximo de la barra de progreso
barra.setValue(barra.getMinimum());
valor=barra.getMinimum();
}
else{
barra.setValue(valor);
if(valor==barra.getMaximum())
//se emite un pitido al llegar al máximo
Toolkit.getDefaultToolkit().beep();
}
}
public static void main(String s[]) {
BarraProgreso ventana = new BarraProgreso();
ventana.creaBarra();
ventana.creaBoton();
ventana.setSize(200,200);
ventana.setVisible(true);
}
}
Código fuente 158
Un ejemplo de ejecución este código se ofrece en la Figura 100.
Figura 100
Como se puede comprobar en el ejemplo, la barra de progreso muestra el tanto por ciento (%) de la
operación realizada, este tanto por ciento lo calcula teniendo en cuenta el valor actual y sus valores
246
© Grupo EIDOS
12. Interfaces de usuario en Java: componentes atómicos de Swing
máximo y mínimo. Para que se muestre el etiqueta del tanto por ciento se ha utilizado el método
setStringPainted() pasándole como argumento el valor true.
Podemos mostrar en la barra de progreso cualquier texto mediante el método setString() de la clase
JProgressBar y así variar el comportamiento por defecto de la barra de progreso.
Para obtener el tanto por ciento de la tarea realizada (completada) representada por la barra de
progreso disponemos del método getPercentComplete().
Este es el último de los componentes Swing pertenecientes al grupo de componentes encargados de
mostrar información, los siguientes componentes pertenecen al grupo de los componentes atómicos
que muestran también una información pero de una forma más compleja y estructurada, además en
algunos casos permiten editar esta información.
Componentes que muestran información estructurada
En este último apartado se van a tratar dos componentes JColorChooser y JFileChooser que permiten
seleccionar un color determinado y un fichero del sistema de ficheros, respectivamente.
JColorChooser
Este componente Swing además de ofrecer una información estructurada, como pueden ser los
distintos colores, nos permite seleccionar un elemento determinado de esa información.
La clase JColorChooser representa un componente Swing atómico, pero bastante complejo y completo
que permite seleccionar colores de distintas formas. Además este componente permite mostrar de
forma sencilla un diálogo que permite seleccionar también un color. Veremos dos ejemplos de las dos
formas que podemos utilizar el componente JColorChooser: como si se tratara de otro componente
más o como un diálogo.
En el Look & Feel de Java (en el apartado correspondiente hablaremos de los distintos Look & Feel y
como establecerlos) una instancia de la clase JColorChooser muestra el siguiente aspecto: se encuentra
dividido en dos partes, un panel con pestañas (JTabbedPane) y un panel con la muestra de la selección
(preview). El panel de selección con pestañas encuentra en la parte superior y presenta tres pestañas,
cada una de ellas permite una forma distinta de seleccionar los colores.
El panel de muestra se encuentra en la parte inferior y va mostrando el color seleccionado en cada
momento del panel de selección.
Para poder tratar la selección actual el componente JColorChooser hace uso de una clase llamada
ColorSelectionModel que se encuentra en el subpaquete de Swing denominado javax.swing.colorchooser.
El modelo de selección de color (instancia de la clase ColorSelectionModel), lanza un evento de la
clase ChangeEvent (este evento vimos que también era lanzado por el componente JSlider) cada vez
que se cambia la selección del color en el selector de color (JColorChooser).
Si queremos detectar los cambios de selección de color del componente JColorChooser debemos
registrar el oyente correspondiente al evento ChageEvent, pero no se debe hacer directamente sobre el
componente JColorChooser, sino sobre su ColorSelectionModel. Para obtener la instancia de la clase
ColorSelectionModel correspondiente a un objeto de la clase JColorChooser, debemos lanzar sobre el
objeto JColorChooser el método getSelectionModel().
247
Programación en Java
© Grupo EIDOS
Pasemos ahora a ver un código de ejemplo que utiliza un objeto de la clase JColorChooser para
configurar el color del texto de una etiqueta.
import
import
import
import
import
public
java.awt.*;
java.awt.event.*;
javax.swing.*;
javax.swing.event.*;
javax.swing.colorchooser.*;
class SelectorColor extends JFrame implements ChangeListener{
private JLabel etiqueta;
private JColorChooser selectColor;
public SelectorColor() {
super("Selector de color");
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
public void creaEtiqueta(){
//Configuramos la etiqueta sobre la que se aplica la selección de
color
etiqueta= new JLabel("La Plataforma Java 2",JLabel.CENTER);
etiqueta.setForeground(Color.green);
etiqueta.setBackground(Color.white);
etiqueta.setOpaque(true);
etiqueta.setFont(new Font("SansSerif", Font.BOLD, 24));
etiqueta.setPreferredSize(new Dimension(80,45));
//panel con borde que va a contener la etiqueta
JPanel etiquetaPanel = new JPanel(new BorderLayout());
etiquetaPanel.add(etiqueta, BorderLayout.CENTER);
etiquetaPanel.setBorder(BorderFactory.createTitledBorder("etiqueta"));
getContentPane().add(etiquetaPanel, BorderLayout.CENTER);
}
public void creaSelectorColor(){
selectColor= new JColorChooser(etiqueta.getForeground());
//se obtiene el modelo de seleción de color para registrar el oyente
//de los eventos ChangeEvent
selectColor.getSelectionModel().addChangeListener(this);
selectColor.setBorder(BorderFactory.createTitledBorder
("Selecciona el color del texto"));
getContentPane().add(selectColor,BorderLayout.SOUTH);
}
public void stateChanged(ChangeEvent e) {
etiqueta.setForeground(selectColor.getColor());
}
public static void main(String[] args) {
SelectorColor ventana = new SelectorColor();
ventana.creaEtiqueta();
ventana.creaSelectorColor();
ventana.pack();
ventana.setVisible(true);
}
}
Código fuente 159
El aspecto del ejemplo es el de la Figura 101
248
© Grupo EIDOS
12. Interfaces de usuario en Java: componentes atómicos de Swing
Figura 101
Como se puede comprobar en el ejemplo el constructor utilizado de la clase JColorChooser recibe
como parámetro un color, este es el color seleccionado inicialmente dentro del selector de color. En
nuestro caso le hemos indicado el color de la letra de la etiqueta sobre la que se va a aplicar las
distintas selecciones de color.
El método stateChanged(), que se lanzará cada vez que se cambie la selección de color del selector de
color, recupera el color actual mediante el método getColor() de la clase JColorChooser, y se lo aplica
al texto de la etiqueta mediante el método setForeground().
En la ejecución del ejemplo se puede observar las tres formas distintas de seleccionar el color que
permite el componente JColorChooser, en la Figura 102 se puede ver otra selección distinta, utilizando
el formato de color RGB.
En este caso el componente JColorChooser lo hemos tratado como otro componente atómico más,
añadiéndolo al panel de contenido de la ventana, pero también podemos mostrarlo como un diálogo,
para ello podemos utilizar el método estático showDialog() de la clase JColorChooser.
La sintaxis de este método es:
JColorChooser.showDialog(Component padre, String título, Color
color)
249
Programación en Java
© Grupo EIDOS
Figura 102
El primer argumento de este método es el componente padre del diálogo (el componente del que
depende), el segundo el título del diálogo y el tercero el color seleccionado por defecto en el diálogo
de selección de color.
Ahora vamos a modificar el ejemplo anterior para realizar una función similar pero utilizando el
selector de colores como un diálogo modal. Para mostrar el diálogo vamos a utilizar un botón
(JButton) y el evento ActionEvent.
En este nuevo supuesto no se utiliza la clase ColorSelectionModel, sino que el color seleccionado lo
obtenemos directamente cuando el usuario pulse el botón de OK del diálogo. Esto se ve mucho más
claro en el código fuente del ejemplo
import
import
import
public
250
java.awt.*;
java.awt.event.*;
javax.swing.*;
class DialogoSelectorColor extends JFrame implements ActionListener{
private JLabel etiqueta;
private JButton boton;
public DialogoSelectorColor() {
super("Selector de color con diálogo");
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
© Grupo EIDOS
12. Interfaces de usuario en Java: componentes atómicos de Swing
});
}
public void creaEtiqueta(){
//Configuramos la etiqueta sobre la que se aplica la selección de
color
etiqueta= new JLabel("La Plataforma Java 2",JLabel.CENTER);
etiqueta.setForeground(Color.green);
etiqueta.setBackground(Color.white);
etiqueta.setOpaque(true);
etiqueta.setFont(new Font("SansSerif", Font.BOLD, 24));
etiqueta.setPreferredSize(new Dimension(80,45));
//panel con borde que va a contener la etiqueta
JPanel etiquetaPanel = new JPanel(new BorderLayout());
etiquetaPanel.add(etiqueta, BorderLayout.CENTER);
etiquetaPanel.setBorder(BorderFactory.createTitledBorder("etiqueta"));
getContentPane().add(etiquetaPanel, BorderLayout.CENTER);
}
public void creaBoton(){
JPanel panel=new JPanel();
panel.setLayout(new FlowLayout());
boton=new JButton("Selección de color");
panel.add(boton);
boton.addActionListener(this);
getContentPane().add(panel,BorderLayout.SOUTH);
}
public void actionPerformed(ActionEvent evento) {
Color color=JColorChooser.showDialog(this,"Selecciona un color",
etiqueta.getForeground());
if (color!=null) etiqueta.setForeground(color);
}
public static void main(String[] args) {
DialogoSelectorColor ventana = new DialogoSelectorColor();
ventana.creaEtiqueta();
ventana.creaBoton();
ventana.setSize(400,180);
ventana.setVisible(true);
}
}
Código fuente 160
El nuevo aspecto de nuestro ejemplo se muestra en la Figura 103.
Figura 103
Y al pulsar el botón de selección de color aparece la Figura 104.
251
Programación en Java
© Grupo EIDOS
Figura 104
Si el usuario cancela la selección de color, ya sea cerrando el diálogo o pulsando el botón de
cancelación, no se devolverá ningún color, sino que se devolverá el valor null (nulo).
En la figura 18 se puede observar que además de los botones OK y Cancel aparece el botón Reset, al
pulsar este botón el color seleccionado actualmente en el diálogo será el que se había indicado
inicialmente, es decir, el color que se indica en el método showDialog().
JFileChooser
Este es el último componente atómico de Swing que vamos a ver. El componente JFileChooser es
similar al visto anteriormente, podemos crearlo como un componente más y añadirlo a un panel o bien
mostrarlo como un diálogo modal.
Este componente nos permite navegar por el sistema de ficheros y seleccionar un fichero o directorio.
Normalmente se utilizan estos componentes como diálogos modales. Pero la clase JFileChooser
únicamente muestra el interfaz que nos permite seleccionar los ficheros, pero el tratamiento de los
mismos corre de nuestra cuenta, es decir, el tratamiento de los ficheros debemos implementarlo en
nuestra aplicación.
La clase JFileChooser ofrece dos métodos para mostrar los dos tipos de selectores de ficheros
distintos, el que se utiliza para abrir ficheros y el que se utiliza para grabar ficheros, estos métodos son
showOpenDialog() y showSaveDialog(). Ambos métodos reciben como argumento un objeto
Component que representa el padre del diálogo de selección de ficheros. Estos métodos no son
estáticos como ocurría con el componente ColorChooser, sino que se deben lanzar sobre una instancia
de la clase JFileChooser.
252
© Grupo EIDOS
12. Interfaces de usuario en Java: componentes atómicos de Swing
Tanto el método showOpenDialog() como showSaveDialog() devuelven un valor entero que se
corresponde con las constantes que indican el resultado de la selección del usuario, indican si el
usuario a pulsado el botón de cancelación (CANCEL_OPTION) o no (APPROVE_OPTION), ambas
constantes de encuentran definidas en la clase JFileChooser.
El aspecto que muestra el diálogo para la apertura de fichero es muy similar al del diálogo para
guardar ficheros.
A continuación vamos a mostrar un sencillo ejemplo que posee dos botones y un área de texto
(JTextArea), según el botón que se pulse se mostrará un diálogo de selección de ficheros distinto, para
abrir ficheros y para guardar ficheros. En el área de texto se irán mostrando las selecciones de ficheros
o cancelaciones realizadas por el usuario. El código fuente completo es el Código fuente 161.
import
import
import
import
public
java.awt.*;
java.awt.event.*;
javax.swing.*;
java.io.*;
class DialogoSelectorFichero extends JFrame implements ActionListener{
private JTextArea resultado;
private JButton botonAbrir;
private JButton botonGuardar;
private JFileChooser selectorFichero;
public DialogoSelectorFichero() {
super("Diálogo para la selección de ficheros");
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
selectorFichero=new JFileChooser();
}
public void creaBotones(){
ImageIcon iconoAbrir = new ImageIcon("abrir.gif");
botonAbrir=new JButton("Abrir un fichero...", iconoAbrir);
ImageIcon iconoGuardar = new ImageIcon("guardar.gif");
botonGuardar=new JButton("Guardar un fichero...", iconoGuardar);
botonAbrir.addActionListener(this);
botonGuardar.addActionListener(this);
JPanel panelBotones=new JPanel();
panelBotones.add(botonAbrir);
panelBotones.add(botonGuardar);
getContentPane().add(panelBotones,BorderLayout.NORTH);
}
public void creaResultado(){
resultado = new JTextArea(5,20);
resultado.setMargin(new Insets(5,5,5,5));
resultado.setEditable(false);
JScrollPane panelResultado = new JScrollPane(resultado);
getContentPane().add(panelResultado,BorderLayout.CENTER);
}
public void actionPerformed(ActionEvent evento) {
if (evento.getSource()==botonAbrir){
int valorDevuelto= selectorFichero.showOpenDialog(this);
if (valorDevuelto== JFileChooser.APPROVE_OPTION) {
File fichero = selectorFichero.getSelectedFile();
resultado.append("Abierto: " + fichero.getName()+"\n");
} else
resultado.append("Apertura cancelada por el
usuario\n");
}else{
int valorDevuelto= selectorFichero.showSaveDialog(this);
253
Programación en Java
© Grupo EIDOS
if (valorDevuelto== JFileChooser.APPROVE_OPTION) {
File fichero = selectorFichero.getSelectedFile();
resultado.append("Guardado: " + fichero.getName()+"\n");
} else
resultado.append("Operación de grabado cancelada"+
"por el usuario\n");
}
}
public static void main(String[] args) {
DialogoSelectorFichero ventana = new DialogoSelectorFichero();
ventana.creaBotones();
ventana.creaResultado();
ventana.pack();
ventana.setVisible(true);
}
}
Código fuente 161
El aspecto de nuestra ventana es el de la Figura 105.
Figura 105
Y la del diálogo de selección de ficheros para su apertura, es la Figura 106.
Figura 106
254
© Grupo EIDOS
12. Interfaces de usuario en Java: componentes atómicos de Swing
Como se puede comprobar en el ejemplo, para obtener el fichero que se ha seleccionado se utiliza el
método getSelectedFile(), que devuelve un objeto de la clase File. A través de este objeto podemos
manipular ficheros, en una aplicación real se utilizaría para guardar o abrir ficheros.
El constructor de la clase JFileChooser puede recibir como argumento un objeto File o String para
indicar el directorio inicial. El directorio inicial también se puede especificar mediante el método
setCurrentDirectory() al que se le pasa como argumento un objeto de la clase File.
Cada vez que se muestra el diálogo para la selección de ficheros guarda el directorio actual de la
selección anterior, es decir, va recordando las rutas de las selecciones anteriores. Además permite la
creación de nuevos directorios.
Este ha sido el componente Swing atómico que cierra este capítulo, en el siguiente capítulo trataremos
el nuevo gestor de diseño de Swing y la característica Pluggable Look & Feel.
255
Interfaces de usuario en Java: otras
características de Swing
Introducción
Este capítulo cierra el ciclo de capítulos dedicado a Swing, este capítulo no va a ser tan extenso como
los anteriores ya que trataremos dos aspectos muy determinados, el nuevo gestor de diseño de Swing,
representado por la clase BoxLayout y la característica especial que ofrece Swing denominada
Pluggable Look & Feel.
El gestor de diseño BoxLayout
La clase BoxLayout, que podemos encontrar en el paquete javax.swing, representa a un nuevo gestor
de diseño ofrecido por Swing.
Este gestor de diseño organiza los componentes en una pila, uno encima del otro y también de
izquierda a derecha uno al lado del otro. Se puede decir que es una versión ampliada del gestor de
diseño FlowLayout que ofrece AWT.
Antes de seguir comentando este gestor de diseño ofrecido por Swing, vamos a ver con un sencillo
ejemplo la forma en la que distribuye los componentes en el panel al que se aplica. El código fuente de
este ejemplo es el Código fuente 162.
Programación en Java
© Grupo EIDOS
import javax.swing.*;
import java.awt.event.*;
public class BoxLayoutSencillo{
public static void main(String s[]) {
JFrame ventana = new JFrame("Ventana con BoxLayout");
ventana.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
JPanel panelContenido=new JPanel();
panelContenido.setLayout(new
BoxLayout(panelContenido,BoxLayout.Y_AXIS));
panelContenido.add(new JButton("Soy un botón"));
panelContenido.add(new JButton("Soy un botón más"));
panelContenido.add(new JButton("Otro botón"));
panelContenido.add(new JButton("Soy el último"));
ventana.setContentPane(panelContenido);
ventana.pack();
ventana.setVisible(true);
}
}
Código fuente 162
Y el aspecto que ofrece es el de la Figura 107.
Figura 107
En el ejemplo anterior podemos comprobar la forma de crear un gestor de diseño para distribuir los
componentes de arriba a abajo. El constructor de la clase BoxLayout posee dos argumentos, el panel
(JPanel) sobre el que se va a aplicar el gestor de diseño y la orientación en la que se van a distribuir los
componentes dentro de dicho panel. Este último parámetro es un entero que se corresponde con las
siguientes constantes definidas en la clase BoxLayout:
•
X_AXIS: los componentes se distribuyen de izquierda a derecha.
•
Y_AXIS: los componentes se distribuyen de arriba a abajo.
Si retomamos el ejemplo anterior y cambiamos la constante BoxLayout.Y_AXIS por BoxLayout.X_
AXIS, se obtiene el resultado que muestra la Figura 108.
Figura 108
258
© Grupo EIDOS
13. Interfaces de usuario en Java: otras características de Swing
Conjuntamente con la clase BoxLayout, se suele utilizar la clase Box, esta clase ofrece una serie de
métodos para controlar de una forma más precisa la forma en la que se distribuyen los componentes
dentro de un panel al que se le ha aplicado este gestor de diseño BoxLayout. En el Código fuente 163
se pueden ver en acción dos de estos métodos, que son métodos estáticos de la clase Box.
import
import
import
public
javax.swing.*;
java.awt.event.*;
java.awt.*;
class DosBoxLayout{
public static void main(String s[]) {
JFrame ventana = new JFrame("Ventana con BoxLayout");
ventana.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
JPanel panelSuperior=new JPanel();
panelSuperior.setBorder(BorderFactory.createLineBorder(Color.red));
panelSuperior.setLayout(new
BoxLayout(panelSuperior,BoxLayout.Y_AXIS));
panelSuperior.add(new JLabel("Esto es una etiqueta"));
panelSuperior.add(Box.createRigidArea(new Dimension(0,5)));
panelSuperior.add(new JTextArea(5,30));
JPanel panelInferior=new JPanel();
panelInferior.setBorder(BorderFactory.createLineBorder(Color.green));
panelInferior.setLayout(new
BoxLayout(panelInferior,BoxLayout.X_AXIS));
panelInferior.add(Box.createHorizontalGlue());
panelInferior.add(new JButton("Botón 1"));
panelInferior.add(Box.createRigidArea(new Dimension(10,0)));
panelInferior.add(new JButton("Botón 2"));
ventana.getContentPane().add(panelSuperior,BorderLayout.NORTH);
ventana.getContentPane().add(panelInferior,BorderLayout.CENTER);
ventana.pack();
ventana.setVisible(true);
}
}
Código fuente 163
El interfaz que genera este código es el de la Figura 109.
Figura 109
259
Programación en Java
© Grupo EIDOS
A la vista del código se puede decir que este interfaz está compuesto por dos paneles los que se han
aplicado un gestor de diseño BoxLayout distinto, uno de arriba a abajo y otro de izquierda a derecha.
Se ha aplicado un borde a cada uno de estos dos paneles para distinguirlos más claramente.
En el primer panel se ha utilizado el método createRigidArea() de la clase Box para crear una
separación fija (área rígida) invisible entre la etiqueta (JLabel) y el área de texto (JTextArea). En este
caso se ha indicado una separación vertical de cinco pixels.
En el segundo panel se utiliza otro método de la clase Box, el método createHorizontalGlue(), este
método provoca que los dos botones que se añadan a continuación se desplacen hacia la derecha, es
decir, este método rellena el espacio sobrante y desplaza a los componentes hacia la derecha. El
método createHorizontalGlue() crea un área invisible que crece horizontalmente para rellenar todo el
espacio libre disponible dentro del contenedor.
En este panel inferior también se utiliza el método createRigidArea() para separar los dos botones diez
pixels, en este caso se ha utilizado un área sólo con componente horizontal. Si la llamada al método
createHorizontalGlue() la hubiéramos realizado en el lugar en la que se encuentra la llamada al método
createRigidArea(), el resultado hubiera sido la Figura 110.
Figura 110
A la vista de la imagen se puede decir que el área se ha extendido entre los dos botones, enviando al
segundo botón a la derecha del todo.
La clase Box ofrece también el método createVerticalGlue(), en este caso el área invisible se extiende
de forma vertical enviando los componentes dentro del panel hacia la zona inferior.
El gestor de diseño BoxLayout respeta el tamaño máximo de los componentes que contiene y también
la alineación establecida por los mismos, aunque para que no existan problemas de alineación todos
los componentes deben tener la misma. Si modificamos el primer ejemplo visto en este apartado como
indica el Código fuente 164, se obtiene la Figura 111.
import java.awt.event.*;
import java.awt.*;
public class BoxLayoutSencillo{
public static void main(String s[]) {
JFrame ventana = new JFrame("Ventana con BoxLayout");
ventana.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
JPanel panelContenido=new JPanel();
260
© Grupo EIDOS
13. Interfaces de usuario en Java: otras características de Swing
JButton boton1=new JButton("Soy un botón");
boton1.setAlignmentX(Component.CENTER_ALIGNMENT);
JButton boton2=new JButton("Soy un botón más");
JButton boton3=new JButton("Otro botón");
boton3.setAlignmentX(Component.RIGHT_ALIGNMENT);
JButton boton4=new JButton("Soy el último");
boton4.setAlignmentX(Component.CENTER_ALIGNMENT);
panelContenido.setLayout(new
BoxLayout(panelContenido,BoxLayout.Y_AXIS));
panelContenido.add(boton1);
panelContenido.add(boton2);
panelContenido.add(boton3);
panelContenido.add(boton4);
ventana.setContentPane(panelContenido);
ventana.pack();
ventana.setVisible(true);
}
}
Código fuente 164
Figura 111
La alineación se respetará en este otro ejemplo.
import
import
import
public
javax.swing.*;
java.awt.event.*;
java.awt.*;
class BoxLayoutSencillo{
public static void main(String s[]) {
JFrame ventana = new JFrame("Ventana con BoxLayout");
ventana.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
JPanel panelContenido=new JPanel();
JButton boton1=new JButton("Soy un botón");
boton1.setAlignmentX(Component.CENTER_ALIGNMENT);
JButton boton2=new JButton("Soy un botón más");
boton2.setAlignmentX(Component.CENTER_ALIGNMENT);
JButton boton3=new JButton("Otro botón");
boton3.setAlignmentX(Component.CENTER_ALIGNMENT);
JButton boton4=new JButton("Soy el último");
boton4.setAlignmentX(Component.CENTER_ALIGNMENT);
panelContenido.setLayout(new
BoxLayout(panelContenido,BoxLayout.Y_AXIS));
panelContenido.add(boton1);
panelContenido.add(boton2);
panelContenido.add(boton3);
panelContenido.add(boton4);
261
Programación en Java
© Grupo EIDOS
ventana.setContentPane(panelContenido);
ventana.pack();
ventana.setVisible(true);
}
}
Código fuente 165
Cuyo resultado es la Figura 112.
Figura 112
Estableciendo el Look & Feel
En el capítulo anterior comentábamos que una característica que ofrece Swing es el aspecto y
comportamiento configurable de sus componentes, lo que se denomina Pluggable Look & Feel, es
decir, a nuestra aplicación Java le podemos asignar un aspecto específico. Incluso, como veremos un
poco más adelante, podemos establecer y cambiar en tiempo de ejecución de nuestra aplicación su
Look & Feel.
Cuando no se indica ningún Look & Feel determinado Swing utiliza el Look & Feel por defecto, que
es el Look & Feel de Java, también denominado Metal (fue el código que se le dio al proyecto que lo
desarrolló). Se pueden distinguir cuatro Look & Feel estándar distintos:
262
•
Java (Metal): se trata del Look & Feel por defecto y es el que se ha estado utilizando en todos
los ejemplos de los componentes Swing, ya que en nuestros ejemplos no hemos especificado
ningún Look & Feel. Se corresponde con la implementación de la clase
javax.swing.plaf.metal.MetalLookAndFeel. Este Look & Feel lo podemos utilizar en cualquier
plataforma.
•
Windows: es aspecto que presentan los sistemas Windows y lo encontramos implementado en
la clase com.sun.java.swing.plaf.windows.WindowsLookAndFeel. A diferencia del anterior
Look & Feel, éste únicamente puede ser utilizado en plataformas Windows.
•
Motif: representa el aspecto CDE/Motif, que es el aspecto que tienen las plataformas de Sun.
Este Look & Feel es implementado por la clase com.sun.java.swing.plaf.motif.MotifLookAndFeel, y al igual que el Look & Feel de Java puede ser utilizado en cualquier plataforma.
•
Mac: este último Look & Feel es el correspondiente a los sistemas Macintosh. Se encuentra en
al clase javax.swing.plaf.mac.MacLookAndFeel. Al igual que ocurría con el Look & Feel de
Windows, este Look & Feel está restringido a una plataforma específica, esta plataforma es la
plataforma Mac OS.
© Grupo EIDOS
13. Interfaces de usuario en Java: otras características de Swing
Todas estas clases que implementan un Look & Feel determinado, tienen la característica común que
todas ellas heredan de la clase abstracta javax.swing.LookAndFeel.
Para indicar un Look & Feel determinado en una aplicación Java utilizaremos la clase UIManager
(gestor de interfaz gráfico) que se encuentra en el paquete javax.swing.
La clase UIManager ofrece un método estático llamado setLookAndFeel(), que recibe como parámetro
una cadena que especifica la clase que se corresponde con el Look & Feel que deseamos asignar a
nuestra aplicación Java.
El método setLookAndFeel() de la clase UIManager lanza una excepción de la clase
UnsupportedLookAndFeelException, que se encuentra también dentro del paquete javax.swing,
cuando el Look & Feel que queremos utilizar no es soportado por la plataforma actual, deberemos
atrapar por lo tanto esta excepción cuando invoquemos el método setLookAndFeel().
De esta forma si queremos que nuestra aplicación posea el Look & Feel de Motif deberemos incluir el
Código fuente 166 dentro del método main() de la aplicación.
try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.motif.MotifLookAndFeel");
} catch (Exception e) { }
new AplicacionSwing();
Código fuente 166
Pero el Look & Feel, como ya hemos adelantado al comienzo de este apartado, se puede establecer
una vez que la aplicación ya se ha iniciado y el interfaz de usuario es visible, es decir, podemos
establecer el Look & Feel de la aplicación en tiempo de ejecución. En este caso además de utilizar el
método setLookAndFeel() de la clase UIManager, deberemos utilizar el método estático
updateComponentTreeUI() de la clase SwingUtilities, presente también en el paquete javax.swing. El
método updateComponentTreeUI() recibe como argumento un objeto de la clase Component que
representa el contenedor de alto nivel sobre el que se quiere actualizar su Look & Feel.
El método updateComponentTreeUI() de la clase SwingUtilities debe utilizarse para cada uno de los
contenedores de alto nivel del interfaz de usuario de la aplicación, para que de esta forma todos
actualicen su Look & Feel en tiempo de ejecución. Además es recomendable lanzar el método pack()
sobre cada uno de los contenedores de alto nivel del interfaz de usuario, para que se redimensionen
todos los componentes según su nuevo Look & Feel. La utilización de este método, conjuntamente
con el método setLookAndFee() se puede ver en el Código fuente 167.
try {
UIManager.setLookAndFeel(lf);
SwingUtilities.updateComponentTreeUI(contenedor);
this.pack(contenedor);
} catch (Exception excepcion) {
System.out.println("No se pudo asignar el Look & Feel");
}
Código fuente 167
En este caso se supone que sólo existe un contenedor de alto nivel.
263
Programación en Java
© Grupo EIDOS
La clase UIManager, además del método setLookAndFeel(), nos ofrece otros dos métodos que pueden
resultar
de
utilidad,
estos
métodos
son
getSystemLookAndFeelClassName()
y
getCrossPlatformLooAndFeelClassName(). Ambos métodos devuelven una cadena, en el primer caso
se corresponde con el nombre de la clase del Look & Feel de la plataforma sobre la que se está
ejecutando la aplicación, y el segundo caso se corresponde con el nombre de la clase que ofrece el
Look & Feel de Java, que es el Look & Feel que se garantiza funcionará correctamente para todas las
plataformas.
Por lo tanto estos dos métodos se pueden utilizar como argumento para el método setLookAndFeel(),
así por ejemplo si deseamos aplicar el Look & Feel de la plataforma sobre la que se ejecuta la
aplicación, utilizaríamos el Código fuente 168.
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) { }
Código fuente 168
Para mostrar la característica Pluggable Look & Feel de Swing se va a utilizar un sencillo ejemplo que
consiste una aplicación con una ventana (JFrame) que permite especificar el Look & Feel en tiempo de
ejecución.
LA ventana de la aplicación del ejemplo, además de contener diversos componentes Swing, contiene
cinco botones de opción (JOptionButton) agrupados, y que al seleccionar cada uno de ellos se aplicará
sobre la aplicación el Look & Feel que representa cada opción. Es decir, existen cinco botones de
opción, cuatro de ellos para cada uno de los distintos Look & Feel (Java, Windows, Motif y Mac) y
otro para seleccionar el Look & Feel de la plataforma sobre la que se ejecuta la aplicación.
Antes de seguir comentando más detalles de esta aplicación de ejemplo, vamos a mostrar el aspecto
que tendría esta aplicación. Al ejecutar la aplicación el aspecto que muestra es el de la Figura 113, y se
corresponde con el Look & Feel de Java.
Figura 113
264
© Grupo EIDOS
13. Interfaces de usuario en Java: otras características de Swing
Al iniciarse su ejecución dentro del constructor de la aplicación, se establece el Look & Feel mediante
el nombre de la clase devuelto por el método getCrossPlatformLookAndFeelClassName() de la clase
UIManager, debido a esto al arrancar la aplicación comienza a ejecutarse utilizando el Look & Feel de
Java. El código fuente perteneciente al constructor de nuestra clase se puede observar en el Código
fuente 169.
public LF(){
super("Pluggable Look & Feel");
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
try {
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
} catch (Exception e) { }
getContentPane().setLayout(new BoxLayout(getContentPane(),BoxLayout.Y_AXIS));
}
Código fuente 169
En el constructor además de establecer el Look & Feel de Java se establece el gestor de diseño que va
a utilizar el panel de contenido de la ventana. El gestor de diseño que se utiliza es BoxLayout, descrito
en el apartado anterior, en su variante de distribución de componentes de arriba a abajo
(BoxLayout.Y_AXIS).
De la figura anterior también se puede comprobar que la opción seleccionada por defecto se
corresponde lógicamente con el Look & Feel que presenta la aplicación inicialmente.
Podemos ir asignando a la aplicación los distintos Look & Feel seleccionando la opción
correspondiente. A continuación se muestran otras dos figuras, cada una con otro nuevo Look & Feel.
La primera corresponde al Look & Feel de Windows y la segunda al Look & Feel de Motif.
Figura 114
265
Programación en Java
© Grupo EIDOS
Figura 115
En mi caso no he podido utilizar el Look & Feel de Mac, ya que he utilizado una plataforma Windows
para ejecutar la aplicación de ejemplo, por lo tanto éste Look & Feel no estará disponible, y además en
mi caso particular el Look & Feel del sistema (Look & Feel de la plataforma actual) será por lo tanto
el de Windows.
No vamos hacer como en otros ejemplos de este curso, en los que se ha ofrecido el código completo
del ejemplo, sólo vamos a mostrar y comentar aquellos fragmentos que resultan más interesantes
(sobre todo desde el punto de vista de la característica Pluggable Look & Feel), ya que el código
fuente de la aplicación de ejemplo es bastante sencillo y se utilizan componentes Swing vistos en los
capítulos anteriores.
Empezamos ofreciendo la declaración de nuestra clase (Código fuente 170).
public class LF extends JFrame implements ActionListener{
Código fuente 170
Como se puede ver es una sencilla ventana que va a contener una serie de componentes Swing. No
vamos a detenernos en la creación de los distintos componentes del interfaz de usuario, pero si que es
interesante mostrar como se crean las opciones que permiten seleccionar el Look & Feel de la
aplicación. Para crear las opciones que representan los distintos LooK & Feel se ha utilizado el
método creaOpciones(), cuyo código ofrecemos en el Código fuente 171.
public void creaOpciones(){
panelOpciones=new JPanel();
panelOpciones.setLayout(new GridLayout(1,5,5,5));
opWindows=new JRadioButton("Windows");
opWindows.setMnemonic(KeyEvent.VK_W);
266
© Grupo EIDOS
13. Interfaces de usuario en Java: otras características de Swing
opWindows.addActionListener(this);
panelOpciones.add(opWindows);
opJava=new JRadioButton("Java/Metal");
opJava.setMnemonic(KeyEvent.VK_J);
opJava.addActionListener(this);
panelOpciones.add(opJava);
opJava.setSelected(true);
opMotif=new JRadioButton("Motif");
opMotif.setMnemonic(KeyEvent.VK_M);
opMotif.addActionListener(this);
panelOpciones.add(opMotif);
opMac=new JRadioButton("Mac");
opMac.setMnemonic(KeyEvent.VK_C);
opMac.addActionListener(this);
panelOpciones.add(opMac);
opSistema=new JRadioButton("Sistema");
opSistema.setMnemonic(KeyEvent.VK_S);
opSistema.addActionListener(this);
panelOpciones.add(opSistema);
panelOpciones.setBorder(BorderFactory.createLoweredBevelBorder());
//se agrupan las opciones
ButtonGroup grupo=new ButtonGroup();
grupo.add(opWindows);
grupo.add(opJava);
grupo.add(opMotif);
grupo.add(opMac);
grupo.add(opSistema);
getContentPane().add(panelOpciones);
}
Código fuente 171
La pulsación de cada opción será tratada dentro del método actionPerformed() que realmente,
conjuntamente con el constructor de la clase, es dónde utilizamos métodos y clases relacionados
directamente con el mecanismo Pluggable Look & Feel.
A continuación se ofrece el código fuente perteneciente al método actionPerformed(). Será en este
método dónde se establezca el Look & Feel seleccionado a través de los distintos botones de opción.
public void actionPerformed(ActionEvent evento){
Object fuente=evento.getSource();
String lf="";
if (fuente==opWindows)
lf="com.sun.java.swing.plaf.windows.WindowsLookAndFeel";
else if(fuente==opJava)
lf="javax.swing.plaf.metal.MetalLookAndFeel";
else if(fuente==opMotif)
lf="com.sun.java.swing.plaf.motif.MotifLookAndFeel";
else if(fuente==opMac)
lf="javax.swing.plaf.mac.MacLookAndFeel";
else if(fuente==opSistema)
lf=UIManager.getSystemLookAndFeelClassName();
try {
UIManager.setLookAndFeel(lf);
SwingUtilities.updateComponentTreeUI(this);
this.pack();
} catch (UnsupportedLookAndFeelException excepcion) {
texto.append("Look & Feel no
soportado.\n"+excepcion+"\n");
} catch (ClassNotFoundException excepcion){
267
Programación en Java
© Grupo EIDOS
texto.append("Clase no encontrada.\n"+excepcion+"\n");
} catch (InstantiationException excepcion){
texto.append("Excepción de
instanciación.\n"+excepcion+"\n");
} catch(IllegalAccessException e){
texto.append("Excepción de acceso.\n"+e+"\n");
}
}
Código fuente 172
Como se puede comprobar a la vista del código anterior, se decidimos atrapar la excepción
UnsupportedLookAndFeelException, se nos obligará a atrapar tres excepciones más. Por lo demás, si
se tiene en cuenta lo explicado hasta el momento, el código de este método es bastante sencillo, lo
único que se hace es establecer el Look & Feel que se corresponde con la opción pulsada. Si ocurre
una excepción se muestra en el área de texto (JTextArea) de la aplicación.
Si en mi caso particular, utilizando una plataforma Windows, selecciono la opción correspondiente al
Look & Feel de Mac, obtendré una excepción lanzada por el método setLooAndFeel(), esto se puede
observar en la Figura 116.
Figura 116
El fichero fuente de Java perteneciente a este ejemplo se puede obtener aquí.
Con este apartado damos por terminado la construcción de interfaces en Java y también los
componentes Swing, aunque en uno de los capítulos dedicados a los applets volveremos a tratar un
componente Swing, el contenedor de alto nivel representado por la clase JApplet.
Como se habrá podido comprobar Swing es bastante extenso y potente, tan extenso que sería posible
dedicar un curso completo dedicado a Swing. Con estos tres capítulos hemos pretendido mostrar los
aspectos más destacados de Swing, sin tener la intención de abarcar todo, ya que como hemos dicho
nos dará para un curso completo más.
En el siguiente capítulo comenzaremos a ver el otro tipo de aplicaciones que podemos realizar con
Java, los applets.
268
Applets de Java: introducción a los
Applets
Conceptos previos
A lo largo del presente capítulo vamos a hacer referencia a una serie de términos relacionados con los
applets de Java que conviene tener claros, por ello hemos incluido este apartado, muchos de estos
conceptos pueden que sean conocidos para todos, pero es mejor partir de una serie de definiciones que
compartamos todos.
Internet
Internet es un conjunto de redes cuyo origen se remonta al año 1969 en Estados Unidos. En un
principio Internet se creó con fines militares durante La Guerra Fría. Era un proyecto del
Departamento de Defensa de Estados Unidos.
En un principio este proyecto se denominó ARPANET (Advanced Research Projects Agency-ARPA).
El objetivo para el que se había creado el proyecto era mantener una infraestructura robusta de
diferentes ordenadores conectados entre sí.
Si se suprimía algún nodo de la red, la red debería seguir funcionando, es decir, no existía ningún nodo
central o principal. Estas características son debidas al origen militar de la red, de esta forma, si
algunas de las partes de la red se destruían debido a un ataque militar, el resto de la red puede seguir
funcionando.
Programación en Java
© Grupo EIDOS
De este inicio militar Internet paso a ser utilizado dentro del campo académico, se utilizaba en
universidades para proyectos de investigación.
Del ámbito académico Internet pasó a ser de dominio público, para todo tipo de actividades, culturales,
comerciales, lúdicas, filosóficas, etc.
La fama y popularidad de Internet se consiguió y llegó a través de la World Wide Web. La Web o la
Gran Telaraña Mundial se basa en el protocolo de Transferencia de Hipertexto HTTP (Hypertext
Transport Protocol).
La Web nació en el CERN (European Center for Nuclear Research) de la mano de Tim Berners-Lee
como intento de crear un sistema hipermedia, basado en enlaces hipertextuales, para la compartición
de información científica entre los integrantes del laboratorio del CERN, de una manera clara y
sencilla aprovechando los recursos que ofrecía Internet en aquella época.
A finales de 1995 la Web se había convertido en el servicio más solicitado y famoso de Internet.
URLs y direcciones IP
Una URL (Uniform Resource Locator) es el mecanismo que se utiliza en Internet para localizar e
identificar de forma única un recurso dentro de Internet. La sintaxis general de una URL se compone
de tres partes:
protocolo://<nombre del dominio>/<nombre del recurso>
El campo protocolo especifica el protocolo de Internet que se va a utilizar para transferir los datos del
recurso entre el cliente y el servidor. Existen varios protocolos en Internet como pueden ser: FTP (File
Transfer Protocolo) para la transferencia de ficheros; HTTP (HyperText Transfer Protocol) protocolo
para la transferencia de hipertexto; Telnet, Gopher, etc.
A continuación del protocolo aparece los dos puntos (:), la doble barra (//) y el nombre del dominio. El
nombre del dominio es la forma de identificar una máquina de forma única en Internet, este nombre de
dominio puede ser la dirección IP, en el formato de tétrada punteada (201.234.123.87) o bien su
traducción correspondiente a un nombre más descriptivo, como puede ser www.eidos.es.
Una dirección IP es un número de 32 bits para identificar de forma única una máquina conectada a
Internet. Se divide en grupos de 8 bits y se identifica con su número en notación decimal separado
cada uno de ellos por puntos, a este formato se le denomina tétrada punteada.
Debido a que recordar direcciones IP puede ser difícil y poco manejable se suelen identificar con
nombres de máquinas, así la dirección IP 206.26.48.100 se corresponde con el nombre de la máquina
java.sun.com que resulta más fácil de recordar y significativo. Un mismo nombre puede tener
diferentes direcciones IP, en Internet esta correspondencia entre direcciones IP y nombres la gestionan
servidores de nombres que se encargan de traducir los nombres fáciles de recordar a sus direcciones de
32 bits.
Acompañando el nombre del dominio puede aparecer precedido de dos puntos un número de puerto.
Los números de puerto se utilizan para identificar de forma única los distintos procesos dentro de una
máquina en la red. A cada servidor se le asigna un número de puerto conocido. El rango del número de
puerto es 0-65535, ya que se trata de números de 16 bits, los números de puerto del rango 0-1023 se
encuentran restringidos ya que se utilizan para servicios conocidos como pueden ser HTTP
270
© Grupo EIDOS
14. Applets de Java: introducción a los Applets
(HyperText Transfer Protocol) en el puerto 80, FTP (File Transfer Protocol) en el puerto 21, telnet en
el puerto 23 y otros servicios del sistema.
Cada dirección IP es un conjunto de puertos a los que los clientes se pueden conectar a través de
Internet.
La última parte de la URL indica dónde se encuentra localizado el recurso dentro del servidor, es
decir, es el camino del fichero demandado en la URL. El servidor verificará esta parte de la URL para
identificar que página HTML, imagen, directorio o aplicación se está demandando. Cada elemento que
forma el camino del recurso se para con una barra (/).
Clientes y Servidores Web
La arquitectura Cliente/Servidor podemos verla como una forma de abordar los problemas.
Dentro de la red el sistema cliente es la estación de trabajo y el servidor es una versión de mayor
tamaño capaz de almacenar gran cantidad de datos y ejecutar grandes aplicaciones.
Cuando un ordenador se comunica con otro a través de Internet, en dicha comunicación se usa el
modelo cliente/servidor. Los recursos de Internet los proporcionan unas máquinas denominadas
servidores, que dan el servicio a las peticiones de los clientes.
Los clientes son los ordenadores que acceden a estos recursos a través de aplicaciones cliente.
Básicamente, un servidor atiende y trata peticiones de varios clientes. En el entorno de Internet la
arquitectura cliente/servidor es bastante común y se encuentra muy extendida. Normalmente un
servidor se ejecuta un una máquina diferente de la del cliente, aunque no siempre es así. La interacción
entre cliente y servidor normalmente comienza en el lado del cliente. El cliente realiza una petición de
un objeto o transacción del servidor, el servidor deberá tratar la petición o denegarla. Si la petición es
tratada, el objeto se le enviará al cliente.
En el entorno de la World Wide Web los servidores se denominan servidores Web y los clientes se
denominan navegadores Web.
Un servidor Web es el encargado de publicar o poner a disposición de una serie de clientes unos
recursos. Estos recursos pueden variar desde una página HTML a un script CGI para la consulta de
una tabla en una base de datos, o bien desde una página HTML con diferentes applets a una página
ASP que realiza el mantenimiento de una tabla, o incluso un servicio de correo.
A la hora de pensar en un servidor Web no debemos limitar sus funciones a la del servicio Web
propiamente dicho, es decir, a la publicación de páginas HTML.
Los navegadores Web realizan peticiones de documentos que se encuentran en servidores Web,
permitiendo así visualizar los documentos de la World Wide Web. Algunos de los clientes más
populares son Netscape Navigator y Microsoft Internet Explorer.
El proceso de visualización de un documento en la Web comienza cuando el navegador Web envía
una petición al servidor Web. El navegador Web envía información, sobre sí mismo y sobre el fichero
que está demandando, al servidor Web en cabeceras de petición del protocolo HTTP.
El servidor Web recibe y consulta las cabeceras HTTP para obtener toda información relevante, tal
como el nombre del fichero que se indica en la petición, y envía el fichero con las cabeceras de
271
Programación en Java
© Grupo EIDOS
respuesta del protocolo HTTP. Entonces el navegador Web utiliza las cabeceras de respuesta HTTP
para determinar como mostrar o tratar el fichero que le envía el servidor Web.
HTML
HTML es la abreviatura de HyperText Markup Language , y es el lenguaje que todos los programas
navegadores usan para presentar información en la World Wide Web (WWW).
Este es un lenguaje muy sencillo que se basa en el uso de etiquetas, consistentes en un texto ASCII
encerrado dentro de un par de paréntesis angulares(<..>). El texto incluido dentro de los paréntesis nos
dará una explicación de la utilidad de la etiqueta. Así por ejemplo la etiqueta <TABLE> nos permitirá
definir una tabla.
Las etiquetas podrán incluir una serie de atributos o parámetros, en su mayoría opcionales, que nos
permitirán definir diferentes posibilidades o características de la misma. Estos atributos quedarán
definidos por su nombre (que será explicativo de su utilidad) y el valor que toman separados por un
signo de igual. En el caso de que el valor que tome el atributo tenga más de una palabra deberá
expresarse entre comillas, en caso contrario no será necesario. Así por ejemplo la etiqueta <TABLE
border="2"> nos permitirá definir una tabla con borde de tamaño 2.
Entre otras cosas, el manejo de estas etiquetas nos permitirá:
•
Definir la estructura lógica del documento HTML.
•
Aplicar distintos estilos al texto (negrita, cursiva, ...).
•
La inclusión de hiperenlaces, que nos permitirán acceder a otros documentos relacionados con
el actual.
•
La inclusión de imágenes y ficheros multimedia (gráficos, vídeo, audio).
HTTP
Como ya se ha comentado anteriormente, el protocolo HTTP (HiperText Transfer Protocol) es el
utilizado para la transferencia de hipertexto entre el servidor Web y el navegador.
Introducción a los applets de Java
Una vez comentados una serie de conceptos relacionados con los applets de Java, vamos a pasar a
tratar los applets propiamente dichos.
Como ya sabrá el lector a estas alturas, los programas Java comprenden dos grupos principales:
applets y aplicaciones. Un applet es un programa dinámico e interactivo que se ejecuta dentro de una
página Web, desplegada por un navegador con capacidad para Java como puede ser los navegadores
Navigator de Netscape o el Internet Explorer de Microsoft, por lo tanto los applets para ejecutarse
dependen de un navegador Web que soporte para Java. Los applets es uno de los principales motivos
que han hecho al lenguaje Java tan popular.
272
© Grupo EIDOS
14. Applets de Java: introducción a los Applets
Un applet es un objeto dentro de la jerarquía de clases de Java, es una subclase de la clase Panel, y se
encuentra en el paquete java.applet, este paquete además de contener la clase Applet contiene tres
interfaces AudioClip, AppletStub y AppletContext.
Para poder incluir un applet dentro de una página Web, se deben seguir los siguientes pasos: después
de crear una clase con el applet y compilarla dando como resultado un archivo de clase (.CLASS), se
debe crear una página Web que contenga al applet, para ello se debe hacer uso del lenguaje HTML
(HyperText Markup Language) que dispone de una serie de etiquetas especiales para incluir applets en
las páginas Web. Estas etiquetas las comentaremos en el apartado correspondiente dentro de este
mismo capítulo.
Después de tener un applet y la página Web que hace referencia al mismo, se debe hacer disponible
para la World Wide Web. Los applets Java se pueden colocar en un servidor Web de la misma manera
que los archivos HTML, no se necesita software de servidor especial para hacer los applets disponibles
en la Web, todo lo que se necesita es ubicar el fichero HTML y los archivos de clase compilados del
applet en el servidor Web, también se deberán incluir junto al fichero .class del applet, todos los
ficheros de clase que utilice el applet y que no se encuentren dentro las clases estándar de Java
contenidas en de la Máquina Virtual.
Cuando un navegador Web se dispone a visualizar una página Web que contiene un applet o varios, el
navegador carga además de la clase, en la que se encuentra el applet, todas las clases que utilice el
mismo, y se ejecuta en la máquina local del cliente; a partir de ahora cada vez que se haga referencia a
la "máquina local", se está hablando de la máquina dónde se ejecuta el navegador y carga el applet a
través de Internet.
Los applets se descargan utilizando el mismo protocolo que se utiliza para transferir las páginas Web,
es decir el protocolo de transferencia de hipertexto HTTP (HyperText Transfer Protocol).
Cabe destacar que los applets poseen una serie de capacidades que las aplicaciones no poseen, esto es
debido a que los applets tienen su propia clase llamada Applet, y disponen de tres interfaces
AppletStub, AppletContext y AudioClip, que les ofrecen apoyo para las siguientes tareas:
•
Los applets pueden reproducir sonidos.
•
Los applets pueden realizar la carga de documentos HTML, ya que se pueden comunicar con
el navegador Web gracias al interfaz AppletContext.
•
Pueden invocar métodos públicos de otros applets en la misma página Web, es decir, varios
applets de pueden comunicar entre sí de forma muy sencilla.
•
Se ejecutan dentro de una página Web cargada por un navegador.
Todas estas características de los que se han esbozado en estos puntos, se comentarán y tratarán de
forma más detenida en los distintos apartados de este tema.
Antes de seguir hablando de los applets como si fuera algo abstracto o difícil de realizar, vamos a
comentar una serie de pasos básicos para crear nuestro primer applet utilizando la herramienta de
Microsoft Visual J++ 6.
Al arrancar Visual J++ en la pestaña de Nuevo seleccionamos la carpeta Páginas Web y dentro de esta
opción elegimos Subprograma en HTML, esta es una curiosa forma que se ha elegido para nombrar a
los applets. Algunos autores hacen referencia a los applets mediante los términos de miniaplicaciones
o subprogramas.
273
Programación en Java
© Grupo EIDOS
Le damos un nombre a nuestro proyecto y pulsamos Abrir, en este momento Visual J++ generará un
fichero fuente de Java llamado Applet1.java, que contiene la estructura de un applet de ejemplo, y
además genera un fichero HTML llamado Page1.htm, que va a ser la página Web que haga referencia
al applet antes mencionado.
Para empezar desde cero y evitar confusiones, vamos a eliminar todo el código que aparece en el
fichero fuente del applet y vamos a comenzar a escribir nosotros mismos nuestro primer applet.
En un primer lugar debemos importar el paquete que contiene a la clase Applet, ya hemos comentado
en este mismo apartado que un applet es un tipo de panel especial que se va a mostrar en el entorno de
un navegador Web dentro de una página HTML.
import java.applet.*;
Código fuente 173
A continuación debemos declarar nuestra clase. Debido a que vamos a crear un applet, nuestra clase
heredará de la clase Applet. Antes de seguir cambiaremos el nombre de nuestro fichero fuente para
cambiar el nombre de la clase del applet, en mi caso lo he llamado AppletSencillo, y la declaración de
la clase se muestra en el Código fuente 174.
public class AppletSencillo extends Applet{
}
Código fuente 174
Nuestro applet no va hacer mucho, simplemente vamos a mostrar un mensaje en la superficie del
applet, para ello vamos a sobrescribir el método paint() de la clase Applet. Más adelante en este
capítulo comentaremos el ciclo de vida de los applets y que implica sobrescribir el método paint(),
también trataremos la clase Graphics y veremos como mostrar gráficos en un applet, de momento sólo
debemos saber que el parámetro de la clase Graphics que recibe el método paint() representa a la
superficie del applet.
Para mostrar el mensaje "Hola Mundo", implementamos el método paint() como indica el Código
fuente 175.
public void paint(java.awt.Graphics g){
g.drawString("Hola Mundo",10,10);
}
Código fuente 175
De esta forma el código completo de nuestra clase es el Código fuente 176.
import java.applet.*;
public class AppletSencillo extends Applet{
public void paint(java.awt.Graphics g){
274
© Grupo EIDOS
14. Applets de Java: introducción a los Applets
g.drawString("Hola Mundo",10,10);
}
}
Código fuente 176
Como se puede ver en el código, un applet no presenta ningún método main(), como ocurría con las
aplicaciones. En la aplicaciones era necesario porque era el método de arranque de la clase y era dónde
se instanciaba un objeto la clase de la aplicación. Sin embargo esto no pasa en los applets, como se
puede ver nosotros no creamos ninguna instancia de la clase de nuestro applet, ahora bien, alguien
debe hacerlo, este alguien es el navegador Web.
El navegador Web crea una instancia de la clase de nuestro applet a través de la máquina virtual (MV)
que contiene, cuando encuentra la etiqueta HTML que hace referencia al applet crea de forma
automática una instancia del mismo.
Ahora compilamos nuestro applet. Al compilar el applet se genera el fichero .class correspondiente, al
igual que ocurría con las aplicaciones.
Una vez compilado y generado nuestro applet tenemos que ver la forma de ejecutarlo, pero antes de
ejecutarlo debemos modificar la página Web Page1.htm para que haga referencia a nuestro applet, ya
que nosotros hemos cambiado el nombre del mismo, y la página Web sigue haciendo referencia a la
clase Applet1.
Para modificar la página Web hacemos doble click sobre ella en el Explorador de proyectos y en ese
momento se abre la página Web en el área que teníamos reservada para el editor de código. El aspecto
que ofrece el entorno de desarrollo se puede ver en la Figura 117.
Figura 117
El editor reservado para la página Web presenta tres vistas diferente de la página HTML: Diseño,
Código y Vista rápida. En la parte de diseño podemos insertar elementos HTML o modificar los
existentes, de forma similar a como lo podemos hacer con otros editores HTML más avanzados y
sofisticados como puede ser Microsoft FrontPage, en esta vista podemos ver el aspecto que tiene el
applet dentro de la página HTML, en este caso no estamos viendo nuestro applet sino el que había
creado de ejemplo Visual J++.
275
Programación en Java
© Grupo EIDOS
Si seleccionamos el área que representa al applet y pulsamos con el botón derecho del ratón podemos
seleccionar la opción de menú Propiedades. Al seleccionar esta opción del menú contextual podemos
modificar las propiedades del applet desde una ventana de propiedades que aparece en el entorno y
que se suele encontrar debajo del explorador de proyectos.
En la ventana de Propiedades vamos a modificar la propiedad code del applet, esta propiedad debe
indicar el nombre de la clase del applet que queremos mostrar en la página Web. Por lo tanto el valor
de esta propiedad va a ser AppletSencillo, también vamos a modificar las propiedades width y height
que indican las dimensiones del applet, yo he indicado el valor 100 para ambas propiedades. También
podemos modificar las dimensiones del applet seleccionando y arrastrando el ratón, modificando de
esta forma su tamaño.
Una vez realizadas estas modificaciones sobre las propiedades, ya podemos ver nuestro applet en el
lugar en el que aparecía el applet de ejemplo creado por Visual J++. Como se puede ver en la Figura
118, la vista Diseño muestra el applet en un estado similar al que presentaría en ejecución, y como era
deseado y esperado muestra el mensaje Hola Mundo.
Figura 118
En la vista Código vemos el código HTML de la página Web (Figura 119), aunque el applet lo
seguimos viendo en modo de ejecución, para ver el código HTML que se corresponde con la inclusión
del applet en la página Web seleccionamos el applet, pulsamos con el botón derecho del ratón y
seleccionamos la opción de menú Ver siempre como texto, de esta forma podemos ver los valores de la
etiqueta <APPLET>. Esta etiqueta la discutiremos en el apartado dedicado a los applets y el código
HTML.
Figura 119
276
© Grupo EIDOS
14. Applets de Java: introducción a los Applets
La última vista, Vista rápida, se muestra la página Web de la forma en la que se mostraría en un
navegador que soporte Java, esta vista si que muestra la verdadera ejecución del applet, no como
ocurría en la vista de diseño, aunque al ser un applet tan sencillo en ambas vistas ofrece el mismo
aspecto.
Por lo tanto si en la vista rápida vemos nuestro applet de forma correcta, es como si ya lo hubiésemos
probado en un navegador, pero de todas formas recomiendo ejecutar el applet directamente sobre un
navegador Web.
Para ejecutar el applet pulsamos directamente el botón representado por una tecla "play" en el entorno
de desarrollo, es decir, al igual que ejecutábamos una aplicación. En ese momento se ejecuta el applet,
pero no mediante un navegador Web, sino mediante una herramienta del entorno Visual J++
denominada Visor de subprograma. El aspecto de este visor se puede ver en la Figura 120, y la
función del mismo es el de desplegar applets, además ofrece un menú que no permite interactuar con
el applet a través de una serie de opciones como: Recargar, Iniciar, etc.
Figura 120
Pero si queremos ejecutar el applet desde el entorno de Visual J++ 6 y que además arranque de forma
automática el navegador Web para que se ejecute el applet en su interior, debemos acudir a las
propiedades del proyecto y en la pestaña Inicio, en la lista desplegable Cuando se ejecute el proyecto,
cargar:, seleccionamos la página HTML que hace referencia a nuestro applet.
De esta forma cuando ejecutemos el proyecto, se lanzará el navegador Web (Internet Explorer) y
cargará la página Web que hace referencia a nuestro applet, y se ejecutará por lo tanto el applet como
se puede comprobar en la Figura 121.
Esta es la forma más recomendable para probar un applet desde el entorno de desarrollo, pero existe
otra forma. Simplemente localizamos en el Explorador de Windows la página Web que hace
referencia a nuestro applet y hacemos doble click sobre ella, de esta forma se lanzará el navegador
Web y cargará la página en cuestión.
Ahora vamos a pasar a construir este mismo applet pero desde el entorno de desarrollo de la
herramienta JBuilder 3.5. Para ello creamos un nuevo proyecto utilizando (si queremos) el asistente de
proyectos. Una vez creado el proyecto desde el menú de opciones seleccionamos la opción Archivo/
Nuevo y seleccionaremos de la galería de objetos el applet, en ese momento se lanzará el asistente de
creación de un nuevo applet.
277
Programación en Java
© Grupo EIDOS
Figura 121
El primer paso de este asistente, que se puede ver en la Figura 122, permite indicar a que paquete va a
pertenecer nuestra clase y el nombre de la misma. En la opción clase base podemos elegir la clase
Applet de la que queremos heredar, existen dos clases: Applet del paquete java.applet y JApplet del
paquete javax.swing. JApplet es un componente Swing que hereda de la clase java.applet.Applet, es la
versión Swing de los applets, la clase javax.swing.JApplet la veremos en próximos capítulos. En el
ejemplo nuestro applet se llama AppletEjemplo y hereda de la clase java.applet.Applet.
En el primer paso del asistente para applets también podemos indicar si queremos que se generen
comentarios, si queremos que se ejecute como una aplicación cuando proceda y si deseamos que se
generen todos los métodos del ciclo de vida de los applets, estos métodos los veremos en el apartado
dedicado al ciclo de vida de los applets.
Figura 122
En la figura 7, se definen los parámetros el applet, en nuestro ejemplo el applet no va a tener ninguno,
ya que consiste únicamente en escribir un mensaje en la superficie del applet.
278
© Grupo EIDOS
14. Applets de Java: introducción a los Applets
Figura 123
En el último paso se nos permite especificar los parámetros de configuración de applet en la página
HTML, es decir, el nombre de la clase, las dimensiones y la alineación. También podemos indicar si
queremos o no que se genere de forma automática la página HTML que va a contener nuestro applet,
de la página podemos especificar su nombre y título.
Figura 124
Al pulsar finalizar se generará el fichero fuente del applet y la página HTML que lo contiene. Ahora
accedemos al código fuente y añadimos el método paint() con el mismo código que vimos
anteriormente para Visual J++. Una vez compilado el applet podemos pulsar el botón "play" para la
ejecución del mismo.
279
Programación en Java
© Grupo EIDOS
En ese momento aparece la pantalla de configuración del proyecto para ejecutar el applet (Figura 125),
esta pantalla que aparece únicamente la primera vez, nos permite asignar la clase principal del applet,
al igual que ocurría con las aplicaciones Java, también es posible definir las dimensiones del applet y
los parámetros, pero estos datos ya los hemos indicado anteriormente en el asistente para la creación
de applets.
Figura 125
Una vez indicada la información necesaria para ejecutar el applet JBuilder inicia la ejecución del
mismo en una herramienta propia de JBuilder, comparable al appletviewer del JDK o al visor de
subprograma del Visual J++. El aspecto del visor de applets de JBuilder se puede comprobar en la
Figura 126.
Figura 126
280
© Grupo EIDOS
14. Applets de Java: introducción a los Applets
Ya hemos visto como crear y ejecutar un applet con Visual J++ y JBuilder, ahora sólo nos queda hacer
lo mismo con la herramienta de Sun JDK. Para ello crearemos el fichero fuente de Java que va a
contener el código del applet que sería el Código fuente 177.
import java.applet.*;
public class AppletSencillo extends Applet{
public void paint(java.awt.Graphics g){
g.drawString("Hola Mundo",10,10);
}
}
Código fuente 177
Compilamos el código con la herramienta javac y creamos una página HTML que haga referencia a la
clase de nuestro applet. Al hacer doble clic sobre la página, ya podríamos ver el applet en nuestro
navegador Web, pero también podemos utilizar la herramienta appletviewer que se ofrece con el JDK.
Para ejecutar nuestro applet con el appletviewer únicamente deberemos escribir la instrucción en la
línea de comandos, como se muestra en el Código fuente 178.
appletviewer pagina.html
Código fuente 178
Y el aspecto del appletviewer es el de la Figura 127.
Figura 127
Desde el menú Applet del appletviewer podemos invocar los distintos métodos del ciclo de vida de los
applets, que veremos en detalle más adelante en este mismo capítulo.
Al principio de este apartado comentábamos que para hacer disponibles los applets en la Web,
necesitamos de un servidor Web, ahora bien, para probarlos, es decir, en tiempo de desarrollo de los
mismos, puede no ser necesario. Así con nuestro sencillo applet lo probaríamos en una máquina local
y una vez que hemos comprobado que funciona lo copiaríamos al servidor Web que corresponda.
281
Programación en Java
© Grupo EIDOS
Este ha pretendido ser el primer contacto con los applets, hemos realizado un applet muy sencillo y lo
hemos realizado desde tres herramientas distintas: Microsoft Visual J++ 6, Borland JBuilder 3.5 y el
JDK 1.3 de Sun. También hemos visto las diferentes formas que tenemos de ejecutarlo.
En los siguientes apartados profundizaremos más en todo lo relacionado con los applets.
El ciclo de vida de los applets
Los applets podemos decir que presentan un ciclo de vida, es decir, durante su ejecución pasan por
diferentes estados.
El ciclo de vida de vida de los applets es controlado por cuatro métodos de la clase Applet, estos
métodos son init(), start(), stop() y destroy().
Cuando se carga una página Web que contiene un applet, éste pasa por varias etapas durante el tiempo
que aparece en pantalla, el applet realiza unas tareas muy diferentes durante cada una de estas etapas.
Si queremos que en una etapa del ciclo de vida de un applet se realice una tarea determinada
deberemos sobrescribir el método adecuado. No siempre es necesario sobrescribir todos los métodos
del ciclo de vida de un applet.
A continuación se comenta cuando se ejecutan cada uno de los métodos del ciclo de vida de un applet.
El método init() es llamado una única vez por la Máquina Virtual de Java (JVM) del navegador
cuando el applet se carga por primera vez, antes de ser mostrado al usuario, es en este método donde
se deben inicializar los objetos, construir los interfaces de usuario, etc.
El método start() es llamado cada vez que la página Web que contiene el applet es mostrada en la
pantalla del usuario.
Al método stop() se le llama cada vez que el usuario carga otra página Web, es decir, cuando se
abandona la página que contiene al applet.
El último método, destroy(), es llamado una única vez, justo antes de ser eliminado el applet, cuando
se abandona el navegador.
Este método es invocado automáticamente para realizar cualquier limpieza que sea necesaria, por lo
general no es necesario sobrescribir este método, sólo será necesario cuando se tengan recursos
específicos que necesiten ser liberados.
Es durante esta etapa final de salida, cuando la Máquina Virtual de Java completa algunas funciones
de recolección de basura para asegurar que los recursos que empleó el applet sean borrados de la
memoria y de que el applet sea completamente destruido cuando se salga de él.
Como se ha podido observar en el ciclo de vida de un applet los métodos init() y destroy() se lanzarán
solamente una vez cada uno, y sin embargo los métodos start() y stop() se lanzarán las veces que sean
necesarios, según interactúe el usuario con el navegador.
La Figura 128 muestra la relación entre los métodos del ciclo de vida del applet y los acontecimientos
que los marcan:
282
© Grupo EIDOS
14. Applets de Java: introducción a los Applets
Figura 128
Vamos a realizar un seguimiento a través del ciclo de vida de un applet, para lo cual vamos a
apoyarnos en una clase Ventana, que va a ser muy sencilla y cuyo código es el Código fuente 179.
import java.awt.*;
import java.awt.event.*;
public class Ventana extends Frame{
public Ventana(String titulo){
super(titulo);
setSize(150,150);
show();
addWindowListener(new AdaptadorVentana(this));
}
}
class AdaptadorVentana extends WindowAdapter{
private Ventana fuente;
public AdaptadorVentana(Ventana fuente){
this.fuente=fuente;
}
public void windowClosing(WindowEvent evento){
fuente.dispose();
}
}
Código fuente 179
Si algún alumno tiene problemas para entender el código anterior, le remito al capítulo dedicado al
interfaz de usuario en Java.
La clase Ventana la vamos a utilizar en nuestro applet para crear una ventana cada vez que se ejecuta
un método del ciclo de vida de un applet. Para diferenciar las ventanas, a cada una de ellas le pasamos
a su constructor el nombre del método del ciclo de vida que las ha llamado. Es decir, cada ventana
tendrá un título distinto.
El código del applet es el Código fuente 180.
import java.applet.*;
public class CicloVida extends Applet{
public void init(){
Ventana ventana=new Ventana("init()");
}
283
Programación en Java
public void
Ventana
}
public void
Ventana
}
public void
Ventana
}
}
© Grupo EIDOS
start(){
ventana=new Ventana("start()");
stop(){
ventana=new Ventana("stop()");
destroy(){
ventana=new Ventana("destroy()");
Código fuente 180
En este caso cada clase se encuentra en un fichero fuente, es decir, existen los ficheros Ventana.java y
CicloVida.java.
Si ejecutamos nuestro applet pulsando play (en cualquiera de los entornos: Visual J++ o JBuilder),
como ya vimos en el apartado anterior, o bien con el appletviewer o directamente con te navegador
Web deben aparecer dos ventanas, una creada por el método init() y otra por el método start().
Una vez que hemos cargado el applet, si cerramos el navegador aparecen momentáneamente otras dos
ventanas, creadas esta vez por el método stop() y destroy(), ya que además de descargar el applet se
cierra el navegador. Hasta aquí todo es correcto y sucede tal y como habíamos explicado
anteriormente.
Un ejemplo de ejecución del applet se puede ver en la Figura 129.
Figura 129
Continuamos con la ejecución de nuestro applet. Pero si cargamos una página distinta, además de
ejecutarse el método stop() se ejecuta el método destroy(), cosa que en absoluto es correcta, sólo se
debería ejecutar el método stop(), ya que el navegador no se ha cerrado.
Y si volvemos a la página del applet (pulsando la flecha hacia atrás del navegador) además de
ejecutarse el método start() también se ejecuta el método init(), situación que también es incorrecta.
Esto no es debido a que nuestro applet esté mal construido, sino que es debido a una implementación
distinta dentro de la máquina virtual del navegador Web. En mi caso he detectado este fallo en el
navegador Internet Explorer en su versión 5.0.
284
© Grupo EIDOS
14. Applets de Java: introducción a los Applets
Si probamos este mismo applet cargando la página que lo contiene desde el navegador Netscape
Navigator en su versión 4.5 el applet funciona correctamente, es decir, al cambiar de página se ejecuta
únicamente el método stop() y al volver a la página el método start().
Realizo estos comentarios, sólo para las versiones señaladas de los navegadores, que son además los
que se han utilizado para las pruebas de los applets que aparecen en este curso.
Estos cuatro métodos que componen el ciclo de vida de un applet se ejecutan, como hemos podido
comprobar, de forma automática y siguiendo un orden determinado.
Otro método que también se ejecuta de forma automática es el método paint(). Este método, que ya
hemos comentado someramente en el apartado anterior, tiene la función de pintar o mostrar la
apariencia del applet en la pantalla. Este método es otro método de la clase Applet que podemos
sobrescribir si lo necesitamos, igual que ocurría con los métodos del ciclo de vida de los applets.
Con respecto al ciclo de vida del applet el método paint() se ejecuta por primera vez inmediatamente
después del método start() y luego cada vez que se necesita "repintar" la pantalla, es decir, cada vez
que se refresca la superficie que comprende al área contenida por el applet. La máquina virtual lanzará
el método paint() cada vez que el applet desaparezca total o parcialmente de la pantalla.
El método paint() recibe como parámetro un objeto de la clase Graphics, que pertenece al paquete
java.awt. La instancia del objeto de la clase Graphics representa la superficie del applet, y lo vamos a
utilizar para escribir y dibujar en la superficie del mismo. En el apartado correspondiente veremos
como dibujar de forma básica en la superficie de nuestro applet.
El método paint() también se ejecuta cuando se llama al método repaint() de la clase Applet.
Seguridad en los applets
Java incluye características de seguridad para reforzar su empleo en Internet. Un problema de
seguridad potencial tiene que ver con los applets de Java, que son código ejecutable que opera en la
máquina local del usuario que se conecta a la página Web en la que se encuentran los applets. Java
emplea verificación de código y acceso limitado al sistema de archivos para garantizar que el código
no dañe nada en la máquina local.
Se debe tener claro que el applet reside en el servidor Web, pero cuando se carga la página Web que lo
contiene, las clases del applet se transfieren hasta la máquina cliente, que es dónde se inicia la
ejecución del applet.
En este apartado, como su nombre indica, vamos a comentar los mecanismos de seguridad que ofrece
Java a la hora de ejecutar los applets en la máquina cliente o máquina local, y que restricciones de
seguridad impone.
Existen dos módulos software encargados del análisis de la seguridad y de la imposición de
restricciones, estos módulos son el ClassLoader y el Security Manager.
El ClassLoader (clase abstracta del paquete java.lang) tiene las siguientes funciones: por un lado
colocar las clases que integran cada applet en un espacio de nombres único y estanco, de forma que es
imposible para un applet acceder o manipular recursos de otros applets.
Por otro lado analiza los bytecodes que componen la representación binaria intermedia del código del
applet para asegurar que son conformes a las normas de Java y no realizan operaciones peligrosas,
285
Programación en Java
© Grupo EIDOS
como conversiones de tipos ilegales, accesos a índices inexistentes de arrays, paso de parámetros
incorrectos, etc. Todas estas funciones las lleva a cabo un verificador de código (bytecode verifier).
Las comprobaciones que realiza el verificador de código (para más información sobre el proceso de
verificación, consultar la siguiente dirección de Internet: http://www.javasoft.com/sfaq/verifier.html) se
ven facilitadas por la filosofía de diseño de Java. Así, por ejemplo, Java carece de punteros, por lo que
es imposible hacer referencia a una posición de memoria explícita. Además, el intérprete comprueba
siempre que el acceso a un array se realiza a través de un índice dentro de un rango válido.
Cuando se carga desde un servidor a nuestra máquina local una página Web que contiene algún applet,
el código del applet es verificado por el segundo módulo de seguridad para garantizar que el applet
sólo emplea llamadas y métodos válidos del sistema. Si el applet pasa la verificación se le permite ser
ejecutado, pero a los applets no se les da acceso al sistema de archivos de la máquina local.
El objeto responsable de evitar que los applets realicen una operación potencialmente peligrosa es el
SecurityManager (gestor de seguridad, clase abstracta del paquete java.lang). Si el SecurityManager
determina que la operación está permitida, deja que el programa siga con su ejecución.
Cuando una aplicación Java se ejecuta, no hay SecurityManager. Esto es debido a que se espera que
cualquier aplicación que el usuario ejecuta es segura para ese usuario. De todas formas podemos
escribir e instalar propio SecurityManager para nuestras aplicaciones, por lo tanto en un principio las
aplicaciones Java no tiene ningún tipo de restricción.
Cada navegador de páginas Web con capacidad para Java tiene un objeto SecurityManager que
comprueba si se producen violaciones de las restricciones de seguridad de los applets. Cada vez que
una clase Java hace una petición de acceso a un recurso del sistema, la instancia de la clase
SecurityManager comprueba la petición realizada, si no se tiene permiso para efectuar el acceso éste
se deniega y en caso contrario se le concede y se sigue normalmente con la ejecución del applet.
Cuando el SecurityManager detecta una violación crea y lanza un objeto SecurityException y se
detiene la ejecución del applet. Normalmente, el constructor de una SecurityException muestra un
mensaje de advertencia que indica que se ha producido una excepción de seguridad.
En la Figura 130, se puede ver de forma ordenada todo el proceso que se sigue para comprobar que el
applet es seguro, desde que el applet se empieza a cargar desde Internet hasta que empieza a ejecutarse
en la máquina cliente:
Figura 130
286
© Grupo EIDOS
14. Applets de Java: introducción a los Applets
La clase SecurityManager posee una serie de métodos para realizar las siguientes acciones que
refuerzan su política de seguridad:
•
Determinar si una petición de conexión, a través de la red, desde una máquina en un puerto
específico puede ser aceptada.
•
Comprobar si un hilo de ejecución puede manipular otro hilo de ejecución diferente.
•
Comprobar si se puede establecer una conexión vía socket con una máquina remota en un
puerto específico.
•
Impedir la creación de un nuevo objeto de la clase ClassLoader.
•
Impedir la creación de un nuevo objeto de la clase SecurityManager, ya que podría
sobrescribir la política de seguridad existente.
•
Comprobar que un fichero puede ser borrado.
•
Comprobar si un programa puede ejecutar otro programa en el sistema local.
•
Impedir que un programa termine con la ejecución de la Máquina Virtual de Java.
•
Comprobar si se puede acceder a una librería dinámica.
•
Comprobar si se puede escuchar en un determinado puerto para esperar peticiones de
conexión.
•
Determinar si un programa puede cargar paquetes Java específicos.
•
Determinar si un programa puede crear nuevas clases en un paquete Java específico.
•
Identificar a que propiedades del sistema se puede acceder.
•
Comprobar si se puede leer un fichero.
•
Comprobar si se puede escribir datos en un fichero.
•
Comprobar si un programa puede crear su propia implementación de los sockets para la red.
•
Establecer si un programa puede crear una ventana de alto nivel. Cualquier ventana que se
cree incluirá algún tipo de advertencia visual.
Después de explicar los mecanismos de los que dispone Java para verificar que un applet es seguro, se
va a pasar a detallar las restricciones de seguridad que poseen los applets (para más información sobre
la seguridad de Java se puede consultar la siguiente dirección: http://www.javasoft.com/sfaq/:
•
Los applets no pueden cargar librerías o definir métodos nativos. Si un applet pudiera definir
una llamada a un método nativo le daría acceso a la máquina en la que se ejecuta.
•
Los applets no pueden detener la ejecución de la Máquina Virtual de Java.
•
No pueden leer ni escribir en el sistema de archivos de la máquina del navegador. No se
pueden abrir ni crear ficheros en la máquina del cliente, ni tampoco crear o leer directorios. Si
287
Programación en Java
© Grupo EIDOS
se intenta crear o abrir un objeto java.io.File o java.io.FileInputStream o java.io.FileOutputStream en la máquina del cliente dará lugar a una excepción de seguridad.
•
Un applet no puede hacer conexiones a través de la red, sólo se puede conectar con el servidor
desde el que se cargó el applet.
•
No pueden conectarse a puertos del cliente.
•
Los applets no pueden actuar como servidores de la red esperando para aceptar conexiones de
sistemas remotos.
•
Los applets que se quieran comunicar entre sí deben estar en la misma página Web, en la
misma ventana del navegador, y además deben ser originarios del mismo servidor.
•
No pueden instanciar un objeto de las clases ClassLoader o SecurityManager.
•
No pueden acceder o cargar clases de paquetes llamados java que no sean los paquetes
estándar del API de Java.
•
No pueden ejecutar ningún programa en el sistema local del cliente.
•
No pueden leer todas las propiedades del sistema en el que se cargaron. En particular, no
puede leer ninguna de las siguientes propiedades del sistema: user.name (nombre de la cuenta
del usuario), user.home (directorio home del usuario), java.home (directorio de instalación de
Java), user.dir (directorio de trabajo actual del usuario) y java.class.path (directorio en el que
se encuentran las clases de Java).
•
Los applets tampoco pueden definir ninguna propiedad del sistema.
•
Las ventanas que abren los applets tienen un aspecto diferente, aparece la advertencia de que
se trata de una ventana abierta o creada por un applet.
•
Los applets no pueden instanciar objetos COM (Component Object Model) o comunicarse con
ellos, esto es para preservar la seguridad, ya que los objetos COM son poco seguros y
permiten realizar operaciones peligrosas en la máquina local.
Los applets se pueden cargar de dos maneras, la forma en que un applet entra en el sistema afecta a lo
que se le va a permitir hacer. Si un applet se carga a través de la red, entonces es cargado por el
ClassLoader y está sujeto a las restricciones impuestas por el SecurityManager.
Pero si un applet reside en el disco local del cliente, y en un directorio que está en el java.class.path
(propiedad de la clase System, que indica en que lugar se encuentran las clases de Java) entonces es
cargado por el FileLoader del sistema. Las diferencias principales con los anteriores son las siguientes:
288
•
Pueden leer y escribir ficheros.
•
Pueden cargar librerías en la máquina del cliente.
•
Pueden ejecutar procesos, es decir, pueden ejecutar cualquier programa en la máquina del
cliente.
•
Pueden parar la ejecución de la Máquina Virtual de Java.
•
No son pasados por el verificador de bytecode.
© Grupo EIDOS
14. Applets de Java: introducción a los Applets
Esta relajación de la seguridad es debido a que se supone que los applets cargados del sistema local
son más fiables que los que se cargan a través de la red de forma anónima.
También se relajará la seguridad de los applets en el caso de que éstos se encuentren firmados
digitalmente a través de un certificado.
El único "agujero" en la seguridad de un applet es que éste puede reservar mucha cantidad de memoria
creando continuamente una gran cantidad de objetos, por ejemplo, un applet podría crear un gran
número de ventanas agotando el sistema GUI (Graphical User Interface) de la máquina, iniciar la
ejecución de muchos hilos de ejecución paralelos o cargar desde la red una gran cantidad de datos, este
tipo de ataque se denomina ataque de negación de servicio (denial of service attack).
Este ataque consume muchos recursos del sistema y puede disminuir la velocidad de la máquina o de
la conexión a la red de forma considerable. Aunque este ataque es molesto no puede causar un daño
real al sistema, se considera que este ataque está fuera del modelo de seguridad de Java.
Trusted/Untrusted applets
Cuando decimos que un applet es trusted (de confianza), este applet podrá saltarse las restricciones de
seguridad que vienen impuestas a los applets; pero si es untrusted (no es de confianza) tendrá que
ajustarse a las restricciones.
Un applet de Java se ejecuta dentro de un entorno ofrecido por la Máquina Virtual que evita que el
applet realice acciones sobre nuestro sistema que pueden ser potencialmente peligrosas desde el punto
de vista de la seguridad. La Máquina Virtual de Java deberá discernir si debe permitir que el applet se
salte las restricciones de seguridad o que por el contrario las cumpla escrupulosamente, es decir, debe
distinguir si un applet es trusted o untrusted.
Por defecto todos applets que cargamos a través de Internet son untrusted, por lo tanto se deben ceñir a
las restricciones de seguridad impuestas por la Máquina Virtual.
Para que un applet sea trusted debe ir firmado digitalmente o bien las clases del applet deben estar
situadas en el CLASSPATH. De esta forma, si nosotros hemos creado nuestro applet, y nuestra
variable de entorno CLASSPATH tiene el valor c:\Java\clases deberemos situar los ficheros de clase
del applet en el directorio c:\Java\clases. Esto sólo es recomendable para applets que hayamos
desarrollado nosotros o alguna persona de confianza.
Diferencias entre applets y aplicaciones
A continuación se comentan las diferencias entre las aplicaciones y los applets de Java. Las
aplicaciones son programas individuales que se ejecutan utilizando sólo el intérprete de Java, los
applets sin embargo se ejecutan desde un navegador de páginas Web, o desde un visor de applets.
Una referencia a un applet se introduce en una página Web empleando una etiqueta HTML especial.
Cuando un navegador carga la página, carga el applet desde el servidor Web y lo ejecuta en el sistema
local.
Ya que los applets se ejecutan dentro de un navegador, tienen la ventaja de la estructura que éste les
ofrece: una ventana, un contexto de manejo de eventos y gráficos y el interfaz de usuario que la rodea.
289
Programación en Java
© Grupo EIDOS
Sin embargo, todas estas ventajas que tienen los applets sobre las aplicaciones, se ven ensombrecidas
por las restricciones impuestas a los applets. Ya que los applets pueden desplegarse desde cualquier
parte y ejecutarse en un sistema cliente, las restricciones son necesarias para prevenir que un applet
cause daños al sistema o rupturas de seguridad.
Sin estas restricciones los applets podrían escribirse para contener virus, gusanos, etc.… y podrían
propagarse por toda la red en cuestión de horas. Sin embargo las aplicaciones de forma explícita no
ofrecen estas restricciones de seguridad.
La etiqueta applet
En este apartado vamos a comentar las características que ofrece el lenguaje HTML para la integración
de los applets dentro de páginas Web. Puesto que los applets están diseñados para usarse en
conjunción con la WWW (World Wide Web), es necesario contar con una manera estándar de llamar a
los applets dentro de un navegador Web.
Esto lo permiten un juego de etiquetas HTML (Hypertext Markup Language) que se creó para
especificar toda la información necesaria para ejecutar un applet. Así, el código HTML puede
emplearse para hacer lo siguiente:
•
Especificar el directorio del applet.
•
Indicar la ubicación del código que emplea el applet.
•
Especificar el tamaño en pantalla del applet.
•
Ofrecer alternativas para los navegadores que no tengan capacidad para Java.
•
Pasar parámetros al applet.
Después de crear el applet y escribir su código, debe añadirse a una página HTML para poder
ejecutarse dentro de un navegador. Para ello se debe utilizar la etiqueta <APPLET> del lenguaje
HTML. La etiqueta <APPLET> posee los siguientes atributos que a continuación se muestran en la
Figura 131, los que son opcionales se encuentran encerrados entre corchetes.
Figura 131
290
© Grupo EIDOS
14. Applets de Java: introducción a los Applets
En su forma más elemental, utilizando sólo los atributos CODE, WIDTH y HEIGHT, la etiqueta
<APPLET> crea un espacio del tamaño deseado en el que se cargará y ejecutará el applet. A
continuación se pasa a describir los atributos de la etiqueta <APPLET>.
Codebase
Este atributo opcional especifica la dirección URL base del applet, es decir, el directorio o carpeta que
contiene el código del applet. Si este atributo no es especificado se utiliza el URL del documento
HTML.
Code
Este atributo requerido ofrece el nombre del fichero .CLASS que contiene el applet. Si no se utiliza el
atributo CODEBASE, el archivo se busca en el mismo directorio del archivo HTML que referencia al
applet.
Alt
Este atributo opcional especifica el texto que debe ser mostrado en pantalla en el caso de que el
navegador utilizado para ver las páginas HTML no tenga capacidad para Java.
Name
Esta atributo de carácter opcional especifica un nombre para el applet. Esto hace posible que los
applets de una misma página Web se puedan comunicar entre sí, haciendo referencia a su nombre.
Width, Height
Estos atributos requeridos definen el ancho y alto (en pixels) del applet, sin tener en cuenta los
diálogos o ventanas que pueda mostrar el applet.
Align
Este atributo de naturaleza opcional especifica la alineación del applet. Los posibles valores de este
atributo son los que se enumeran a continuación:
•
TEXTTOP: alinea la parte superior del applet con la parte superior del texto más alto en la
línea.
•
TOP: alinea el applet con el elemento más alto en la línea, el cual puede ser otro applet, una
imagen, o la parte superior del texto.
•
ABSMIDDLE: alinea el centro del applet con el centro del elemento más grande de la línea.
•
MIDDLE: alinea el centro del applet con el centro de la línea base del texto.
291
Programación en Java
© Grupo EIDOS
•
BASELINE: alinea la parte inferior del applet con la línea base del texto.
•
ABSBOTTON: alinea la parte inferior del applet con el elemento más bajo en la línea.
Vspace, Hspace
Estos atributos opcionales especifican el número de pixels que debe haber encima y debajo del applet
(VSPACE) y a cada lado del applet (HSPACE), es decir, los márgenes del applet.
Fuera ya de los atributos de la etiqueta <APPLET> pueden aparecer otras etiquetas que se comentan a
continuación:
<PARAM>
Esta etiqueta permite especificar parámetros de un applet. Los applets leen los valores de los
parámetros con el método getParameter() de la clase Applet.
Los parámetros para los applets son como los argumentos de la línea de comandos para las
aplicaciones. Permiten que el usuario pueda personalizar algunas de las operaciones o características
del applet. Al definir parámetros se aumenta la flexibilidad del applet, el applet podrá ejecutarse en
diferentes situaciones sin necesidad de modificar su código y volver a compilarlo.
Al definir un parámetro para un applet mediante la etiqueta <PARAM> se debe tener en cuenta que es
lo que se va a permitir al usuario configurar del applet, como se debe llamar el parámetro, que tipo de
valor va a tener el parámetro y cual es su valor por defecto.
Además de ofrecer parámetros al usuario para que pueda configurar el applet, se le debe indicar como
se pueden utilizar estos parámetros. Por lo tanto se debe implementar el método getParameterInfo() de
la clase Applet para que devuelva toda la información y ayuda referente a los parámetros del applet.
Los navegadores pueden utilizar esta información para ayudar a los usuarios e indicar que valores
puede dar a los parámetros.
Etiquetas HTML alternativas
Si el navegador que carga la página en la que se encuentra el applet no reconoce la etiqueta
<APPLET> al no tener capacidad para Java, entonces utiliza estas etiquetas alternativas cuando
muestre el documento HTML. Los navegadores con capacidad para Java ignoran todas estas etiquetas
alternativas.
292
Applets de Java: utilizando los applets
Interacción de los applets con el navegador web
Cuando realizamos la introducción a los applets, en el capítulo anterior, comentamos que los applets
pueden realizar una serie de operaciones apoyándose en el navegador Web. En este apartado vamos a
comentar con ejemplos una serie de características que ofrecen los applets y que en cierta medida son
exclusivas, y por lo tanto no las presentan las aplicaciones.
Lo primero que vamos a comentar acerca de las capacidades de los applets, es la posibilidad que
tienen éstos de comunicarse entre sí en una misma página Web.
Para realizar una comunicación entre diferentes applets de deben tener en cuenta dos restricciones de
seguridad:
•
Los applets deben estar en la misma página Web, en la misma ventana del navegador.
•
Los applets deben ser originarios del mismo servidor.
Un applet para comunicarse con otro puede hacerlo refiriéndose al nombre del applet con el que se
quiere comunicar, para ello se usa el método getApplet() del interfaz AppletContext. Este interfaz se
corresponde con el entorno o contexto en el que se encuentra un applet, es decir, el documento que
contiene al applet y los otros applets contenidos en el mismo documento.
El contexto del applet se obtiene utilizando el método getAppletContext() de la clase Applet, este
método devuelve un objeto que representa el contexto en el que se encuentra el applet.
Programación en Java
© Grupo EIDOS
Los métodos del interfaz AppletContext pueden ser usados para obtener información relativa al
entorno del applet. El método getApplet(), a partir de un nombre que se le pasa como parámetro, nos
devuelve una instancia de la clase Applet que representa al applet que posee el nombre que se le ha
pasado como parámetro a getApplet() y que se encuentra dentro del entorno correspondiente al
AppletContext. Si el applet no existe nos devolverá un nulo.
También se puede utilizar otro método del interfaz AppletContext, llamado getApplets(), que
devolverá una enumeración (objeto de tipo java.util.Enumeration) de todos los applets existentes en el
documento representado por el correspondiente AppletContext.
Para darle un nombre a cada applet se debe utilizar dentro del documento HTML el atributo name de
la etiqueta <APPLET>, así por ejemplo si queremos identificar con el nombre pepe a un applet
representado por la clase Ejemplo escribiríamos lo que vemos en el Código fuente 181.
<applet code="Ejemplo.class" width="450" height="200" name="pepe">
Código fuente 181
Vamos a ver todo esto mediante un ejemplo sencillo que va a consistir en un par de applets,
representados por las clases Remitente y Destinatario. Estos dos applets se van a enviar un mensaje
entre sí.
La clase Destinatario va a facilitar un método público llamado enviarMensaje() que permite a la clase
Remitente enviar un mensaje. El código de la clase Destinatario, es el que muestra el Código fuente
182.
import java.applet.*;
import java.awt.*;
public class Destinatario extends Applet{
private TextField mensaje;
public void init(){
mensaje=new TextField(20);
add(mensaje);
}
public void enviarMensaje(String men){
mensaje.setText(men);
}
}
Código fuente 182
Como se puede ver el método enviarMensaje() permite asignar texto a la caja de texto del applet que
actúa como destinatario. El código es bastante sencillo y no ofrece ninguna complicación.
Ahora vamos a ver el segundo applet, el que actúa de remitente. Este applet si que va a hacer uso del
interfaz AppletContext.
Este applet ofrece una caja de texto y un botón, en la caja de texto escribiremos el mensaje que
queremos que aparezca en la caja de texto del applet de destino, y al pulsar el botón invocaremos el
método enviarMensaje() de la clase Destinatario. El código de la clase Remitente aparece en el Código
fuente 183.
294
© Grupo EIDOS
15. Applets de Java: utilizando los applets
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
public class Remitente extends Applet implements ActionListener{
private Button boton;
private TextField mensaje;
public void init(){
boton=new Button("Enviar mensaje");
mensaje=new TextField(20);
add(mensaje);
add(boton);
boton.addActionListener(this);
}
public void actionPerformed(ActionEvent evento){
//se obtiene el contexto del applet
AppletContext contexto=getAppletContext();
//se obtiene la referencia al otro applet a través de su nombre
Applet destinatario=contexto.getApplet("destino");
//se invoca el método que nos interesa del applet Destinatario
((Destinatario)destinatario).enviarMensaje(mensaje.getText());
}
}
Código fuente 183
Especial atención merece el método actionPerformed(), que se ejecutará al pulsar el botón de envío de
mensaje del applet. En este método se obtiene primero el contexto del applet, es decir un objeto
AppletContext. A continuación se obtiene una referencia al applet que nos interesa, en este caso el
applet se llama destino. Y por último se invoca el método deseado del applet, para poder invocarlo
debemos realizar un casting (conversión de clases), ya que la clase Applet no dispone del método
obtenerMensaje(), este método pertenece a nuestra clase Destinatario.
En el código HTML de la página Web que contiene a los dos applets debemos asignar a la propiedad
name de la etiqueta APPLET, que hace referencia al applet Destinatario, el valor destino, ya que es la
cadena que le pasamos por parámetro al método getApplet(). El código HTML se muestra en el
Código fuente 184.
<HTML>
<HEAD>
<META NAME="GENERATOR" Content="Microsoft Visual Studio">
<META HTTP-EQUIV="Content-Type" content="text/html">
<TITLE>Document Title</TITLE>
</HEAD>
<BODY>
<APPLET code="Destinatario" width="250" height="30" name="destino">
</APPLET>
<hr>
<APPLET code="Remitente" width="300" height="130">
</APPLET>
</BODY>
</HTML>
Código fuente 184
En la Figura 132 se puede ver un ejemplo de la ejecución de estos dos applets cooperantes entre sí.
295
Programación en Java
© Grupo EIDOS
Figura 132
Antes habíamos comentado que el interfaz AppletContext ofrece dos métodos para tener referencias a
los applets dentro de una misma página Web, los métodos getApplet() y getApplets(). En el ejemplo
anterior hemos visto un ejemplo que utiliza el método getApplet(), ahora vamos a ver un nuevo
ejemplo que utiliza el método getApplets().
Vamos a crear un applet que muestre el nombre de las clases de todos los applets que se encuentren en
la página Web actual, evidentemente este applet también debe estar incluido en la página.
Para mostrar el nombre de las clases, nuestro applet va a utilizar un objeto de la clase TextArea. Al
pulsar el botón que ofrece el applet se procederá a obtener las referencias a los applets, para así poder
mostrar el nombre de la clase de cada uno de ellos. El código de este applet se indica en el Código
fuente 185.
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public class ListaApplets extends Applet implements ActionListener{
private TextArea listado;
private Button boton;
public void init(){
setLayout(new BorderLayout());
listado=new TextArea(5,30);
boton=new Button("Listar Applets");
add("North",listado);
add("South",boton);
boton.addActionListener(this);
}
public void actionPerformed(ActionEvent evento){
listarApplets();
}
public void listarApplets(){
AppletContext contexto=getAppletContext();
String nombreClase="";
listado.setText("");
//obtenemos la referencia a todos los applets de la página
Enumeration applets=contexto.getApplets();
//recorremos la lista de applets
while (applets.hasMoreElements()){
296
© Grupo EIDOS
15. Applets de Java: utilizando los applets
//obtenemos la referencia al applet
Object applet=applets.nextElement();
//recuperamos el nombre de la clase del applet
nombreClase=applet.getClass().getName();
//añadimos el nombre de la clase al área de texto
listado.append(nombreClase+"\n");
}
}
}
Código fuente 185
Como se puede observar se importa el paquete java.util, este paquete es necesario para poder utilizar la
clase Enumeration. Esta clase la utilizamos al ejecutar el método getApplets() del interfaz
AppletContext.
El método getApplets() nos devuelve un objeto de la clase Enumeration que contiene todos los applets
que existen en la página Web actual.
La clase Enumeration es una clase de utilidad que posee únicamente dos métodos hasMoreElements()
y nextElement(). El primero de ellos devuelve un valor booleano y se utiliza para comprobar si existen
más elementos en la enumeración, mientras que el segundo de los métodos devuelve el siguiente
elemento de la enumeración.
El método nextElement() devuelve siempre un objeto de la clase Object, ya que un objeto de la clase
Enumeration puede contener cualquier tipo de objeto.
Si situamos este nuevo applet en la misma página Web que contenía los dos anteriores y lo ejecutamos
mostrará el aspecto de la Figura 133.
Figura 133
Otra posibilidad que nos ofrecen los applets es la de cargar páginas en el navegador, es decir, podemos
indicar al navegador que muestre una página determinada. Para ello también nos vamos a apoyar en el
interfaz AppletContext, este interfaz ofrece el método showDocument() para realizar esta tarea. Este
método recibe como parámetro un objeto de la clase URL. La clase URL representa una URL
(Uniform Resource Locator) y se encuentra dentro del paquete java.net. Este paquete contiene una
serie de clases especializadas en la utilización de recursos y comunicaciones en Internet.
A continuación vamos a realizar un applet que muestre una ventana en la que podemos indicar la URL
que deseamos cargar en el navegador.
Vamos a crear, además de la clase del applet, una clase llamada VentanaURL que va a representar la
ventana que muestra el applet, antes de seguir con la exposición de este ejemplo vamos a ver el código
fuente completo en el Código fuente 186.
297
Programación en Java
© Grupo EIDOS
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.net.*;
public class MuestraDocumento extends Applet{
private VentanaURL ventana;
public void init(){
ventana=new VentanaURL(getAppletContext());
}
}
class VentanaURL extends Frame implements ActionListener{
//caja de texto que contiene la URL
private TextField textoURL;
private Button boton;
private AppletContext contexto;
//el constructor tiene el parámetro contexto para que le pasemos
//el contexto del applet
public VentanaURL(AppletContext contexto){
super("Mostrar Documento");
this.contexto=contexto;
boton=new Button("Muestra URL");
textoURL=new TextField(20);
setLayout(new GridLayout(1,2,5,5));
add(textoURL);
add(boton);
pack();
show();
boton.addActionListener(this);
addWindowListener(new AdaptadorVentana());
}
public void actionPerformed(ActionEvent evento){
String cadenaURL=textoURL.getText();
URL url=null;
//se crea la URL, y es necesario atrapar una excepcion
//que lanza su constructor
try{
url=new URL(cadenaURL);
}catch(MalformedURLException ex){
textoURL.setText("Error en la URL");
}
//se muestra la URL en el navegador
if (url!=null)
contexto.showDocument(url);
}
class AdaptadorVentana extends WindowAdapter{
public void windowClosing(WindowEvent evento){
dispose();
}
}
}
Código fuente 186
Como se puede ver la clase MuestraDocumento no hace mucho, el applet simplemente crea en su
método init() una instancia de la clase VentanaURL, a la que le pasa por parámetro el contexto del
applet, ya que la clase que va a realizar la operación de mostrar la URL va a ser la clase VentanaURL,
por lo tanto vamos a pasar a comentar la clase VentanaURL.
En el constructor de la clase VentanaURL creamos el interfaz de usuario, registramos los oyentes
necesarios para la pulsación del botón y para el cierre de la ventana. Además se inicializa el atributo
contexto con el contexto que se ha obtenido en el applet.
298
© Grupo EIDOS
15. Applets de Java: utilizando los applets
Al pulsar el botón se procede a recuperar la URL de la caja de texto y a continuación se muestra en el
navegador. Es necesario crear una instancia de la clase URL, ya que el método showDocument()
recibe un objeto de esta clase como parámetro. Al crear la URL se debe atrapar la excepción que lanza
su constructor, esta excepción es de la clase MalformedURLException, esta clase al igual que la clase
URL se encuentra en el paquete java.net.
El método showDocument() lo lanzamos sobre el atributo contexto, que como ya hemos comentado es
el contexto que nos proporciona el applet.
Si probamos este applet con Internet Explorer 5, volvemos a tener el problema de la ejecución del
método destroy() al abandonar la página Web, este problema ya lo comentábamos en el capítulo
anterior en el apartado dedicado al ciclo de vida de los applets. Como hemos dicho con IE 5 se ejecuta
el método destroy() por lo tanto perdemos el contexto del applet y no podemos volver a cargar otra
URL, es decir, el applet funciona sólo la primera vez. Sin embargo con Netscape Navigator 4.5 el
funcionamiento es correcto y podemos cargar tantas URLs como queramos.
Este applet, al consistir únicamente en una ventana, podemos insertarlo en la página Web con
dimensiones cero. En la Figura 134 se puede ver un ejemplo de la ejecución de este applet.
Figura 134
Otra característica que ofrecen los applets y que se encuentra íntimamente relaciona con el navegador
Web es la posibilidad de mostrar mensajes en la línea de estado del navegador. Para esto se vuelve a
utilizar un método del interfaz AppletContext, en este caso showStatus(). El método showStatus()
recibe como parámetro la cadena de texto que queremos mostrar en la línea de estado del navegador
Web.
Antes de continuar se debe aclarar que la línea de estado del navegador Web se encuentra en la zona
inferior del mismo y es dónde se indica en que estado se encuentra el documento cargado actualmente.
Para ilustrar la utilización de este nuevo método vamos a realizar un applet que consiste en una caja de
texto, en la que introduciremos el texto que queremos aparezca en la línea de estado del navegador, y
un botón con el que indicaremos que queremos asignar el texto correspondiente.
El código de este sencillo applet es el Código fuente 187.
299
Programación en Java
© Grupo EIDOS
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
public class LineaEstado extends Applet implements ActionListener{
private Button boton;
private TextField texto;
public void init(){
boton=new Button("Muestra Estado");
texto=new TextField(20);
setLayout(new GridLayout(1,2,5,5));
add(texto);
add(boton);
boton.addActionListener(this);
}
public void actionPerformed(ActionEvent evento){
AppletContext contexto=getAppletContext();
contexto.showStatus(texto.getText());
}
}
Código fuente 187
Y un ejemplo de la ejecución del mismo se puede observar en la Figura 135.
Figura 135
Hasta ahora se han comentado funciones de los applets que tienen que ver con el navegador Web
directamente, como el propio título del apartado indica, pero también vamos a incluir en este mismo
apartado un par de funciones de los applets, que aunque ya no tengan que ver directamente con el
navegador, pueden resultar interesantes.
La primera de estas funciones ya la habíamos comentado con anterioridad y consiste en el paso de
parámetros al applet desde la página Web. Este paso de parámetros se realiza mediante la etiqueta
<PARAM>.
Para recuperar el parámetro desde el applet utilizamos el método getParameter() de la clase Applet,
pasándole por parámetro una cadena que se corresponde con el nombre del parámetro.
El Código fuente 188 se corresponde con el de un applet que recoge un parámetro llamado nombre, y
muestra un saludo en la pantalla.
300
© Grupo EIDOS
15. Applets de Java: utilizando los applets
import java.applet.*;
import java.awt.*;
public class Parametros extends Applet{
private String parametro="";
public void init(){
parametro=getParameter("nombre");
if (parametro==null)
parametro= "persona desconocida";
}
public void paint(Graphics g){
g.drawString("Buenos días "+parametro,10,20);
}
}
Código fuente 188
Como se puede observar en el código, el método init() es el encargado de recuperar el parámetro y es
en el método paint() dónde se muestra el saludo utilizando el atributo del applet que posee el valor
ofrecido por el parámetro.
En la Figura 136 se muestra la ejecución de este applet, en las dos situaciones, con parámetro y sin el.
Figura 136
Y el código HTML de la página de la Figura 136 es el Código fuente 189.
<HTML>
<HEAD>
<META NAME="GENERATOR" Content="Microsoft Visual Studio">
<META HTTP-EQUIV="Content-Type" content="text/html">
<TITLE>Document Title</TITLE>
</HEAD>
<BODY>
<APPLET code="Parametros" height="50" name="destino" width="250">
<PARAM name="nombre" value="Pepe">
</APPLET>
<hr>
301
Programación en Java
© Grupo EIDOS
<APPLET code="Parametros" height="50" name="destino" width="250">
</APPLET>
</BODY>
</HTML>
Código fuente 189
La siguiente, y última función de los applets que vamos a comentar en este apartado, es la
reproducción se sonidos. Para reproducir sonidos disponemos del interfaz AudioClip que lo podemos
encontrar dentro del paquete java.applet, pero para utilizar un objeto de este tipo debemos antes
asignarle el fichero de sonido que representa.
El interfaz AudioClip ofrece tres métodos que nos permiten manipular un fichero de sonido, éstos son
play(), loop() y stop(). El método play() ejecuta el fichero de sonido, es decir, reproduce el sonido, el
método loop() también lo reproduce pero en un bucle infinito, y stop() detiene la ejecución del sonido.
Vamos a ver esto con el ejemplo de un applet que permite reproducir un sonido, pararlo y ejecutarlo
en un bucle, primero se muestra el código (Código fuente 190) y a continuación lo comentamos.
import java.awt.*;
import java.applet.*;
import java.awt.event.*;
import java.net.*;
public class Sonidos extends Applet implements ActionListener{
private AudioClip sonido;
private Button ejecutar,bucle,parar;
public void init(){
setLayout(new GridLayout(3,1,10,10));
ejecutar=new Button("Play");
bucle=new Button("Loop");
parar=new Button("Stop");
add(ejecutar);
add(bucle);
add(parar);
ejecutar.addActionListener(this);
bucle.addActionListener(this);
parar.addActionListener(this);
//obtenemos el camino hasta el lugar dónde se encuentra el applet
URL codeBase=getCodeBase();
//cargamos el fichero de sonido
sonido=getAudioClip(codeBase,getParameter("sonido"));
}
public void actionPerformed(ActionEvent evento){
AppletContext contexto=getAppletContext();
if (evento.getSource()==ejecutar){
contexto.showStatus("Reproducir sonido");
sonido.play();
}
else if (evento.getSource()==parar){
contexto.showStatus("Detener sonido");
sonido.stop();
}
else{
contexto.showStatus("Reproducir sonido en un bucle");
sonido.loop();
}
}
}
Código fuente 190
302
© Grupo EIDOS
15. Applets de Java: utilizando los applets
Como se puede comprobar a la vista del código, en este caso tenemos tres fuentes de eventos y un
mismo oyente, por lo que en el método actionPerformed() se debe identificar la fuente del evento, para
ello utilizamos el método getSource() de la clase java.util.EventObject, de esta clase heredan todas las
clase que representan a los eventos. Este método nos devuelve la referencia al objeto que ha producido
el evento, por lo tanto debemos comparar el valor devuelto con cada uno de los objetos que son fuente
de eventos, es decir, con cada uno de los objetos Button.
En el método init() del applet además de crear el interfaz de usuario (Figura 137) y registrar los
oyentes correspondientes, cargamos el fichero de sonido. Pero antes necesitamos obtener la ruta hasta
nuestro applet, esto se consigue mediante el método getCodeBase() de la clase Applet que nos
devuelve un objeto de la clase URL que contiene la dirección hasta nuestro applet. A continuación
utilizamos el método getAudioClip() de la clase Applet, el cual permite cargar en el objeto AudioClip
correspondiente el fichero de sonido que le especificamos por parámetro.
Como se habrá observado, el nombre del fichero de sonido lo pasamos mediante un parámetro del
applet (etiqueta <PARAM>) y lo recuperamos con el método getParameter() de la clase Applet.
Figura 137
Eventos, gráficos y fuentes en los applets
En este apartado vamos a tratar las clases Graphics, FontMetrics y Font, es decir, vamos a tratar la
parte gráfica de los applets, algo que habíamos comentado de forma muy breve al utilizar el método
paint().
También se va a comentar algunos aspectos del tratamiento de eventos dentro de los applets y se
mostrarán algunos tipos de eventos que no habíamos visto hasta ahora. Todo ello se irá mostrando y
comentando a través de numerosos ejemplos de applets.
Pasemos a la parte gráfica de nuestro apartado.
El objeto de la clase Graphics que recibe como parámetro el método paint() de la clase Applet
representa a la superficie del applet y todo lo que se dibuje o escriba en el se mostrará en el applet.
Aunque el método paint() lo estamos utilizando en la clase Applet, se debe señalar que este método se
hereda de la clase Container, y por lo tanto lo presentan todos los objetos que realicen funciones de
contenedor.
La superficie del applet en el navegador Web Internet Explorer 5 se representa mediante el color gris
por defecto, pero en el navegador Netscape Navigator 4.5 se representa mediante el color blanco por
defecto.
El método de la clase Graphics que hemos estado utilizando en diferentes momentos en este capítulo
ha sido drawString(). Este método mostrará la cadena de caracteres que le pasemos por parámetro y en
la posición que le indiquemos. El origen de coordenadas en la superficie del applet se encuentra en la
parte superior izquierda.
303
Programación en Java
© Grupo EIDOS
Vamos a realizar un applet que vaya mostrando las coordenadas sobre las que se encuentra en ese
momento el cursor del ratón dentro del applet, además estas coordenadas se muestran justamente al
lado del cursor, es decir, en la propia coordenada en la que se encuentra.
Nuestro applet va a tener una clase interna que va a ser una clase adaptadora que hereda de la clase
MouseMotionAdapter, es decir, va a ser la clase oyente de los movimientos del ratón en nuestro
applet. Este ejemplo es interesante también porque vemos un evento que no habíamos tratado hasta
ahora, el evento de los movimientos del ratón.
El método que vamos a utilizar y sobrescribir de la clase MouseMotionAdapter es mouseMoved() .
Como su propio nombre indica este método se ejecutará cuando se mueva el ratón. En este método
recogemos los valores actuales de las coordenadas y se los asignamos a los atributos coordX y coordY
de nuestro applet, estos atributos han sido declarados para representar a las coordenadas x e y del ratón
dentro de nuestro applet. En la última línea del método mouseMoved() se llama al método repaint()
para que se actualice y repinte la superficie de nuestro applet.
Los valores de las coordenadas se obtienen utilizando los métodos getX() y getY() de la clase
MouseEvent.
En el método paint() escribiremos el valor de las coordenadas en la coordenada exacta, pero antes de
seguir comentando más acerca de este applet de ejemplo veamos su código, en el Código fuente 191.
import java.awt.*;
import java.applet.*;
import java.awt.event.*;
public class Coordenadas extends Applet{
int coordX, coordY;
public void init(){
//se inicializan las coordenadas
coordX=-1;
coordY=-1;
//registramos el oyente de los movimientos del ratón
addMouseMotionListener(new AdaptadorMovimientosRaton());
}
public void paint(Graphics g){
//se comprueba que las coordenadas son correctas
if (coordX!=-1)
//se escribe la coordenada en la posición en la
//que está el ratón
g.drawString("("+coordX+","+coordY+")",coordX,coordY);
}
//clase adaptadora interna que trata los movimientos del ratón
class AdaptadorMovimientosRaton extends MouseMotionAdapter{
public void mouseMoved(MouseEvent evento){
coordX=evento.getX();
coordY=evento.getY();
//se vuelve a pintar el applet
repaint();
}
}
}
Código fuente 191
En el método paint() mostramos las coordenadas en las que se encuentra el cursor del ratón mediante
el ya conocido método drawString() de la clase Graphics.
304
© Grupo EIDOS
15. Applets de Java: utilizando los applets
Pero si probamos este applet comprobamos que no funciona correctamente, ya que cuando salimos de
la superficie del applet las coordenadas se quedan pintadas en la superficie del applet. Para evitar este
efecto debemos modificar el código del applet para tener en cuenta también los eventos de la entrada y
salida del ratón en nuestro applet.
Para ello hemos creado una clase interna que herede a la clase adaptadora MouseAdapter y que
implemente los métodos mouseExited() y mouseEntered(). Al applet Coordenadas se le añade un
nuevo atributo de tipo booleano llamado fuera, para saber en que situación estamos. El nuevo código
del applet se muestra en el Código fuente 192.
import java.awt.*;
import java.applet.*;
import java.awt.event.*;
public class Coordenadas2 extends Applet{
int coordX, coordY;
//indica si el ratón está fuera del applet
boolean fuera;
public void init(){
coordX=-1;
coordY=-1;
fuera=false;
addMouseMotionListener(new AdaptadorMovimientosRaton());
addMouseListener(new AdaptadorRaton());
}
public void paint(Graphics g){
//se comprueba si el ratón se encuentra en la
//superficie del applet
if(!fuera){
if(coordX!=-1)
g.drawString("("+coordX+","+coordY+")",coordX,coordY);
showStatus("");
}
else
showStatus("El ratón ha salido");
}
class AdaptadorMovimientosRaton extends MouseMotionAdapter{
public void mouseMoved(MouseEvent evento){
coordX=evento.getX();
coordY=evento.getY();
repaint();
}
}
class AdaptadorRaton extends MouseAdapter{
public void mouseExited(MouseEvent evento){
//el ratón ha salido
fuera=true;
repaint();
}
public void mouseEntered(MouseEvent evento){
//el ratón ha entrado
fuera=false;
repaint();
}
}
}
Código fuente 192
305
Programación en Java
© Grupo EIDOS
En este código se muestra un mensaje en la línea de estado del navegador, pero no se utiliza el interfaz
AppletContext, sino que directamente se utiliza el método showStatus() de la clase Applet, el resultado
es el mismo que veíamos en el apartado anterior.
Con esta nueva versión del applet ya no se produce el efecto comentado anteriormente, cuando desde
el método mouseExited() se invoca al método paint() del applet mediante repaint(), el atributo fuera
tiene el valor true, por lo tanto en el método paint() no escribe nada y se eliminan las coordenadas de
la superficie del applet.
En este punto es necesario realizar una aclaración. Cuando modificamos el código fuente de un applet
que ya hemos ejecutado en el navegador, si lo volvemos a probar, en muchos casos, los cambios
realizados no se reflejan, esto es debido a que el applet se encuentra en la memoria caché del
navegador y se está utilizando la versión anterior de nuestro fichero de clase. En este caso la solución
es cerrar el navegador y volver a abrirlo para probar el nuevo applet.
En la Figura 138 se puede observar un ejemplo de ejecución de este applet.
Figura 138
Si cambiamos todas las sentencias repaint() por la siguiente paint(getGraphics()), podemos pensar que
el efecto es el mismo, es decir, la llamada al método paint(). Si realizamos este cambio y ejecutamos el
applet de nuevo vemos un efecto distinto, las coordenadas que escribimos nunca se borran, como se
puede observar en la Figura 139.
El método getGraphics() de la clase Component nos devuelve el objeto Graphics que se corresponde
con la superficie del componente gráfico correspondiente, en este caso la superficie del applet.
Figura 139
306
© Grupo EIDOS
15. Applets de Java: utilizando los applets
Este efecto se produce porque no hemos definido correctamente lo que significa llamar al método
repaint(), habíamos dicho que el método repaint() llamaba al método paint() del applet, pero esto no es
correcto, ya que la llamada no es directa, sino que el método repaint() llama antes al método update()
de la clase Component y después de llamar al método update() llama al método paint(). La función que
realiza el método update() antes de llamar al método paint() es la de limpiar o borrar toda la superficie
que se encuentre representada por la clase Graphics.
Por lo tanto si antes de todas las sentencias paint(getGraphics()) añadimos la sentencia
update(getGraphics()) el efecto será el idéntico al que se producía al lanzar el método repaint().
Vamos a seguir comentando la utilización del objeto Graphics en los applets en conjunción con el
tratamiento de eventos dentro de los mismos. En este caso se va a mostrar un ejemplo de un applet que
consiste en escribir en la superficie del mismo la cadena "¡click!" en las coordenadas en las que se
haya producido la pulsación del ratón.
Para ello vamos a utilizar una clase adaptadora que herede de la clase MouseAdapter, y el método que
nos va a interesar va a ser el método mousePressed(). Este método se ejecutará cuando se pulse el
botón del ratón. El código de este applet es el que se muestra en el Código fuente 193.
import java.awt.*;
import java.applet.*;
import java.awt.event.*;
public class AppletClick extends Applet{
private int coordX,coordY;
public void init(){
coordX=-1;
coordY=-1;
addMouseListener(new AdaptadorRaton(this));
}
public void paint(Graphics g){
if (coordX!=-1)
g.drawString("¡Click!",coordX,coordY);
}
//métodos de acceso
public void modificaX(int x){
coordX=x;
}
public void modificaY(int y){
coordY=y;
}
}
class AdaptadorRaton extends MouseAdapter{
private AppletClick fuente;
public AdaptadorRaton(AppletClick fuente){
this.fuente=fuente;
}
//detectamos la pulsación en el ratón
public void mousePressed(MouseEvent evento){
fuente.modificaX(evento.getX());
fuente.modificaY(evento.getY());
fuente.repaint();
}
}
Código fuente 193
307
Programación en Java
© Grupo EIDOS
Como se puede comprobar, al no utilizar una clase interna debemos ofrecer un par de métodos de
acceso para que la clase AdaptadorRaton pueda modificar los atributos coordX y coordY de la clase
AppletClick, van a ser estos dos atributos los que representen la coordenada en la que se debe escribir
la cadena. Además debemos llevar la referencia de la clase fuente del evento, para ello la clase
AdaptadorRaton tiene como atributo un objeto de la clase AppletClick.
En este caso no es necesario controlar que el ratón salga de la superficie de nuestro applet, ya que el
evento de la pulsación del ratón siempre se va a producir en el interior del mismo.
El aspecto de este applet en ejecución es el de la Figura 140.
Figura 140
Podemos realizar una modificación sobre el código anterior para que la cadena "¡Click!" sólo aparezca
en el momento de la pulsación del botón y no permanezca pintada en al superficie del applet. Para ello
debemos implementar el método mouseReleased(). El método mouseReleased() se ejecutará cuando se
suelte el botón del ratón, es decir, un click de ratón ejecutará primero el método mousePressed() y a
continuación el método mouseReleased().
En el método mouseReleased() asignamos el valor –1 a los atributos de la clase AppletClick que
representan las coordenadas del ratón y a continuación se llama al método repaint(). El código del
método mouseReleased() se muestra en el Código fuente 194.
public void mouseReleased(MouseEvent evento){
fuente.modificaX(-1);
fuente.modificaY(-1);
fuente.repaint();
}
Código fuente 194
Creo que ya hemos visto bastante de momento sobre el método drawString() de la clase Graphics, el
método paint() y el tratamiento de eventos sobre la superficie del applet, por lo tanto a continuación
nos vamos a centrar más en los distintos métodos de la clase Graphics. De esta clase comentaremos
desde los métodos que nos permiten modificar el aspecto del applet a los que nos permiten dibujar
distintas figuras e insertar ficheros de imágenes. También trataremos los tipos de fuente dentro de los
applets.
308
© Grupo EIDOS
15. Applets de Java: utilizando los applets
En todos los ejemplos anteriores cuando escribimos una cadena en la superficie del applet ésta parece
en negro, si queremos que aparezca en un color determinado el texto, utilizaremos el método
setColor() de la clase Graphics, de esta forma todo lo que se pinte en el objeto Graphics del applet
aparecerá con el color especificado como parámetro en el método setColor().
El método setColor() recibe como parámetro un objeto de la clase Color que va a representar el color
que se quiere asignar al objeto Graphics correspondiente.
La clase Color se encuentra dentro del paquete java.awt y representa como su nombre indica los
colores. Esta clase ofrece una serie de constantes para representar colores estándar: rojo, azul,
amarillo, etc. Así para asignar el color azul a un objeto Graphics escribiremos lo que indica el Código
fuente 195.
g.setColor(Color.blue);
Código fuente 195
Pero si queremos asignar un color personalizado por nosotros mismos, deberemos crear un objeto de la
clase Color y pasárselo por parámetro al método setColor(). Para indicar un color personalizado, el
constructor de la clase Color acepta como parámetros tres enteros en los rangos 0-255 siguiendo por lo
tanto la definición de colores según la norma RGB. Cada uno de los enteros indica la intensidad de
color rojo, verde y azul, respectivamente. De esta forma, si queremos asignar un color personalizado
tenemos que escribir una sentencia similar a la que muestra el Código fuente 196.
g.setColor(new Color(200,4,100));
Código fuente 196
Para asignar un color al fondo del applet utilizamos el método setBackGround(), pero este método no
lo ofrece la clase Graphics, sino que se encuentra en la clase Applet. Al método setBackGround() le
pasamos por parámetro un objeto de la clase Color, al igual que hacíamos con el método anterior.
setBackground(new Color(12,134,100));
Código fuente 197
Hemos modificado el color de fondo del applet y el color de los elementos gráficos que van a aparecer
en el mismo, ahora vamos a modificar el tipo de letra.
Para modificar el tipo de letra utilizaremos el método setFont() de la clase Graphics. Este método
recibe como parámetro un objeto de la clase Font que se corresponde con la letra que se desea utilizar.
La clase Font se encuentra en el paquete java.awt y tiene un constructor que nos permite especificar el
tipo o nombre de la letra, el aspecto que va a tener (normal, cursiva o negrita) y el tamaño de la
misma. Así si queremos asignar a nuestro applet una letra de tipo Courier en negrita y de tamaño 20
píxeles escribiremos el Código fuente 198.
309
Programación en Java
© Grupo EIDOS
g.setFont(new Font("Courier",Font.BOLD,20));
Código fuente 198
Para definir los diferentes aspectos de las fuentes la clase Font ofrece una serie de constantes,
Font.BOLD para la fuente en negrita, Font.ITALIC para la letra cursiva y Font.PLAIN para que
muestre el aspecto normal.
Para mostrar un ejemplo sencillo de todo lo visto hasta el momento vamos a realizar el famoso applet
"Hola Mundo", pero modificando los colores y el tipo de letra. El código de este applet es el Código
fuente 199.
import java.awt.*;
import java.applet.*;
public class HolaMundo extends Applet{
public void init(){
setBackground(Color.pink);
}
public void paint(Graphics g){
g.setColor(new Color(200,4,100));
g.setFont(new Font("TimesRoman",Font.ITALIC,20));
g.drawString("Hola Mundo",20,20);
}
}
Código fuente 199
Y el aspecto de esta nueva versión del applet "Hola Mundo" se puede comprobar en la Figura 141.
Figura 141
La clase Font ofrece una serie de métodos que nos permiten consultar las características de la fuente
actual. Estos métodos son:
310
•
getName(): devuelve una cadena que indica el nombre de la fuente.
•
getSize(): devuelve el tamaño de la fuente actual como un entero.
© Grupo EIDOS
15. Applets de Java: utilizando los applets
•
getStyle(): devuelve el aspecto de la fuente actual mediante un entero que se corresponde con
los valores de las constantes de la clase Font. Font.PLAIN fuente normal tiene el valor 0,
Font.BOLD fuente negrita tiene el valor 1, Font.ITALIC tiene el valor 2 y la combinación de
negrita y cursiva devolverá el valor 3.
•
isPlain(): devuelve verdadero si el aspecto de la fuente es normal.
•
isBold(): devuelve verdadero si el aspecto de la fuente es negrita.
•
isItalic(): devuelve verdadero si el aspecto de la fuente es cursiva.
Para obtener información acerca de las fuentes en el paquete java.awt encontramos también la clase
FontMetrics. Esta clase ofrece una serie de métodos que permiten consultar una serie características de
las fuentes que tiene asignadas actualmente la clase Graphics y que tiene que ver con las métricas de la
fuente.
Para obtener una instancia de un objeto de la clase FontMetrics lanzaremos el método
getFontMetrics() de la clase Graphics sobre el objeto Graphics del que se quiere consultar la
información de su letra.
Algunos de los métodos de la clase FontMetrics son getHeight() y getCharWidth(), que nos indican la
altura de la letra y la anchura de un carácter determinado, respectivamente, en pixeles. Como se puede
deben ver estos métodos ofrecen una información más detallada y específica de las métricas de la
fuente que los métodos de la clase Font. Otros métodos de esta clase son:
•
stringWidth(): se le pasa una cadena por parámetro y devuelve la anchura de la cadena en
pixeles.
•
getAscent(): devuelve la distancia entre la línea base de la fuente y la parte superior de los
caracteres.
•
getDescent(): devuelve la distancia entre la línea base de la fuente y la parte inferior de los
caracteres, esto se aplica a caracteres como p y q que caen más bajo de la línea base.
•
getLeading(): devuelve el interlineado de la fuente.
El siguiente ejemplo es un applet que permite configurar el mensaje "Hola Mundo", ofrece un interfaz
de usuario para realizar esta tarea, además mediante este interfaz de usuario vamos a comentar el
tratamiento de nuevos eventos, como son el de selección de una lista desplegable, la entrada de texto
en un caja de texto y el de la selección de una casilla de verificación. Para la construcción del interfaz
se ha utilizado el gestor de diseño GridLayout, BorderLayout y el anidamiento de objetos Panel.
Se podría considerar que este applet es un ejemplo que además de mostrar lo que hemos explicado
sobre el tratamiento de fuentes y color, también ofrece un resumen de muchos de los puntos vistos en
anteriores apartados y capítulos, por lo que puede resultar bastante interesante.
Este applet va a ser algo complicado, por lo tanto vamos a cambiar un poco la filosofía que veníamos
utilizando hasta ahora en la explicación de los ejemplos, en lugar de mostrar todo el código y luego
comentarlo, vamos a desglosarlo y comentarlo más minuciosamente, aunque para una mayor claridad
al final de la exposición mostraremos el código completo.
El primer punto que vamos a tratar va a ser la construcción del interfaz de usuario. Para que sea más
claro primero vamos a mostrar el aspecto del applet, a continuación comentamos un poco su estructura
y a continuación se procederá a facilitar el código fuente. El interfaz de usuario que presenta el applet
311
Programación en Java
© Grupo EIDOS
va a ser el de la Figura 142, que como se puede ver está formado por un objeto TextField, varios
objetos Label, dos objetos Checkbox y dos objetos Choice.
Figura 142
En realidad todos los componentes AWT utilizados se encuentran incluidos dentro de un objeto Panel,
y es este objeto Panel, conteniendo todos los componentes, el que se añade al applet. Se han utilizado
diversos paneles auxiliares, con diferentes gestores de diseño, para ir colocando los componentes del
interfaz de usuario.
El panel principal, denominado en el código panelInterfaz, presenta un gestor de diseño de la clase
GridLayout de 2 filas y una columna. En la primera fila se añade un panel representado por el objeto
panel1, que contendrá los controles que indican el tamaño y aspecto de la fuente, y en la segunda fila
un panel representado por el objeto panel3. A su vez estos dos paneles incluyen en su interior otros
gestores de diseño y otros paneles.
En la Figura 143 se puede ver un esquema en el que se identifican cada uno de los paneles y como se
encuentran divididos.
Figura 143
El código que permite construir este interfaz de usuario lo tenemos en el método creaInterfaz(). Todos
los componentes AWT utilizados para crear el interfaz han sido declarados como atributos de nuestro
applet.
public void creaInterfaz(){
//panel que contiene todo el interfaz
panelInterfaz=new Panel();
panelInterfaz.setLayout(new GridLayout(2,1));
//panel que contiene los controles de tamaño y aspecto de la fuente
312
© Grupo EIDOS
15. Applets de Java: utilizando los applets
panel1=new Panel();
panel1.setLayout(new GridLayout(1,3,5,1));
//panel que contiene la caja de texto con el tamaño de la fuente
panel2=new Panel();
panel2.setLayout(new BorderLayout());
tamaño=new TextField(10);
panel2.add("North",new Label("Tamaño"));
panel2.add("South",tamaño);
panel1.add(panel2);
negrita=new Checkbox("Negrita");
panel1.add(negrita);
cursiva=new Checkbox("Cursiva");
panel1.add(cursiva);
panelInterfaz.add(panel1);
//panel que contiene las dos listas desplegables
panel3=new Panel();
panel3.setLayout(new GridLayout(1,2,5,1));
//panel que contiene la lista con los tipos de letra
panel4=new Panel();
panel4.setLayout(new GridLayout(2,1));
panel4.add(new Label("Tipo Letra:"));
tipoLetra=new Choice();
tipoLetra.addItem("Courier");
tipoLetra.addItem("Arial");
tipoLetra.addItem("TimesRoman");
panel4.add(tipoLetra);
panel3.add(panel4);
//panel que contiene la lista con los colores
panel5=new Panel();
panel5.setLayout(new GridLayout(2,1));
panel5.add(new Label("Color Letra:"));
colorLetra=new Choice();
colorLetra.addItem("rojo");
colorLetra.addItem("azul");
colorLetra.addItem("verde");
panel5.add(colorLetra);
panel3.add(panel5);
panelInterfaz.add(panel3);
add(panelInterfaz);
}
Código fuente 200
A la vista del código fuente del método creaInterfaz() y el esquema de la Figura 143, damos por
concluida la primera parte de la explicación.
Al añadir el panel panelInterfaz al applet, el resto de la superficie del applet queda reservada para
contener la cadena de texto a la que se va a ir modificando su aspecto según lo indiquemos en los
controles del interfaz de usuario.
Ahora debemos centrarnos en el tratamiento de eventos. En nuestro applet tenemos cinco fuentes de
eventos, la caja de texto, las dos casillas de verificación y las dos listas desplegables, pero vamos a
tener un único oyente, nuestro applet.
La caja de texto va a lanzar un evento del tipo TextEvent cada vez que se modifique, ya que queremos
que se cambie el tamaño de las letras mientras que lo indicamos en la caja de texto. Este evento puede
ser atrapado y tratado por un oyente que implemente el interfaz TextListener. El interfaz TextListener
tiene un único método, llamado textValueChanged() y que se ejecutará cuando se modifique el
contenido de la caja de texto. Por lo tanto el applet deberá implementar el interfaz TextListener.
313
Programación en Java
© Grupo EIDOS
Cuando cambiamos el valor de un objeto Checkbox o un objeto Choice se lanza un evento de la clase
ItemEvent. Este evento será tratado por un oyente que implemente el interfaz ItemListener. El interfaz
ItemListener, al igual que el anterior, tiene un único método llamado itemStateChanged() que se
ejecutará cuando se seleccione un elemento de la lista o se modifique el valor de la casilla de
verificación. Por lo tanto nuestro applet además de implementar el interfaz TextListener debe
implementar el interfaz ItemListener.
La declaración de nuestra subclase de clase Applet quedaría como indica el Código fuente 201.
public class Fuentes extends Applet implements ItemListener, TextListener{
}
Código fuente 201
En el método itemStateChanged() deberemos distinguir en que situación estamos, ya que este método
se puede ejecutar desde cuatro fuentes diferentes, las dos casillas de verificación o las dos listas
desplegables, realizando en cada caso un proceso distinto, cada uno de estos procesos los
comentaremos más adelante.
El tratamiento de cualquiera de las cinco situaciones distintas que se puedan dar en el lanzamiento de
un evento, es decir, modificación del contenido de la caja de texto, modificación de cualquiera de las
dos casillas de verificación y selección de un elemento de cualquiera de las dos listas, ocasionarán una
llamada al método repaint(), y consecuentemente una llamada al método paint(), que es precisamente
el método que pasamos a comentar a continuación.
En el método paint() lo que vamos a hacer va a ser asignar al objeto Graphics de nuestro applet los
valores que se hayan indicado en el interfaz de usuario y que tienen que ver con el color de la letra,
tamaño, tipo y aspecto. Por lo tanto es necesario definir en nuestro applet cuatro atributos que
representen cada uno de estos valores. Y en los métodos para el tratamiento de eventos, es decir en los
métodos textValueChanged() y itemStateChanged(), lo que deberemos hacer antes de llamar al
método repaint() es actualizar el valor del atributo correspondiente.
El método paint() simplemente emplea los valores de los atributos del applet para dibujar en pantalla
el mensaje Hola Mundo. El código de este método es el Código fuente 202.
public void paint(Graphics g){
g.setFont(new Font(tipo,aspecto,tam));
g.setColor(color);
g.drawString("Hola Mundo",10,180);
}
Código fuente 202
Aquí se pueden apreciar los atributos que se han definido para nuestra clase Fuentes. El atributo tipo
de la clase String contendrá el valor que se ha seleccionado en la lista desplegable que contiene los
tipos de letra disponibles, el atributo aspecto del tipo primitivo int contiene el valor de la combinación
de las casillas de verificación que indican si la letra va ser en negrita y/o en cursiva.
El atributo tam del tipo primitivo int contiene el valor de la caja de texto que indica el tamaño de la
fuente, y por último el atributo color que es un objeto de la clase Color, va a contener el color indicado
en la lista desplegable correspondiente. Si bien el resto de los valores de los atributos se asigna de
314
© Grupo EIDOS
15. Applets de Java: utilizando los applets
forma más o menos directa, en el caso del atributo color debemos emplear un método auxiliar llamado
devuelveColor() que permite realizar la traducción del color especificado en la lista de colores con los
disponibles en la clase Color como constantes de la misma. El código de este método es el Código
fuente 203.
public Color devuelveColor(String col){
if (col.equals("rojo"))
return Color.red;
else if (col.equals("azul"))
return Color.blue;
else return Color.green;
}
Código fuente 203
En el método init() del applet se deben inicializar los todos estos atributos, además de esta
inicialización se lanza el método creaInterfaz() y se registra el oyente para las cinco fuentes de eventos
ya comentadas. El código del método init() es el Código fuente 204.
public void init(){
creaInterfaz();
tam=20;
tipo="Arial";
color=Color.green;
aspecto=Font.PLAIN;
tipoLetra.addItemListener(this);
colorLetra.addItemListener(this);
tamaño.addTextListener(this);
negrita.addItemListener(this);
cursiva.addItemListener(this);
}
Código fuente 204
Ya sólo nos queda entrar en detalle en los métodos que tratan los eventos. El método
textValueChanged() recupera el valor de la caja de texto correspondiente, lo transforma al tipo
primitivo int y se lo asigna al atributo tam de la clase Fuentes. La conversión de cadena de caracteres a
tipo int se realiza mediante el método estático parseInt() de la clase Integer, si se produce algún error
en la transformación se lanza la excepción NumberFormatException, si se produce esta excepción no
se modifica el valor del atributo tam.
public void textValueChanged(TextEvent evento){
int t;
try{
t=Integer.parseInt(tamaño.getText());
tam=t;
repaint();
}
catch (NumberFormatException ex){}
}
Código fuente 205
315
Programación en Java
© Grupo EIDOS
El método itemStateChanged() debe utilizar el método getSource() sobre el objeto evento de la clase
ItemEvent que recibe como parámetro, para averiguar la fuente del evento, ya que, como ya hemos
comentado, hay cuatro fuentes posibles.
Si el origen del evento es cualquiera de los dos objetos Choice se recupera el valor de la lista
desplegable mediante el método getSelectedItem() de la clase Choice, y se le asigna al atributo
correspondiente. Y si el origen del evento es uno de los objetos Checkbox se modifica el atributo
aspecto sumándole o restándole, según el estado del objeto Checkbox, el valor que se corresponde con
la casilla verificada, es decir, Font.BOLD o Font.ITALIC. El código de este método se puede observar
en el Código fuente 206.
public void itemStateChanged(ItemEvent evento){
if(evento.getSource()==tipoLetra){
tipo=tipoLetra.getSelectedItem();
}
else if(evento.getSource()==colorLetra){
color=devuelveColor(colorLetra.getSelectedItem());
}
else if(evento.getSource()==negrita){
if (negrita.getState())
aspecto=aspecto+Font.BOLD;
else
aspecto=aspecto-Font.BOLD;
}
else if (evento.getSource()==cursiva){
if (cursiva.getState())
aspecto=aspecto+Font.ITALIC;
else
aspecto=aspecto-Font.ITALIC;
}
repaint();
}
Código fuente 206
Una vez comentado cada fragmento del código del applet Fuentes, en el Código fuente 207, se muestra
el código completo del mismo para tener una visión más general.
import
import
import
public
java.awt.*;
java.applet.*;
java.awt.event.*;
class Fuentes extends Applet implements ItemListener, TextListener{
private Choice tipoLetra, colorLetra;
private Checkbox negrita, cursiva;
private TextField tamaño;
private Panel panelInterfaz, panel1, panel2,panel3,panel4,panel5;
private String tipo;
private int tam;
private Color color;
private int aspecto;
public void init(){
creaInterfaz();
tam=20;
tipo="Arial";
color=Color.green;
aspecto=Font.PLAIN;
316
© Grupo EIDOS
15. Applets de Java: utilizando los applets
tipoLetra.addItemListener(this);
colorLetra.addItemListener(this);
tamaño.addTextListener(this);
negrita.addItemListener(this);
cursiva.addItemListener(this);
}
public void itemStateChanged(ItemEvent evento){
if(evento.getSource()==tipoLetra){
tipo=tipoLetra.getSelectedItem();
}
else if(evento.getSource()==colorLetra){
color=devuelveColor(colorLetra.getSelectedItem());
}
else if(evento.getSource()==negrita){
if (negrita.getState())
aspecto=aspecto+Font.BOLD;
else
aspecto=aspecto-Font.BOLD;
}
else if (evento.getSource()==cursiva){
if (cursiva.getState())
aspecto=aspecto+Font.ITALIC;
else
aspecto=aspecto-Font.ITALIC;
}
repaint();
}
public void textValueChanged(TextEvent evento){
int t;
try{
t=Integer.parseInt(tamaño.getText());
tam=t;
repaint();
}
catch (NumberFormatException ex){}
}
public Color devuelveColor(String col){
if (col.equals("rojo"))
return Color.red;
else if (col.equals("azul"))
return Color.blue;
else return Color.green;
}
public void paint(Graphics g){
g.setFont(new Font(tipo,aspecto,tam));
g.setColor(color);
g.drawString("Hola Mundo",10,180);
}
public void creaInterfaz(){
//panel que contiene todo el interfaz
panelInterfaz=new Panel();
panelInterfaz.setLayout(new GridLayout(2,1));
//panel que contiene los controles de tamaño y aspecto de la fuente
panel1=new Panel();
panel1.setLayout(new GridLayout(1,3,5,1));
//panel que contiene la caja de texto con el tamaño de la fuente
panel2=new Panel();
panel2.setLayout(new BorderLayout());
tamaño=new TextField(10);
panel2.add("North",new Label("Tamaño"));
panel2.add("South",tamaño);
panel1.add(panel2);
negrita=new Checkbox("Negrita");
panel1.add(negrita);
cursiva=new Checkbox("Cursiva");
panel1.add(cursiva);
panelInterfaz.add(panel1);
317
Programación en Java
© Grupo EIDOS
//panel que contiene las dos listas desplegables
panel3=new Panel();
panel3.setLayout(new GridLayout(1,2,5,1));
//panel que contiene la lista con los tipos de letra
panel4=new Panel();
panel4.setLayout(new GridLayout(2,1));
panel4.add(new Label("Tipo Letra:"));
tipoLetra=new Choice();
tipoLetra.addItem("Courier");
tipoLetra.addItem("Arial");
tipoLetra.addItem("TimesRoman");
panel4.add(tipoLetra);
panel3.add(panel4);
//panel que contiene la lista con los colores
panel5=new Panel();
panel5.setLayout(new GridLayout(2,1));
panel5.add(new Label("Color Letra:"));
colorLetra=new Choice();
colorLetra.addItem("rojo");
colorLetra.addItem("azul");
colorLetra.addItem("verde");
panel5.add(colorLetra);
panel3.add(panel5);
panelInterfaz.add(panel3);
add(panelInterfaz);
}
}
Código fuente 207
En el siguiente enlace se puede encontrar el fichero de código fuente del applet.
En la Figura 144 se ofrece un ejemplo de la ejecución del applet. Y con esto se da por concluida la
explicación de este applet de ejemplo. Lo que veremos a continuación serán los diferentes métodos
que nos ofrece la clase Graphics para realizar figuras.
Figura 144
Hasta ahora sólo hemos visto un método que nos permita dibujar algo en la superficie del applet, el
método drawString(), pero la clase Graphics ofrece una serie de métodos que nos permiten dibujar
diferentes tipos de figuras como pueden ser circunferencias, rectángulos, todo tipo de polígonos, etc.
318
© Grupo EIDOS
15. Applets de Java: utilizando los applets
La clase Graphics tiene una serie de métodos del tipo drawXXX() donde XXX será el tipo de figura
que se desea dibujar. Algunos de estos métodos del tipo drawXXX() tienen su equivalente método
fillXXX(), estos métodos dibujan la misma figura pero la rellenan de color. Por ejemplo el método
drawRect(), que dibuja un rectángulo, tiene su equivalente fillRect(), que dibuja un rectángulo de
color, según el color que se haya especificado al objeto Graphics.
Todos estos métodos de dibujo reciben una serie de parámetros del tipo entero int que se corresponde
con las coordenadas y dimensiones de las figuras. El Código fuente 208 dibujaría un rectángulo de
anchura 125 y altura 180 en la posición (35,15) .
public void paint(Graphics g){
g.drawRect(35,15,125,180);
}
Código fuente 208
El siguiente applet de ejemplo permite elegir de una lista desplegable la figura que se desea dibujar en
la superficie del mismo. Al producirse la selección se dibuja en el applet la figura correspondiente.
El tratamiento de eventos en este applet es muy sencillo simplemente debemos implementar el interfaz
ItemListener, ya que es el encargado de atrapar los eventos que provienen de la selección de una lista.
En el método itemStateChanged() vamos a recoger el nombre de la figura seleccionada y se lo vamos
a asignar a un atributo de nuestra clase llamado seleccion y que va a ser un objeto de la clase String. El
atributo seleccion contiene el nombre de la figura que se desea dibujar.
El método itemStatedChanged() también realizará una llamada al método repaint() para que se ejecute
el método paint() del applet. En el método paint() dibujaremos la figura correspondiente atendiendo al
valor del atributo seleccion.
El código completo de este applet se puede ver en el Código fuente 209.
import java.awt.*;
import java.applet.*;
import java.awt.event.*;
public class Figuras extends Applet implements ItemListener{
private Choice figuras;
private String seleccion;
public void init(){
setLayout(new BorderLayout());
figuras=new Choice();
figuras.addItem("línea");
figuras.addItem("rectángulo");
figuras.addItem("cuadrado");
figuras.addItem("rectángulo redondeado");
figuras.addItem("rectángulo 3D");
figuras.addItem("arco");
figuras.addItem("elipse");
figuras.addItem("círculo");
figuras.addItem("polígono");
add("South",figuras);
seleccion="";
figuras.addItemListener(this);
}
public void itemStateChanged(ItemEvent evento){
seleccion=figuras.getSelectedItem();
319
Programación en Java
© Grupo EIDOS
repaint();
}
public void paint(Graphics g){
if (seleccion.equals("línea"))
// coordenadas origen, coordenadas fin
g.drawLine(35,15,90,200);
else if (seleccion.equals("rectángulo"))
//coordenadas,anchura,altura
g.drawRect(35,15,125,180);
else if (seleccion.equals("cuadrado"))
g.drawRect(35,15,125,125);
else if (seleccion.equals("rectángulo redondeado"))
//coordenadas,anchura,altura,arco de la esquina
g.drawRoundRect(35,15,125,180,20,20);
else if (seleccion.equals("rectángulo 3D"))
//coordenadas,anchura,altura,elevación
g.draw3DRect(35,15,125,185,true);
else if (seleccion.equals("arco"))
//coordenadas,anchura,altura,ángulo inicio, grados
g.drawArc(35,15,160,160,90,180);
else if (seleccion.equals("elipse"))
//coordenadas,anchura, altura
g.drawOval(35,15,160,100);
else if (seleccion.equals("círculo"))
g.drawOval(35,15,100,100);
else if (seleccion.equals("polígono")){
//coordenadas x, coordenadas y, número de puntos
int coordX[]={39,94,97,142,53,58,26};
int coordY[]={33,74,36,70,108,80,106};
int puntos=coordX.length;
g.drawPolygon(coordX,coordY,puntos);
}
}
}
Código fuente 209
En este enlace se puede obtener el código del ejemplo.
En este ejemplo se han utilizado los diferentes métodos drawXXX() de la clase Graphics, cada uno de
ellos se acompaña de un comentario en el que se indica el significado de los parámetros. Un ejemplo
de la ejecución del applet anterior se puede observar en la Figura 145.
Figura 145
320
© Grupo EIDOS
15. Applets de Java: utilizando los applets
Es posible copiar un área rectangular de un objeto Graphics a una posición diferente, para ello se
utiliza el método copyArea() de la clase Graphics. Este método recibe los siguientes parámetros:
coordenadas de origen, anchura y altura del área a copiar, coordenadas de destino. Así por ejemplo, si
deseamos copiar un área cuadrada de 100 pixeles a 100 pixeles a su derecha escribiremos lo que
muestra el Código fuente 210.
g.copyArea(0,0,100,100,100,0);
Código fuente 210
Para borrar un área rectangular de un objeto Graphics se utiliza el método clearRect() de la misma
clase. Este método recibe como parámetro las coordenadas y la anchura y altura del área a borrar, es
decir, tiene los mismos parámetros que el método drawRect().
Para terminar este apartado comentaremos el método drawImage() de la clase Graphics. Este método
permite mostrar en la superficie del applet el contenido de un fichero de imagen. Como parámetros
recibe un objeto de la clase Image, las coordenadas en las que va a mostrarse la imagen y una
referencia a la clase que va a contener a la imagen y la va a mostrar.
La clase Image se encuentra en el paquete java.awt y representa una imagen. Para crear un objeto
Image se utilizará el método getImage() de la clase Applet. Este método devuelve el objeto Image que
se corresponde con el nombre del fichero de imagen que se le pasa por parámetro. Para obtener el
nombre completo del fichero de imagen se debe utilizar el método ya conocido getCodeBase() de la
clase Applet.
El Código fuente 211 se corresponde con un applet que muestra una imagen que se encuentra
enmarcada mediante un rectángulo con las esquinas redondeadas.
import java.applet.*;
import java.awt.*;
import java.net.URL;
public class Imagen extends Applet{
Image imagen;
public void init(){
URL codeBase=getCodeBase();
//creamos el objeto imagen
imagen=getImage(codeBase,"arma.gif");
}
public void paint(Graphics g){
//tamaño de la imagen
int anchura=imagen.getWidth(this);
int altura=imagen.getHeight(this);
//se dibuja el rectángulo
g.drawRoundRect(52,52,anchura+10,altura+10,30,30);
//se dibuja la imagen
g.drawImage(imagen,57,57,anchura,altura,this);
}
}
Código fuente 211
321
Programación en Java
© Grupo EIDOS
Para obtener el tamaño de la imagen utilizamos los método getWidth() y getHeight() de la clase
Image. Estos dos métodos tienen un parámetro que será una referencia a la clase que muestra la
imagen, en este caso es el mismo applet, por lo que se utiliza this. El aspecto de este applet es el de la
Figura 146
Figura 146
Compresión de applets
Como ya hemos comentado los applets pueden estar formados por diferentes clases. Cuando cargamos
uno de estos applets en una página Web, se establece una conexión para cada una de la clases de los
applets con el servidor Web que contiene las mismas.
De esta forma si hay una página Web con muchos applets o con un applet muy complejo que para su
ejecución necesita de un gran número de clases, se incrementarán los tiempos de espera en la carga de
los mismos, ya que cada vez que se carga un fichero .CLASS es necesaria una nueva conexión a través
de la red, esto es, si el applet tiene veinte clases entonces serán necesarias veinte peticiones HTTP
(HyperText Transfer Protocol) que el navegador debe realizar.
Esto supone una gran pérdida de tiempo, ya que además de conectarse veinte veces al mismo servidor
Web, los ficheros .CLASS no se encuentran comprimidos con utilidades de compresión como PKZIP
o similares.
Para reducir los tiempos de descarga de los applets se ha creado un mecanismo para enviar los applets
a través de la Web dentro de un sólo fichero que contiene todos los ficheros de las clases necesarias y
todos los recursos que utilice el applet como pueden ser imágenes o sonidos.
De esta forma el navegador sólo necesita realizar una sola conexión con el servidor Web, ya que todos
los ficheros necesarios se encuentran en un único fichero. Además de introducir en un mismo archivo
todos los ficheros necesarios, se comprimen éstos de forma individual, así se reduce de forma
considerable el tiempo de carga.
Esta solución la implementan dos mecanismos diferentes, pero que esencialmente hacen lo mismo:
ficheros CAB (cabinet files) y ficheros JAR (Java archive). La gran diferencia entre ficheros CAB y
ficheros JAR es el formato de los ficheros y los algoritmos de compresión. Los ficheros CAB usan el
formato de los ficheros cabinet de Microsoft, que ha sido utilizado por los productos y paquetes de
instalación de Microsoft durante bastante tiempo.
322
© Grupo EIDOS
15. Applets de Java: utilizando los applets
En este apartado vamos a comentar ambos mecanismos y también vamos a comentar que herramientas
tenemos a nuestra disposición para comprimir ficheros de clase.
Para la creación de ficheros cabinet disponemos de la herramienta Microsoft SDK for Java 3.2, esta
herramienta la obtenemos del Web de Microsoft, ya que desgraciadamente Visual J++ no nos ofrece
ninguna herramienta para empaquetar ficheros de clase.
Los ficheros cabinet se basan en el sistema de compresión Lempei-Ziv, como parte fundamental de
este formato está la herramienta de compresión Diamond, que provee una compresión eficiente para
los datos de programas de instalación y aplicaciones para Internet. Con la herramienta Diamond se
pueden almacenar varios ficheros en un único fichero cabinet, y comprimir cada uno de ellos.
Diamond es utilizado por la herramienta llamada CABARC (CABinet ARChiver), y esta será la
herramienta que usaremos para la creación del fichero cabinet que, como se ha comentado, contendrá
todos los ficheros de las clases utilizadas por los applets.
La herramienta CABARC la encontramos dentro de Microsoft SDK for Java 3.2.
La herramienta CABARC tiene múltiples opciones, pero las más relevantes son: n para la creación de
un nuevo fichero cabinet y l para listar los contenidos del fichero cabinet. De esta forma para crear el
fichero cabinet llamando clases.cab con todos los ficheros .CLASS del directorio actual se debe
escribir lo siguiente en la línea de comandos:
cabarc n clases.cab *.class
Para asegurarnos que los ficheros que han sido añadidos al fichero clases.cab son los correctos, se
puede escribir lo que indica el Código fuente 212.
cabarc l clases.cab
Código fuente 212
Para poder utilizar el fichero clases.cab dentro de la página Web, se debe utilizar un parámetro de la
etiqueta <APPLET> llamado CABBASE.
<APPLET CODE="AppletInicio.class" WIDTH=0 HEIGHT=0 >
<PARAM NAME="cabbase" VALUE="clases.cab">
</APPLET>
Código fuente 213
El único navegador que soporta ficheros cabinet es Internet Explorer. El formato genérico que
presentan los ficheros .CAB es el que se puede apreciar en la Figura 147.
Otra utilidad adicional que presentan los ficheros .CAB, es la posibilidad de firmarlos digitalmente.
De esta forma, cuando un usuario cargue el applet se le preguntará si acepta la firma digital. Si acepta
el applet ya no estará sujeto a las restricciones de seguridad, es decir, se podrá conectar a cualquier
máquina de Internet, podrá acceder al sistema local de ficheros, podrá instanciar y usar objetos COM,
etc. La firma digital asegura la integridad (el código no ha sido alterado ni manipulado después de su
publicación) y la autenticidad (indicando el lugar del que proviene el código).
323
Programación en Java
© Grupo EIDOS
Figura 147
Para realizar una firma digital es necesario tener un certificado, para conseguir un certificado válido
(SPC, Software Publisher Certificate) es necesario contactar con una autoridad de certificación (CA,
Certification Authorities) algunas de ellas son: GTE, VeriSing Inc., etc. La persona que pide el
certificado debe identificarse con sus credenciales y garantizar que el código (en nuestro caso los
applets) no contiene virus ni elementos maliciosos que puedan dañar el sistema del usuario.
El segundo tipo de empaquetado que podemos realizar es a través del formato JAR, este formato es
más estándar que el anterior, ya que lo soportan tanto los navegadores Web Netscape Navigator e
Internet Explorer, además es el formato propuesto por Sun.
Los ficheros JAR se comprimen atendiendo al formato ZIP. Los ficheros JAR también permiten la
firma de código.
Para crear ficheros JAR Sun nos ofrece la herramienta Java Archive Tool, esta herramienta se ofrece
junto el kit de desarrollo JDK (Java Developers Kit) de Sun.
Para crear un fichero JAR con todos los ficheros del directorio actual deberemos escribir lo siguiente
en la línea de comandos, dónde fichero.jar contendrá todos los ficheros comprimidos.
jar cf fichero.jar *
Para que un applet contenido en un fichero JAR se pueda incluir en una página Web es necesario
escribir el código HTML que se muestra en el Código fuente 214.
<APPLET CODE="ClaseApplet.Class" WIDTH=120 HEIGHT=120
ARCHIVE="Fichero.jar">
</APPLET>
Código fuente 214
En este caso se utiliza una propiedad de la etiqueta APPLET llamada ARCHIVE. En definitiva
debemos utilizar ficheros CAB y/o JAR debido a que:
324
•
Ofrecen una compresión eficiente.
•
Supone un beneficio inmediato y significante para los usuarios de nuestra página Web ya que
permite una reducción en el tiempo de carga e inicialización de los applets.
© Grupo EIDOS
15. Applets de Java: utilizando los applets
Componentes Swing y applets
En todos los ejemplos utilizados en este capítulo y en el anterior hemos utilizado componentes AWT
dentro de objetos de la clase Applet, para construir interfaces de usuario dentro de nuestros applets,
también podríamos haber utilizado componentes Swing conjuntamente con la clase
javax.swing.JApplet, pero si lo hubiéramos hecho posiblemente al cargar nuestro applet en el
navegador no nos habría funcionado.
El problema consiste en que en las MV (maquinas virtuales) que poseen los navegadores Netscape o
Explorer no poseen la última versión del lenguaje Java, como ejemplo cabe reseñar que Internet
Explorer 5 o Netscape Navigator 4.5 no permiten la ejecución de estos applets, mejor dicho, no ocurre
nada cuando se ejecuta una página que invoca a uno de estos applets. La máquina virtuales de estos
dos navegadores se corresponden con la versión 1.1 del lenguaje Java, es decir, no soportan Java 2.
Lo mismo ocurrirá con el visor de applets de Visual J++, sin embargo el visor de applets del JBuilder
y del JDK (appletviewer) si que implementan la última versión del lenguaje Java, la plataforma Java 2.
La solución para poder utilizar applets Swing (JApplet) en un navegador Web consiste en instalar un
software (a modo de parche) que podemos encontrar en el sitio Web de Sun y que se denomina Java
Plug-in. Este añadido permite ejecutar applets implementados en Swing, que heredarán de la clase
JApplet, esta clase del paquete javax.swing la comentaremos en el siguiente apartado.
El Plug-in lo podemos obtener en la dirección http://java.sun.com/products/plugin/, a la hora de
obtener el Plug-in deberemos indicar el tipo de plataforma en el que vamos a utilizarlo. También
existen varias versiones del Plug-in en nuestro caso nos interesa la última que se corresponde con el
JDK 1.3, por lo tanto seleccionaremos el software Java Plug-in 1.3.
La instalación del Plug-in en un sistema operativo Windows no tiene ningún tipo de complicación,
simplemente deberemos ejecutar el fichero que hemos obtenido de Sun y seguir las instrucciones
correspondientes incluso el hecho de que se tenga que utilizar diferentes navegadores Internet
Explorer o Netscape Navigator no va a resultar ningún problema, ya que se instala en el sistema
operativo independientemente del navegador que se utilice.
Solo a la hora de crear la página Web que vaya a hacer la llamada al applet es la que va a ser diferente
dependiendo del tipo de navegador. Otra cosa importante es que a diferencia de los applets que no
utilizan Swing, con este tipo de applets no vamos a utilizar la etiqueta <APPLET> en el código HTML
en ningún caso, puesto que en realidad lo que estamos haciendo es una llamada a un componente
ActiveX (proporcionado por el Java Plug-in) que se encarga de su visualización. Debido a esto la
etiqueta utilizada es <OBJECT> o <EMBED> dependiendo de si el navegador Web es Internet
Explorer o Netscape Navigator respectivamente.
Así por ejemplo si tenemos la clase SwingApplet, que es la clase que representa a un applet, si
queremos incluirla en una página HTML, escribiríamos el código HTML que aparece en el Código
fuente 215.
<APPLET code="SwingApplet.class" align="baseline" width="200" height="200">
</APPLET>
Código fuente 215
Pero si esta clase es un applet de Swing, es decir, hereda de la clase JApplet, deberemos escribir el
Código fuente 216, suponiendo que el navegador Web que va a cargar la página, es Internet Explorer.
325
Programación en Java
© Grupo EIDOS
<OBJECT classid="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93"
width="200" height="200" align="baseline">
<PARAM NAME="code" VALUE="SwingApplet.class">
<PARAM NAME="type" VALUE="application/x-java-applet;version=1.3">
<PARAM NAME="scriptable" VALUE="true">
</OBJECT>
Código fuente 216
Pero si el navegador Web es Netscape Navigator escribiremos el Código fuente 217.
<EMBED type="application/x-java-applet;version=1.3" width="200"
height="200" align="baseline" code="SwingApplet.class"
</EMBED>
Código fuente 217
Y lo más recomendable es no presuponer nada sobre el navegador Web que va a ejecutar el applet, y
utilizar el código HTML que se muestra en el Código fuente 218, que es una mezcla de los dos
anteriores, para que funcione el applet correctamente tanto con el navegador Web Internet Explorer
como con el navegador Web Netscape Navigator.
<OBJECT classid="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93"
width="200" height="200" align="baseline">
<PARAM NAME="code" VALUE="SwingApplet.class">
<PARAM NAME="type" VALUE="application/x-java-applet;version=1.3">
<PARAM NAME="scriptable" VALUE="true">
<COMMENT>
<EMBED type="application/x-java-applet;version=1.3" width="200"
height="200" align="baseline" code="SwingApplet.class"
</EMBED>
</COMMENT>
</OBJECT>
Código fuente 218
En la Figura 148 se puede ver un ejemplo de ejecución de este applet de Swing, cuyo código fuente es
el Código fuente 219.
import java.awt.*;
import javax.swing.*;
public class SwingApplet extends JApplet {
JButton boton;
public void init() {
getContentPane().setLayout(new FlowLayout());
boton = new JButton("Soy un applet de Swing");
getContentPane().add(boton);
}
}
Código fuente 219
326
© Grupo EIDOS
15. Applets de Java: utilizando los applets
Figura 148
Si el lector a probado en su equipo este ejemplo, habrá comprobado lo lento y tedioso que resulta
ejecutar los applets que utilizan Swing a través del Plug-in de Java, además esto se debe considerar
como un parche a medias, ya que el Plug-in de Java los único que permite es poder utilizar los
componentes de Swing, pero no el resto de características de la versión 2 del lenguaje Java.
De momento, hasta que no exista un navegador Web que implemente en su máquina virtual de Java la
versión Java 2, es recomendable no abusar del uso de applets de Swing.
La clase JApplet
En el uno de los capítulos dedicados a los componentes Swing, más concretamente a los contenedores
Swing, ya introdujimos esta clase. Se trata de un contenedor Swing de alto nivel que se muestra dentro
de páginas Web a través de navegadores, es por lo tanto la versión Swing de la clase
java.applet.Applet, además la clase JApplet es clase hija de la clase java.applet.Applet.
Básicamente un objeto de la clase javax.swing.JApplet va a tener las mismas funciones que un objeto
de la clase java.applet.Applet, ya que como ya hemos dicho, se trata de la versión Swing de los applets
de Java. A continuación vamos a comentar algunas de las diferencias que existen entre estas dos clases
y que novedades aporta la clase JApplet sobre la clase Applet, muchas de estas novedades se
desprenden de las características comunes que presentan los componentes Swing.
La clase JApplet pertenece al paquete de los componentes Swing javax.swing, no pertenece a un
paquete específico como si lo hace la clase Applet.
Al ser un contenedor Swing de alto nivel, la clase JApplet posee un panel raíz (root pane) al igual que
sucedía con la clase JFrame. Como resultados más destacables de esta característica tenemos que es
327
Programación en Java
© Grupo EIDOS
posible añadir una barra de menú a un applet y que para añadir componentes al mismo debemos hacer
uso de su panel de contenido (content pane).
De las afirmaciones anteriores se extraen las siguientes apreciaciones a la hora de utilizar objetos de la
clase JApplet:
•
Los componentes se añaden al panel de contenido del applet, no directamente al applet, es
decir, haremos uso del método getContentPane() para obtener el panel de contenido, y sobre
este panel de contenido lanzaremos los métodos add() que sean necesarios para añadir los
distintos componentes que va a contener nuestro applet.
•
El gestor de diseño se aplicará sobre el panel de contenido del applet, no sobre el applet.
•
El gestor de diseño por defecto de los applets de Swing (JApplet) es el gestor BorderLayout, a
diferencia de la clase Applet que presentaba como gestor de diseño por defecto al gestor
FlowLayout.
Además, como todo componente Swing, la clase JApplet soporta la característica Pluggable Look &
Feel, es decir, a nuestros applets de Swing podemos darles el aspecto que deseemos entre los
diferentes Look & Feel (Mosaic, Java, Mac y Windows).
Como vemos la clase JApplet ofrece a los applets convencionales, la funcionalidad que poseen los
contenedores de alto nivel de Swing.
Por lo demás casi todo lo visto para la clase Applet se puede aplicar a la clase JApplet.
De esta forma si tomamos el ejemplo utilizado para mostrar la característica Pluggable Look & Feel,
en el tema llamado Interfaces de usuario en Java: Otras características de Swing, podemos modificarlo
para que funcione como un objeto de la clase JApplet.
No vamos a mostrar de forma completa el código, sino sólo los fragmentos que cambian. En primer
lugar la cabecera de la clase es la que se muestra en el Código fuente 220. Y el constructor se debe
eliminar.
public class LF extends JApplet implements ActionListener{
Código fuente 220
El método main() de arranque de la aplicación, se debe sustituir por el método init(), que inicializará el
applet y que posee el Código fuente 221.
public void init(){
try {
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
} catch (Exception e) { }
getContentPane().setLayout(new BoxLayout(getContentPane(),BoxLayout.Y_AXIS));
creaMenu();
creaTexto();
creaBotones();
creaSelector();
creaCombo();
creaEtiqueta();
328
© Grupo EIDOS
15. Applets de Java: utilizando los applets
creaOpciones();
}
Código fuente 221
En el método actioPerformed() se debe eliminar la llamada al método pack(). El resto del código es
exactamente igual al de la aplicación.
Si ejecutamos este applet con el visor de applets de JDK (herramienta appletviewer), obtenemos el
resultado que muestra la Figura 149.
Figura 149
El código completo del ejemplo se puede obtener en este enlace.
Con los applets de Swing damos por finalizada la parte del curso dedicada al segundo tipo de
programas que podemos realizar en Java, los applets.
329
Aspectos avanzados de Java: procesos
Introducción
En este capítulo y en el siguiente vamos a tratar algunos aspectos y características avanzadas del
lenguaje Java. Como características avanzadas se han seleccionado las siguientes:
•
La posibilidad de multiproceso en Java, es decir, la posibilidad de tener distintos hilos de
ejecución paralelos. Veremos la clase Thread y el interfaz Runnable.
•
Utilización de canales de entrada salida, para leer y escribir información en diversos medios,
desde la memoria a ficheros en disco. Ilustraremos los canales con numerosos ejemplos.
•
Aplicaciones cliente/servidor en Java a través del mecanismo de los sockets, es decir, a través
de conexiones en Internet. Comentaremos las clases Socket y ServerSocket.
Procesos y multiproceso
El ser humano y toda criatura viva posee la capacidad del multiproceso. Para regir correctamente sus
acciones reflejas, los seres humanos mantienen una serie de procesos simultáneos y que en perfecta
armonía hacen posible su desarrollo como ser vivo.
El latido del corazón, la digestión, la respiración, son algunos de los procesos que de forma
simultánea, y perfectamente coordinada, tienen lugar diariamente en nuestro cuerpo. Como ya ha
ocurrido en multitud de ocasiones el ser humano ha emulado lo que ya estaba presente en la naturaleza
(véanse las alas de un avión a imagen de las alas de las aves como muestra de lo dicho), y a igual que
Programación en Java
© Grupo EIDOS
procesos humanos los programas de software necesitan de los procesos para acomodarse a los
requerimientos actuales de los usuarios.
Hace unos años, un procesador de textos debía paralizar el sistema del usuario mientras preparaba el
documento para la impresión y lo imprimía. El programa se apoyaba en el tamaño del buffer de la
impresora para retornar el control del ordenador al usuario con mayor o menor celeridad. Los
procesadores de textos de la generación anterior a la actual ya salvaban esta incomodidad permitiendo
el proceso en segundo plano de la tarea de impresión.
La época en que todo el tiempo de la CPU de un ordenador personal era propiedad de un único
proceso, ya pasó. Ahora no solamente múltiples procesos se están ejecutando en un mismo instante de
tiempo, sino que también multitud de programas realizan diversos cometidos simultáneamente en el
equipo del usuario.
Hola mundo con hilos
Vamos a rescribir el applet que realizábamos en un capítulo anterior y que mostraba en pantalla el
saludo Hola Mundo, para que utilicemos la técnica de multihilo en Java. El código se muestra en el
Código fuente 222.
import java.awt.*;
import java.applet.*;
public class HolaMundoHilo extends Applet implements Runnable{
Thread oHilo;
public void start(){
if( oHilo == null ){
oHilo = new Thread(this);
oHilo.start();
}
}
public void run(){
// Lo que tengamos que hacer
}
public void stop(){
if( oHilo != null ){
oHilo.stop();
oHilo = null;
}
}
public void paint( Graphics g ){
g.drawString( "!Hola Mundo¡", 0, 10 );
}
}
Código fuente 222
Si comparamos éste con el antiguo, vemos unas mínimas diferencias, a saber:
1. Hacemos que la clase principal de nuestra applet incorpore el interfaz Runnable. La interfaz
Runnable habilita los mecanismos necesarios para la ejecución de un proceso, en este caso la
declaración del método run().
2. Dotamos a la clase de una variable que será la encargada de almacenar el proceso del applet.
Indicamos para esta tarea que nuestra variable será de la clase Thread (hilo de proceso,
332
© Grupo EIDOS
16. Aspectos avanzados de Java: procesos
proceso). Thread es una clase definida en el paquete java.lang por lo que no es necesario
importarlo ya que este paquete es automáticamente importado para todos los fuentes.
3. Añadimos el método start(), en él crearemos el hilo de proceso para la applet, y enviaremos el
mensaje start() sobre él para que se inicie su ejecución. Una vez finalizada la construcción del
objeto Thread ligándolo al applet, se deposita en el objeto oHilo para futuras referencias. En la
línea siguiente se pone en funcionamiento el proceso por medio del envío del mensaje start()
sobre oHilo. Una vez que el proceso es puesto en marcha, éste se encarga de llamar al método
run() de la clase (se debe recordar que en el paso uno hicimos las modificaciones oportunas
para importar el interfaz Runnable). Dentro del método run() será donde situaremos el código
de aquello que deseamos realice nuestro proceso.
Si run() es el punto de entrada del proceso, stop() es el de salida. En el método stop() del
applet tras comprobarse que oHilo no está vacío, se envía sobre él el mensaje stop() y se vacía
la variable de su contenido, asignándola el valor null. En caso de que se retorne a dicha página
el método start() del applet será el encargado de crear nuevamente el proceso, reiniciando todo
el ciclo.
Aunque como se puede observar en el código del applet nuestro proceso no hace nada, simplemente se
ha añadido para mostrar una pequeña introducción a la programación multiproceso en Java.
Paralelismo
Por diferentes motivos, el paralelismo, que habilitan los procesos, es un elemento a tener muy en
cuenta a la hora de escribir el código de un proceso. Hemos de adecuar nuestra mente monoproceso a
una mente multiproceso en la que diferentes procesos, o incluso múltiples ocurrencias del mismo
proceso pueden estar ejecutándose a un tiempo.
Con un problema similar, aunque a menor escala, se encuentra el programador que ha de migrar sus
aplicaciones desde un entorno monousuario hacia uno multiusuario. Exponemos el típico problema
que se plantea en el que un punto de venta con varios operadores despachando artículos. Dos
dependientes, A y B, realizan la venta de una cafetera del mismo modelo. Una de las acciones del
programa es descontar de la tabla de existencias el artículo despachado. Podríamos optar por un
código similar al Código fuente 223.
oExistencias.nCantidad --;
Código fuente 223
Imaginemos que al comenzar había 20 cafeteras, sí ambas actualizaciones son coincidentes en el
tiempo, y el programa no fue concebido para ejecutarse en un entorno multiusuario, pudiera producirse
la circunstancia en que los dos puntos de venta tomaran 20 como base de la línea que decrementa,
perdiéndose una de las actualizaciones.
En este caso la solución es sencilla, bien por medio de la seguridad que una base de datos aporta a este
tipo de transacciones, bien porque nosotros manualmente bloqueemos la fila donde se va a producir la
actualización.
Podemos pensar que el problema, después de todo, no es tan grave, pero pensemos en el Código fuente
224, que bien pudiera formar parte del proceso que dispensa dinero en un cajero automático.
333
Programación en Java
© Grupo EIDOS
if( nSaldo > 0 ){
// Se realizan diversas operaciones tomando como base nSaldo
// ..
nSaldo -= nInvertido;
}
Código fuente 224
Si sabemos que el proceso que contiene dicho código puede ejecutarse varías veces, puede darse el
caso de que dos o más lleguen a la comparación al mismo tiempo... y ya tenemos problema a la vista
puesto que nSaldo - nInvertido será idéntico en ambos procesos, uno de los decrementos se pierde.
Imaginemos que el sistema ha autorizado dos trasferencias de 100.000 dólares es una cuenta cuyo
nSaldo era de 112.003 dólares.
Podríamos estar pensando que reduciendo más y más el área del ejemplo podríamos llegar a eliminar
el problema. Esto no es posible sin operaciones atómicas (este tipo de operaciones parecen suceder
todas a un mismo tiempo, es decir, no pudiendo ser interrumpidas por otro proceso durante la
ejecución del primero).
Pero éste es un problema soluble. La librería de Java fue concebida desde el principio con los procesos
en mente, por lo que no deberemos preocuparnos de la pulcritud de las clases estándar. Además se
aportan todos los elementos dentro del lenguaje para que los procesos que nosotros creemos, se
comporten correctamente en un ambiente multiproceso. Vamos a aproximarnos al tema que nos
ocupará por medio de la prueba y error, que aunque tedioso en algunos casos es uno de los mejores
métodos para enfrentarse a un lenguaje de programación.
Observemos el Código fuente 225.
public CuentaProcesos{
int nValor;
public void agregame(){
nValor ++;
}
public int cuantos(){
return nValor;
}
}
Código fuente 225
Olvidemos por el momento como se crean los procesos, pensemos que en el sistema se han creado una
serie de ellos a partir de la clase CuentaProcesos y se están ejecutando. El incremento de nValor no es
una operación atómica.
Requiere tres pasos: obtener el valor actual de nValor, agregarle uno y asignarlo a nValor. Al no ser
una operación atómica no esta sincronizada y, por tanto, no es segura. Esta falta de seguridad puede
provocar fallos a la hora de llevar la cuenta de procesos, cometido de la hilo que hemos codificado.
Una palabra clave, synchronized, viene a resolver nuestro problema.
public CuentaProcesos{
int nValor;
334
© Grupo EIDOS
16. Aspectos avanzados de Java: procesos
public synchronized void agregame(){
nValor ++;
}
public int cuantos(){
return nValor;
}
}
Código fuente 226
Con synchronized informamos a la máquina virtual de Java que el método sólo podrá ejecutarse por un
proceso al mismo tiempo. Los procesos esperarán a que uno termine para que otro empiece, pero esto
tiene un serio inconveniente en grandes métodos synchronized. Estos pueden provocar importantes
cuellos de botella en el sistema al esperar que les llegue el turno de ejecución. En el caso de las
variables, Java las define como sincronizadas por defecto. En el raro caso de que no deseemos
sincronización para variables deberemos indicar el modificador volatile para las mismas.
El método cuantos() no ha de ser synchronized ya que tan sólo retorna el valor de una variable del
objeto. Veamos ahora el Código fuente 227.
public class Dimension{
private int nAlto, nAncho;
public int nAlto(){
return nAlto;
}
public nAncho(){
return nAncho;
}
// ... métodos para fijar nAlto y nAncho
}
public ImprimeDimension{
public void print( Dimension d ){
System.out.println( "El ancho es " + d.nAncho() +
" y el alto es " + d.nAlto()+" para el objeto dimensión" );
}
}
Código fuente 227
Similares en cometido al método cuantos() de la clase CuentaProcesos, son en la clase Dimension los
métodos nAlto() y nAncho(). No necesitan ser synchronized ya que tan solo retornan variables del
objeto (recuérdese que Java las define como synchronized por defecto). Es cometido y responsabilidad
de la parte de código que llama a nAncho() y nAlto() saber si ha de sincronizarse o no. Aunque la
operación del método print() es simple, no es atómica. El método print() lee dos valores y los imprime.
Precisamente entre la ejecución de nAlto() y nAncho() podría suceder que otro proceso cambiara los
valores, falseándose los resultados mostrados. Cuando se trabaja con procesos es de vital importancia
no suponer que nada se ha ejecutado entre dos partes de nuestro programa, incluso entre dos partes de
una misma expresión. Debemos tener siempre presenta la pregunta ¿qué sucederá si dos procesos
ejecutan esta línea al mismo tiempo?, igualmente veamos si la operación que se está realizando es
atómica o no.
Veamos una versión más segura de ImprimeDimension que nos permite ser más precisos a la hora de
especificar que parte del método deseamos que esté protegido.
335
Programación en Java
© Grupo EIDOS
public ImprimeDimension2{
public void print( Dimension d ){
int nAnchoAux, nAltoAux;
// Todo el bloque ocurre de forma atómica
synchronized(this){
nAnchoAux = d.nAncho();
nAltoAux = d.nAlto();
}
System.out.println( "El ancho es " + nAnchoAux +
" y el alto es " + nAltoAux +" para el objeto dimensión" );
}
}
Código fuente 228
Ahora hemos visto otra de las posibilidades que nos da la sentencia synchronized. A esta sentencia
podemos enviarle un parámetro que indique el objeto a bloquear si más de un proceso intenta la
ejecución del código encerrado entre llaves. De este modo liberamos al sistema de caer en cuellos de
botella de los anteriormente indicados. Ahora los programadores tenemos la potestad de indicar qué
partes de un método son seguras y qué parte no necesitan serlo, pudiéndose, por tanto, ejecutarse a un
mismo tiempo.
Aunque hemos dado un paso importante nadie impide que otro proceso cambie el valor de Dimension
d. Para impedir este comportamiento indeseable remitiremos a synchronized el objeto d, asegurando
de este modo que nada ni nadie podrá modificar el valor de Dimension d durante la obtención de los
valores nAlto y nAncho.
public ImprimeDimension3{
public void print( Dimension d ){
int nAnchoAux, nAltoAux;
// Todo el bloque ocurre de forma atómica
// y solamente un proceso puede cambiar d
synchronized(d){
nAnchoAux = d.nAncho();
nAltoAux = d.nAlto();
}
System.out.println( "El ancho es " + nAnchoAux+
" y el alto es " + nAltoAux+" para el objeto dimensión" );
}
}
Código fuente 229
Con esta leve pero efectiva modificación hemos completado la seguridad que estaba en nuestras
manos, siempre y cuando asumamos que la forma de obtener Dimension d es segura. Esta asunción es
totalmente fiable siempre que se trate de clases del sistema. Pero fuimos nosotros los que codificamos
la clase Dimension y es nuestra responsabilidad codificar un método seguro para cambiar los valores
nAlto y nAncho. Podemos asegurar esto codificando un único método que fije ambos valores.
public class Dimension2{
private int nAncho, nAlto;
// Los métodos para obtener nAlto y nAncho...
336
© Grupo EIDOS
16. Aspectos avanzados de Java: procesos
public synchronized void fijarAltoAncho(nAlto, nAncho){
nAlto = nAltoNuevo;
nAncho = nAnchoNuevo;
}
}
Código fuente 230
Haciendo synchronized el único método con el que se pueden alterar los contenidos de nAncho y
nAlto protegemos el código ante cualquier actualización concurrente que pudiera suceder.
Con synchronized garantizamos que sólo un método de este tipo se estará ejecutando a un mismo
tiempo. Esta garantía es posible debido a que una de las características de synchronized es
precisamente que sólo un método synchronized puede estar ejecutándose para la misma clase en el
mismo instante de tiempo.
Utilizando procesos. El interfaz runnable
Para realizar un proceso en Java se debe seguir los siguientes pasos:
1. Heredamos de la clase Thread.
public class MiThreadHeredado extend Thread{
public void run(){
// Lo que deseemos que ejecute MiThreadHeredado
}
}
Código fuente 231
2. No tenemos nada más que un descendiente de Thread con las nuevas características que le
hayamos indicado. Pero para que todo se ponga en funcionamiento, debemos crear una
ocurrencia real en el ámbito de ejecución de Java para que, sea lo que fuere, lo que
programamos en el método run() se active.
oMTH = new MiThreadHeredado();
Código fuente 232
3. Una vez creado el objeto, tan sólo nos queda echarlo a andar. Esto lo conseguimos enviando el
mensaje start() sobre el objeto recién creado. El método start(), codificado dentro de la librería
estándar de Java, será el encargado de enviar sobre sí mismo (el objeto que estamos creando,
this) el mensaje run() que ejecutará el método homónimo. El método run() ejecutado será él
que codificamos con aquello con lo que deseábamos particularizar la nueva clase.
oMTH.start()
Código fuente 233
337
Programación en Java
© Grupo EIDOS
Algunas tareas adicionales que habitualmente desearemos ejecutar sobre un proceso serán pararlo
temporal o definitivamente y reanudar su ejecución tras una parada temporal. Para paralizar
temporalmente la ejecución de un proceso utilizaremos el mensaje suspend(). Cuando deseemos
reanudar la ejecución de un proceso parado con suspend() utilizamos resume().
oMTH.suspend();
// lo que necesitemos hacer mientras oMTH no se está ejecutando
oMTH.resume();
Código fuente 234
Cuando indicamos que un método es synchronized, Java realiza automáticamente esta desactivación y
posteriormente la activación. Todos los objetos que estén haciendo uso del código del método en
cuestión recibirán el correspondiente mensaje suspend(), exceptuando el que tenga la atención de la
máquina virtual de Java en ese instante. Una vez concluya la ejecución del método que debe ser
sincronizado (fue definido como synchronized) todos los procesos que fueron temporalmente
paralizados recibirán el mensaje resume().
Esta tarea sería ardua si tuviéramos que realizarla manualmente. Gracias a que Java fue diseñado
desde un principio para ser un entorno multiproceso podemos despreocuparnos ahora de estas
pequeñeces.
Para una parada definitiva haremos uso del método stop().
¿Qué ocurre si necesitamos que la nueva clase que vamos a definir tome ciertas características de, por
ejemplo, la clase Impresora que no hereda de Thread?; ¿podemos en Java heredar de dos clases a la
vez?; ¿cómo podemos resolver este pequeño inconveniente con lo que ya sabemos?.
Efectivamente la respuesta negativa a la segunda pregunta nos pone en la pista para dar una respuesta
correcta a la tercera pregunta. Y esta respuesta no ha de ser otra que los interfaces, de los que ya se ha
hablado en capítulos anteriores a lo largo del presente curso. Contestando de este modo la tercera
pregunta, la respuesta a la primera pregunta es clara: deberemos aprender a utilizar la interfaz
Runnable.
Sustituyamos el primer paso que comentábamos anteriormente por este otro:
1. Debemos implementar el interfaz Runnable.
public class MiNuevaImpresora extend Impresora implements Runnable{
public void run(){
// Lo que deseemos que caracterice a MiNuevaImpresora
}
}
Código fuente 235
Ahora deberemos crear un proceso. Pero deberemos remitir al constructor de la clase Thread un objeto
de la clase MiNuevaImpresora, que va a ser la clase que va a contener el método run() que ejecutará el
proceso correspondiente.
338
© Grupo EIDOS
16. Aspectos avanzados de Java: procesos
Thread oHiloParaMNI;
MiNuevaImpresora oMNI;
oMNI = new MiNuevaImpresora();
oHiloParaMNI = new Thread( oMNI );
oHiloParaMNI.start()// Ejecutará el método run() de la clase de oMNI
Código fuente 236
Tras declarar un objeto de la clase Thread, otro de la clase MiNuevaImpresora y construir el objeto
oMNI a partir de las especificaciones de la clase MiNuevaImpresora procedemos a crear el proceso
vinculándolo al objeto oMNI. Este último paso no nos debe dejar indiferentes por dos motivos básicos:
primero porque es fundamental para poder implementar clases que se desarrollen dentro de un proceso
y que hereden sus características de otra clase diferente a Thread, segundo ya que es de suma
importancia para comprender como funciona la característica de las interfaces en Java.
Gracias a que indicamos que la clase MiNuevaImpresora implementaba la interfaz Runnable
disponemos del método run() para una clase que no heredó de Thread, o mejor dicho, nos obligamos a
codificar un método run() para dicha clase. Cuando se ejecute el método start() parte del proceso
consistirá en enviar el mensaje run() sobre el objeto que fue remitido como parámetro (recuérdese que
oMNI fue remitido como argumento en el momento de crear el oHiloParaMNI).
Este objeto oMNI será guardado en una variable interna del objeto Thread, llamémosla por ejemplo
oOwner, cuando se esté ejecutando el método start(), éste comprobará si oOwner es un objeto, en caso
afirmativo se enviará sobre él el mensaje run().
Apoyándonos en los dos siguientes códigos vamos a mostrar un ejemplo algo más completo que el
anterior.
El primero de los códigos fuente corresponde al archivo SencilloEjRunnable.java y contiene la
definición de la clase SencilloEjRunnable que implementa la interfaz Runnable. En el ejemplo, y en
aras de una mejor didáctica, el método run() de esta clase tan sólo muestra un mensaje con el nombre
del proceso en la consola (pantalla).
public class SencilloEjRunnable implements Runnable{
public void run(){
System.out.println( "En el hilo llamado'" +
Thread.currentThread().getName() + "'" );
}
}
Código fuente 237
Pero si SencilloEjRunnable no hereda de Thread....¿Cómo se averigua el nombre del Thread sobre el
que se ejecuta?. Si SencilloEjRunnable fuera una clase que hubiera heredado de Thread la respuesta
sería clara: el objeto Thread sería el mismo, this. Pero al no ser una clase que herede de Thread, sino
una que implementa la interfaz Runnable, hemos de poder referirnos de alguna manera al objeto
Thread dentro del marco del cual se está ejecutando SencilloEjRunnable.
Por medio del método estático currentThread() lanzado sobre el objeto genérico, identificado por
Thread, accedemos al objeto que refleja al proceso en cuestión. Una vez que tenemos dicho objeto
339
Programación en Java
© Grupo EIDOS
enviamos sobre éste el mensaje getName(), existente para la clase Thread, que nos devuelve el nombre
del hilo de ejecución.
El Código fuente 238 corresponde al archivo PruebaSER.java y contiene la definición de la clase
PruebaSER. Estamos utilizando una aplicación de consola, por lo que definimos un método main().
Como ya sabemos, en este método main() situamos la lógica que deseamos realice nuestra aplicación
al ser lanzada. En PruebaSER la tarea a realizar será la creación indefinida de procesos que sigan el
modelo definido en la clase SencilloEjRunnable.
public class PruebaSER{
public static void main( String argv[] ){
SencilloEjRunnable oSER;
oSER = new SencilloEjRunnable();
while( true ){
Thread oH = new Thread(oSER);
if (oH==null)
System.out.println( "new Thread() falló.");
else
System.out.println( "new Thread() correcto.");
oH.start();
// Espera a que la hilo finalice su método run()
try{
oH.join();
} catch( InterruptedException ex) {}
}
}
}
Código fuente 238
Tras declarar la clase el método main(), y crear el objeto oSER, que representa una instancia de la
clase SencilloEjRunnable nos introducimos en el bucle infinito. Creamos el proceso para el objeto
oSER y lo almacenamos en el objeto oH. Posteriormente visualizamos un mensaje en la consola
indicando si el proceso de construcción del objeto Thread fue satisfactorio o no.
Acto seguido ponemos a funcionar el proceso e indirectamente también nuestra clase SencilloEjRunnable, se debe recordar, que el método run() de SencilloEjRunnable será ejecutado por el método
start() del hilo recién creado. El objeto oSER fue remitido como parámetro en la construcción del
objeto oH, acción por la que fueron vinculados.
Como última acción el bucle se introduce en una espera por medio del envío del mensaje join() sobre
el proceso. El método join() espera a que el proceso termine. El programa queda a la espera de que el
método run() del proceso finalice. Cuando esto suceda una excepción InterruptedException será
enviada.
340
© Grupo EIDOS
16. Aspectos avanzados de Java: procesos
Es importante detenerse a evaluar las implicaciones que puede tener un join() infinito en cada caso. No
tenemos ninguna seguridad del tiempo que tomará un método run() en completarse. En el ejemplo es
inmediato puesto que lo único que hace es imprimir un literal en pantalla. Si necesitamos hacer un
join() temporizado también disponemos de esta posibilidad ya que el mensaje admite como parámetro
la temporización en milisegundos y opcionalmente nanosegundos.
En la Figura 150 podemos ver parte de la salida que produce esta aplicación.
Figura 150
Si ejecutamos este código para detenerlo deberemos abortar su ejecución, ya que hemos construido un
bucle infinito.
Se propone en este segundo ejemplo una variación sencilla respecto a la clase PruebaSER. Esta
variación reside únicamente en que el nombre que tendrá el proceso lo fijamos nosotros en lugar de
permitir que sea Java quien lo nombre. Para esta tarea sólo hemos de indicar el nombre que deseamos
como parámetro en la línea del fuente donde se construye el proceso.
Thread oH = new Thread( oSER, nCuenta + " hilos lanzados.");
Código fuente 239
La variable nCuenta es un contador que almacena el número de proceso que se llevan lanzados. Dicha
variable debe de incrementarse, lógicamente, dentro del bucle. La ventaja de poder nombrar como
deseemos es obvia, identificaremos de modo mucho más fácil un proceso al que seamos nosotros
quienes demos nombre.
Sabemos como parar un proceso por medio del método stop(). Pero Si necesitamos realizar alguna
acción cuando pare un proceso ¿Cómo debemos estar a la escucha de esta eventualidad?. La respuesta
se encuentra ligada a la manipulación de excepciones, pues es por medio de ellas como nos enteramos
que un proceso a muerto. Cuando se lanza el método stop() sobre un proceso, éste lanza una excepción
del tipo ThreadDeath. Nuestro cometido es habilitar la correspondiente construcción try-catch que
atienda esta excepción.
public class Prueba{
public static void main( String argv[] ){
SencilloEjRunnable oSER;
341
Programación en Java
© Grupo EIDOS
oSER = new SencilloEjRunnable();
Thread oH = new Thread(oSER);
try{
oH.start();
//...
// El código que en alguna situación enviará stop()
//...
}
catch( ThreadDeath oTD ){
// lo que deseamos hacer cuando se detenga el hilo
// por medio de stop()
throw oTD;
}
}
}
Código fuente 240
Es importante indicar que esta construcción sólo resuelve la captura del final de un hilo si éste es
parado por medio de stop(). Un hilo puede pararse por otros motivos diferentes a la ejecución del
método stop(), por ejemplo lanzando alguna excepción. Si necesitamos ejecutar algún código sea cual
sea el camino por el que finalice una hilo, lo situaremos en la cláusula finally de la construcción trycatch.
Vamos a ver un ejemplo más de la utilización de hilos de ejecución, en este caso va a ser un ejemplo
más vistoso, se trata de crear un applet que realice las funciones de un reloj, es decir, va a mostrar la
hora actual del sistema y se va a ir actualizando cada segundo. La clase de este ejemplo se va a
denominar RelojApplet, esta clase hereda de la clase Applet e implementa el interfaz Runnable. Su
código fuente es el Código fuente 241.
import java.awt.*;
import java.util.Calendar;
import java.util.Date;
import java.text.DateFormat;
import java.applet.*;
import java.awt.event.*;
//implementamos el interfaz Runnable porque tenemos un hilo de ejecución
public class RelojApplet extends Applet implements Runnable {
private Thread relojThread=null;
private boolean pulsado;
public void init(){
pulsado=true;
addMouseListener(new AdaptadorRaton());
}
public void start() {
if (relojThread == null) {
relojThread = new Thread(this, "reloj");
//se inicia el hilo de ejecución
//que ejecutará el método run()
relojThread.start();
}
}
public void run() {
Thread miThread = Thread.currentThread();
while (relojThread == miThread) {
repaint();
try {
//realizamos una pausa de 1 segundo (1000 milisegundos)
relojThread.sleep(1000);
342
© Grupo EIDOS
16. Aspectos avanzados de Java: procesos
//Thread.sleep(1000);
}catch (InterruptedException e){ }
}
//finaliza el hilo de ejecución
}
public void paint(Graphics g) {
//para obtener la hora actual
Calendar calendario = Calendar.getInstance();
Date fecha = calendario.getTime();
//formato de la hora
DateFormat formatoFecha = DateFormat.getTimeInstance();
g.setFont(new Font("Arial",Font.ITALIC+Font.BOLD,14));
g.drawString(formatoFecha.format(fecha), 5, 10);
}
public void stop() {
//se destruye el hilo de ejecución
relojThread = null;
}
class AdaptadorRaton extends MouseAdapter{
//al pulsar sobre el applet se parará o reanudará el hilo
public void mousePressed(MouseEvent evento){
AppletContext contexto=getAppletContext();
if (pulsado){
relojThread.suspend();
contexto.showStatus("Hilo en pausa");
}else{
relojThread.resume();
contexto.showStatus("Hilo en ejecución");
}
pulsado=!pulsado;
}
}
}
Código fuente 241
Como se puede apreciar en el método run() se realiza una pausa de un segundo, ya que como es lógico
el reloj se actualizará cada segundo, para ello se lanza el método repaint(), que llamará paint(). El
método paint() del applet únicamente recupera la hora actual y le aplica el formato deseado, a
continuación se dibuja la cadena en la superficie del applet, que va a representa la hora actual.
Al pulsar con el ratón sobre la superficie del applet se parará o se iniciará el hilo de ejecución,
atendiendo al valor del atributo de tipo booleano pulsado, que hace las veces de interruptor. En la
Figura 151 se puede ver un ejemplo de ejecución de este applet.
Figura 151
343
Programación en Java
© Grupo EIDOS
Coordinando los procesos
El planificador de procesos de un sistema operativo que los permita, debe poner orden a la ejecución
de los mismos. Igualmente ha de fijar prioridades a la solicitud de los tiempos-máquina solicitados por
cada uno de los procesos implicados en la realización de una serie de tareas en un instante
determinado.
Para esta tarea se han realizado abordajes diferentes dando lugar a dos tipos fundamentales de
coordinación de procesos: Sin derecho preferente y con derecho preferente
Sin derecho preferente
En este tipo de planificadores la decisión de ceder el control a otro proceso recae sobre el hilo en
ejecución. Por este motivo la cortesía es el factor clave en este tipo de planificadores, para un reparto
equitativo del tiempo. Es ideal para aplicaciones críticas o en tiempo real en las que un proceso no
puede estar detenido por mucho tiempo porque haya muchos otros ejecutándose al mismo tiempo.
Con derecho preferente
En las aplicaciones modernas se ha implantado otro tipo de distribuidor. En estos nuevos
planificadores es tarea del mismo decidir que proceso se ejecuta en cada instante. Esta característica
facilita enormemente la codificación de programas con procesos. Actualmente también podemos
definir una escala de prioridades, para que el planificador ceda más o menos tiempo en función de
ésta. El planificador de Java puede ser informado de la prioridad de un hilo por medio del método
setPriority() de la clase Thread. Remitiendo al mismo un valor comprendido entre las constantes
MIN_PRIORITY (de valor 1) y MAX_PRIORITY (de valor 10).
344
Aspectos avanzados de Java: canales y
Sockets
Introducción a los canales
La comunicación de Java con el "exterior" se realiza a través de canales o flujos, que no son más que
caminos que usan los programas para comunicarse con otros programas o con cualquier dispositivo de
entrada o salida, y por los que se transmite la información.
Gracias a esta manera de enviar información, el origen y el destino de los datos no necesitan
conocerse, ni saber qué va a hacer cada uno de ellos con los datos, ni qué tipo de datos se envía, ni por
qué medio se está transmitiendo la información. Esto es así debido a que todas las operaciones de
lectura y escritura se realizan sobre el canal. Así, leeríamos igual de un canal que viniera de un fichero
que de uno que llegara desde Internet. Esta independencia permitiría que si en un programa cambiase
la fuente de los datos, sólo habría que especificar el nuevo canal y no haría falta cambiar la forma en
que se tratan los datos.
Esta independencia que nos dan los canales tiene sus límites, pues, en algún momento, tendremos que
decirle al programa dónde queremos que nos escriba los datos o de dónde los queremos leer. Es más,
hay canales para el tipo de datos que enviamos o recibimos, ya sean caracteres unicode o bytes; dentro
de esa división hay otra dependiendo del origen o destino de los datos: memoria, fichero, ...; y
finalmente hay otra clasificación según el tipo de operación que realizan los canales con los datos: los
que realizan alguna operación y los que no hacen nada con los datos.
Programación en Java
© Grupo EIDOS
Como vemos, esa supuesta independencia que nos dan los canales es sólo a la hora de tratarlos, ya que
cuando tenemos que crearlos la cosa se complica un poco y aquí sí que tenemos que saber por dónde
van a ir los datos, qué tipo de datos vamos a tratar y lo que vamos a hacer con ellos.
Al tratar con canales de datos (en inglés streams, que literalmente serían corrientes de datos), debemos
tener en cuenta dos cosas:
•
Cuando accedemos a un canal se bloquean los demás procesos. Para evitar que el canal
bloquee la ejecución, lo normal será colocarlo en un hilo de ejecución separado.
•
Si se produce algún error mientras se opera con un canal, se lanzará una excepción del tipo
IOException, por lo tanto tenemos que tratar estas posibles excepciones introduciendo la
operación con el canal en un bloque try-catch.
Ahora que ya sabemos lo que es un canal de datos, vamos a empezar por los más sencillos que seguro
ya hemos usado en distintos ejemplos: los canales estándar. Luego continuaremos con los canales
especializados del paquete java.io.
Canales estándar de entrada/salida
Los canales estándar vienen definidos en la clase System y ya los hemos usado aunque no supiéramos
que se consideraban canales. Los canales estándar nos comunican, por defecto, con la pantalla y el
teclado. La salida o entrada de estos canales se puede redirigir, pero en el caso de que queramos
hacerlo, será mejor usar uno de los canales especializados que veremos en el paquete java.io .
La clase System implementa tres canales de comunicación:
•
Salida estándar (System.out): es la salida por pantalla que ya hemos usado desde nuestras
aplicaciones. No la usamos desde los applets ya que en ellos no escribimos directamente en la
pantalla (no está permitido). Los métodos más habituales son print() y println().
// Muestra ‘Hola’. Deja el cursor al final del texto
System.out.print("Hola");
// Muestra ‘Adiós’ y salta de línea
System.out.println("Adiós");
Código fuente 242
•
Errores estándar (System.err): es un canal especial cuya única diferencia con la salida estándar
es que no puede ser redirigido: los datos siempre saldrán por pantalla. Por lo demás es igual
que System.out y comparte sus métodos.
System.err.println("¡Esto es un error!");
Código fuente 243
346
© Grupo EIDOS
•
17. Aspectos avanzados de Java: canales y Sockets
Entrada estándar (System.in): es la entrada por teclado. El método más usado es read() con el
que leeríamos caracteres hasta que le especifiquemos en una condición. El método read() tiene
un comportamiento que merece ser explicado.
El método read() devuelve un entero. Para estudiar su funcionamiento, vamos a ver un ejemplo muy
simple: un programa que lee del teclado y luego nos muestra por pantalla lo que hemos escrito.
import java.io.*;
class LecturaEstandar{
public static void main(String args[]) throws IOException{
int caracter;
System.out.println("Escribe lo que quieras.
Para terminar, pulsa ENTER");
System.out.println();
while ( (caracter = System.in.read()) != 13 )
System.out.println(caracter);
}
}
Código fuente 244
A la vista de la ejecución de este programa nos podemos preguntar dónde guarda los caracteres que
vamos introduciendo, ya que no hemos definido ningún array de enteros y sin embargo nos muestra
todos los caracteres.
Lo que ocurre cuando llamamos a read() es que se crea un canal de entrada que sólo se lee cuando
pulsamos la tecla ENTER (en la entrada por teclado no hay fin de fichero). Una vez que lo hemos
pulsado, read() leerá el canal secuencialmente, comprobando la condición para dejar de leer. Mientras
lee el canal, va mostrando los caracteres por la pantalla, de uno en uno.
En la Figura 152 se puede ver un ejemplo del resultado.
Figura 152
Si cambiásemos la condición para dejar de leer y le forzáramos a terminar al escribir ‘A’, la condición
sería la que se muestra en el Código fuente 245.
347
Programación en Java
© Grupo EIDOS
while ((caracter = System.in.read()) != 65) // Código ASCII de ‘A’
Código fuente 245
Al ejecutarlo ahora veríamos lo que se muestra en la Figura 153
Figura 153
Esta vez nos deja escribir (llenar el canal) hasta que pulsamos ENTER. Una vez pulsado, es cuando se
analizan los datos que hay en el canal, y vemos que sólo lee las cuatro ‘a’ ya que al llegar a la ‘A’, se
cumple la condición de salida del bucle e ignora el resto de los datos que hay en el canal.
Con estos tres canales podemos realizar operaciones básicas, pero no es suficiente para manejar
ficheros, leer de Internet, filtrar datos, etc.... Para estas operaciones más específicas usaremos los
canales que nos proporciona el paquete java.io.
Canales de java.io
Las clases que ofrece el paquete java.io para manejar los distintos tipos de canales se dividen en el tipo
de dato que manejan, ya sean caracteres o bytes. La diferencia entre los dos tipos es que Java usa
caracteres Unicode de 16 bits y los bytes tienen 8 bits. Normalmente no hay diferencia al usar uno u
otro, ya que si usamos un sistema operativo que use caracteres tradicionales de 8 bits, Java, al leer un
carácter, leerá 8 bits y no 16. Esto lo hace casi siempre, pero en algunos casos sí puede guardar un
carácter de 16 bits cuando nosotros queremos guardar uno de 8. Por esto, lo más recomendable es
trabajar con canales de bytes a no ser que estemos seguros de que queremos usar caracteres Unicode.
Como los canales de byte y de carácter se comportan prácticamente igual, solo que unos tratando bytes
y los otros tratando caracteres, sería más lógico clasificar los canales por la forma de tratar los datos.
Atendiendo a esto, tenemos otros dos tipos de canales:
348
•
Canales de transmisión (Data Sink Streams): son canales que no realizan ninguna operación
con los datos que leen o escriben en el canal. Se limitan a transmitir los datos.
•
Canales de proceso (Processing Streams): son canales que sí realizan alguna operación con los
datos antes de que los reciba el programa. Estos canales actúan sobre un canal de transmisión,
es decir, el canal de transmisión establece el flujo de datos y el canal de proceso trabaja sobre
los datos que hay en ese canal. Algunos procesos son: filtrado, concatenación de canales,
conversión de datos, recuento de líneas, etc...
© Grupo EIDOS
17. Aspectos avanzados de Java: canales y Sockets
Todas las clases son subclases (clases hijas) de InputStream y de OutputStream que definen un canal
cualquiera de entrada y otro de salida. Gracias a estas superclases, el tratamiento de los canales es
prácticamente igual, salvando las peculiaridades de cada uno en particular. Estas clases tienen unos
métodos comunes para todos los canales. Los más importantes son read() y write() para leer y escribir,
pero implementan otros que no todos los canales pueden usar satisfactoriamente. Este uso de métodos
comunes es lo que permite la independencia del canal a la hora del tratamiento: podemos usar read()
en todos los canales de entrada y write() en los de salida. Luego quedan las operaciones especiales que
tenga cada canal que será por lo que le hemos elegido: no vale cualquier canal para cualquier cosa.
Los métodos read() de InputStream son:
•
int read()
•
int read(byte cadena[])
•
int read(byte cadena[], int comienzo, int longitud)
El primer método lee un byte y nos devuelve su valor en forma de entero. El segundo lee una cadena
de bytes hasta que no haya más datos disponibles y la guarda en la variable cadena. Devuelve el
número de bytes leídos. El tercero lee una cadena de bytes desde comienzo hasta que lea los bytes
especificados en longitud o no tenga más datos para leer, y los guarda en cadena. Devuelve el número
de bytes leídos.
Los mismos métodos están disponibles para leer caracteres. Los métodos write() de OutputStream son
similares:
•
write(int c)
•
write(char cadena[])
•
write(char cadena[], int comienzo, int longitud)
Estos métodos no creo que necesiten explicación.
La jerarquía de clases la vemos en las siguientes imágenes. Aquí están separadas por tipo de dato que
manejan, si leen o escriben y las operaciones que realicen con los datos (canales de transmisión en gris
y canales de proceso en el color de fondo).
Canales que manejan caracteres (Figura 154 y Figura 155)
Figura 154
349
Programación en Java
© Grupo EIDOS
Figura 155
Canales que manejan bytes (Figura 156 y Figura 157).
Figura 156
Figura 157
Pasemos, ahora, a explicar los distintos tipos de canales.
Canales de transmisión (Data Sink Streams)
Los canales de transmisión se usan para leer o escribir cadenas de caracteres, ficheros o pipes. Los
pipes son un tipo de canal especial que veremos luego. En la Tabla 24 se muestran los canales de
transmisión.
350
© Grupo EIDOS
17. Aspectos avanzados de Java: canales y Sockets
Origen/Destino
Canales de Carácter
Canales de Byte
Memoria
CharArrayReader,
ByteArrayInputStream,
CharArrayWriter
ByteArrayOutputStream
StringReader,
StringBufferInputStream
StringWriter
Pipe
Fichero
PipedReader,
PipedInputStream,
PipedWriter
PipedOutputStream
FileReader,
FileInputStream,
FileWriter
FileOutputStream
Tabla 24
Como podemos ver, por cada canal de carácter hay uno de byte y por cada canal de lectura hay uno de
escritura (con excepción de StringBufferInputStream).
Los canales que escriben o leen de la memoria se crean sobre un array o una cadena existente.
Para ver como funcionan los canales que trabajan con ficheros, llamados canales de fichero, veamos el
Código fuente 246 que los copia.
import java.io.*;
public class CopiaFichero{
public static void main(String args[]) throws IOException{
FileReader entrada = new FileReader("entrada.txt");
FileWriter salida = new FileWriter("salida.txt");
int caracter;
while ((caracter=entrada.read()) != -1)
salida.write(caracter);
entrada.close();
salida.close();
}
}
Código fuente 246
Este programa va a leer el fichero ENTRADA.TXT y lo va a copiar en SALIDA.TXT. ENTRADA.TXT
debe existir y puede ser cualquier fichero de texto (un TXT, un BAT, etc...).
La lectura del fichero de entrada finaliza cuando se ha alcanzado el final del fichero, esto se identifica
mediante el valor –1.
Aquí vemos que el uso de los métodos read() y write() no depende de que el canal sea de una clase o
de otra. Si leyéramos un String de la memoria y lo escribiéramos en el disco, sólo tendríamos que
cambiar el canal, no el método de lectura. Cambiando el Código fuente 247.
351
Programación en Java
© Grupo EIDOS
FileReader entrada = new FileReader("entrada.txt");
Código fuente 247
Por el Código fuente 248.
String cadena="Esta cadena es un ejemplo";
StringReader entrada = new StringReader(cadena);
Código fuente 248
Tendremos un programa que lee de la memoria y escribe en un fichero. Y sólo hemos cambiado el
canal.
Los canales pipe son un tipo especial algo complejo que se usa para pasar información de un hilo de
ejecución a otro controlando toda la sincronización automáticamente. Para usar los pipe hay que crear
uno de entrada y otro de salida y relacionarlos, como muestra el Código fuente 249
PipedInputStream entrada = new PipedInputStream();
PipedOutputStream salida = new PipedOutputStream(entrada);
Código fuente 249
De estos canales no vamos a ver ningún ejemplo porque son bastante complejos y se sale de las
expectativas del curso.
Canales de proceso (Processing Streams)
Los canales de proceso realizan alguna operación con los datos antes de recuperar o de introducir los
datos en el canal. Estos canales actúan sobre un canal de transmisión que les proporciona los datos.
Proceso
Canales de Carácter
Canales de Byte
Buffering
BufferedReader,
BufferedInputStream,
BufferedWriter
BufferedOutputStream
Filtrado
Conversión entre
Bytes y Caracteres
Unión de Canales,
concatenación
352
FilterReader,
FilterInputStream,
FilterWriter
FilterOutputStream
InputStreamReader,
OutputStreamWriter
SequenceInputStream
© Grupo EIDOS
17. Aspectos avanzados de Java: canales y Sockets
Serialización de
Objetos
ObjectInputStream,
ObjectOutputStream
Conversión de Tipos
DataInputStream,
DataOutputStream
Contador de Líneas
LineNumberReader
Vuelta Atrás
PushbackInputStream
Impresión
PrintWriter
LineNumberInputStream
PrintStream
Tabla 25
Estos canales, al crearse a partir de uno de transmisión, se usan sobre todo para optimización de
accesos o para añadir funcionalidades extra.
Vamos a comentar estos canales.
Canales de Filtrado
Son unas clases abstractas de las que heredan los canales de buffering, conversión de tipos, contador
de líneas, vuelta atrás e impresión.
Canales de buffer
Son uno de los tipos de canales más importantes, ya que optimizan el acceso a los datos guardándolos
en memoria, limitando así los accesos a la fuente de los mismos. Son los únicos canales que utilizan
adecuadamente los métodos mark() y reset() de la superclase InputStream, el método mark() marca
una posición en el canal y el método reset() vuelve a ella aunque nos hayamos movido.
Vamos a ver un ejemplo que usa un BufferedReader para leer el código HTML de una página Web y
un FileWriter para guardarla en disco. Por cierto, esta página se puede abrir en el explorador.
import java.net.*; // En este paquete está la definición de la URL
import java.io.*;
public class LeerUrl{
public static void main(String args[]) throws Exception{
URL eidos = new URL("http://www.eidos.es/");
FileWriter salida = new FileWriter("salida.htm");
BufferedReader entrada = new BufferedReader
(new InputStreamReader (eidos.openStream()));
int caracter;
while ((caracter=entrada.read()) != -1)
salida.write(caracter);
entrada.close();
salida.close();
}
}
Código fuente 250
353
Programación en Java
© Grupo EIDOS
La forma de usar los canales de proceso es a partir de un canal de transmisión, pero en este caso
estamos usando InputStreamReader, que hemos dicho que es un canal de proceso. Esto es porque
InputStreamReader proporciona datos: es un canal como los de transmisión pero que pasa los bytes a
caracteres al leerlos y convierte los caracteres en bytes al escribirlos. Es el único canal que se
comporta como uno de transmisión.
También se pueden anidar canales. Por ejemplo, definir un canal PrintWriter a partir de entrada, que es
un canal BufferedReader creado a partir de un InputStreamReader. Con esto conseguimos la
funcionalidad de los tres canales en uno.
Este ejemplo funcionaría igual si no usáramos BufferedReader, pero el acceso al canal es mejor
usándolo. Además, los canales de buffer se van a usar mayoritariamente para optimizar las
operaciones de lectura y escritura, no porque sean estrictamente necesarios.
También podemos apreciar que la forma de leer es siempre la misma aunque cambiemos de canal.
Este programa está hecho con canales de carácter, pero se puede hacer igualmente con canales de byte:
BufferdedInputStream y FileOutputStream.
Canal de Concatenación
El canal de concatenación une varios canales en uno sólo. Como parámetros acepta dos canales de
transmisión del tipo InputStream, o bien una lista de estos del tipo Enumeration, que es un interfaz
especial para hacer listas de datos. Vamos a ver un ejemplo, en el Código fuente 251, de
concatenación de dos canales.
import java.io.*;
public class UnirFicheros{
public static void main(String args[]) throws IOException{
FileInputStream entrada1 = new FileInputStream("hola.txt");
FileInputStream entrada2 = new FileInputStream("adios.txt");
SequenceInputStream ficheros =
new SequenceInputStream(entrada1, entrada2);
int c;
while ((c = ficheros.read()) != -1)
System.out.write(c);
ficheros.close();
}
}
Código fuente 251
Este programa abre dos canales que leen de los ficheros de texto HOLA.TXT y ADIOS.TXT, y los une
en un solo canal SequenceInputStream cuyo contenido se muestra en la pantalla.
Canales de conversión de tipos
Hasta ahora hemos leído los datos como carácter o byte. Para leer o escribir datos de otros tipos
tenemos los canales DataInputStream y DataOutputStream con los que podemos grabar datos de tipo
boolean, short, int, long, float y double, además de char y byte.
354
© Grupo EIDOS
17. Aspectos avanzados de Java: canales y Sockets
Para comprobar su funcionamiento, tenemos este ejemplo que graba un fichero con datos de tipo int y
String, separados por un tabulador. Luego leemos de ese fichero, sacando la información por pantalla
y haciendo un total de uno de sus campos.
import java.io.*;
public class PruebaTipos{
public static void main(String[] args) throws IOException{
PruebaTipos obj = new PruebaTipos();
obj.escribeDatos();
obj.leeDatos();
}
public void escribeDatos() throws IOException{
DataOutputStream salida = new DataOutputStream(
new FileOutputStream("datos.dat"));
int[] precios = { 3000, 5000, 7000, 6500, 750 };
int[] unidades = { 12, 8, 13, 29, 50 };
String[] descripcion = { "Camisetas","Bañadores","Pantalones",
"Zapatillas","Calcetines" };
for (int i = 0; i < precios.length; i ++){
salida.writeInt(precios[i]);
salida.writeChar('\t');
salida.writeInt(unidades[i]);
salida.writeChar('\t');
salida.writeBytes(descripcion[i]);
salida.writeChar('\n');
}
salida.close();
}
public void leeDatos() throws IOException{
DataInputStream entrada = new DataInputStream(
new FileInputStream("datos.dat"));
int precio;
int unidad;
String desc;
int total = 0;
try{
while (true){
precio = entrada.readInt();
entrada.readChar(); // Ignoramos el tabulador
unidad = entrada.readInt();
entrada.readChar(); // Ignoramos el tabulador
desc = entrada.readLine();
System.out.println("Has pedido " +
unidad + " unidades de " +
desc + " a " + precio + " pts.");
total = total + unidad * precio;
}
}
catch (EOFException e) {}
System.out.println("Por un total de: " + total + " Pts.");
entrada.close();
}
}
Código fuente 252
Lo primero que hacemos es crearnos un canal de salida capaz de tratar tipos a partir de un canal de
fichero. Una vez que definimos los datos, los grabamos en columnas (separando los elementos por
tabuladores). Creamos el fichero en disco y lo leemos. Si observamos datos.dat veremos que no
distinguimos las cantidades: ya no las guarda como caracteres o bytes reconocibles.
355
Programación en Java
© Grupo EIDOS
El segundo método de la clase, crea un canal de entrada para leer tipos a partir de un fichero.
Definimos los campos que contiene el archivo y los leemos ignorando el tabulador que los separa (lo
leemos sin hacer nada con él).
En el bucle de lectura encontramos algo nuevo: ya no leemos hasta encontrar el final de los datos, sino
que tenemos el código de lectura en un bucle infinito dentro de un bloque try-catch. Esto se hace así
porque tenemos que leer varios datos diferentes. En este caso no podríamos repetir el bucle de lectura
que hemos estado usando siempre, ya que no podemos asignar el resultado de una lectura a un solo
dato. En todos los casos hemos estado usando el Código fuente 253.
while ((c = ficheros.read()) != -1)
Código fuente 253
Con un canal de tipos, no podemos recoger la lectura en una sola variable puesto que tenemos que leer
varias (en nuestro caso el precio, la descripción y la cantidad). La manera de poder leerlo todo es
poner el proceso de lectura en un bucle infinito dentro de un bloque try-catch, del que sólo se saldrá
cuando se alcance el final del fichero y se lance la excepción de fin de fichero que recogemos sin hacer
nada.
Si compilamos este ejemplo, el compilador nos dará el siguiente mensaje: String readLine() ha sido
desaprobado por el autor de java.io.DataInputStream. Este mensaje indica que no es recomendable
utilizar este método ya que se ha quedado obsoleto, nosotros lo hemos utilizado y dejado en nuestro
código por simplicidad, ya que sino tendríamos que utilizar un canal más del tipo BufferedReader.
En la Figura 158 se puede observar la ejecución de este ejemplo.
Figura 158
Serialización de Objetos
Ya hemos visto cómo escribir o leer tipos de datos en un canal, pero entre esos tipos no están los
objetos. La serialización de objetos consiste en poder enviar objetos a un canal y luego reconstruir el
objeto y el valor de sus atributos en el otro extremo del canal. Para reconstruir un objeto se tiene que
importar el paquete donde está definido en el destino.
Para ver cómo funciona, tenemos el Código fuente 254 que escribe un objeto de tipo Date en un canal
y luego lo recupera mostrándonos los valores que tenía cuando se creó.
356
© Grupo EIDOS
17. Aspectos avanzados de Java: canales y Sockets
import java.io.*;
import java.util.Date;
public class Serializacion{
public static void main(String args[]) throws Exception{
FileOutputStream fichero = new FileOutputStream("salida.dat");
ObjectOutput salida = new ObjectOutputStream(fichero);
salida.writeObject("Hoy");
salida.writeObject(new Date());
salida.flush();
salida.close();
while (System.in.read() != 13);
FileInputStream fichero1 = new FileInputStream("salida.dat");
ObjectInputStream entrada = new ObjectInputStream(fichero1);
String hoy = (String)entrada.readObject();
Date fecha = (Date)entrada.readObject();
System.out.println(hoy);
System.out.println(fecha);
entrada.close();
}
}
Código fuente 254
En la primera parte, creamos un canal de serialización a partir de uno de fichero. Podemos definir el
tipo del canal como ObjectOutput ya que es una superclase de ObjectOutputStream, aunque también
podríamos haber puesto directamente ObjectOutputStream, como haremos con el canal de entrada.
Luego escribimos dos objetos: uno de la clase String y otro de la clase Date. Cuando llamamos al
constructor de Date sin parámetros, nos crea un objeto con la fecha y la hora actuales. Una vez
escritos, lo guardamos en disco y cerramos el canal.
Esperamos a que se pulse la tecla ENTER para apreciar el cambio de hora desde que se creó el objeto
Date hasta que mostramos su valor.
La segunda parte es muy parecida a la primera: creamos un canal de lectura a partir de uno de fichero.
Para leer los objetos usamos el método readObject() de la clase ObjectInputStream. Tenemos que
definir un objeto del tipo que vamos a leer y hacer un casting a ese tipo en el readObject(). Los objetos
se leen en el orden en que fueron escritos. Una vez leídos, los mostramos comprobando que la hora
que vemos es la de creación.
Nosotros también podemos serializar nuestras propias clases sólo con que estas implementen el
interfaz Serializable. Afortunadamente, este interfaz está vacío y sólo actúa a modo de etiqueta.
public class MiClase implements Serializable
Código fuente 255
Solamente con esta línea, todos los objetos de esta clase serán serializables.
Canal Contador de Líneas
En Java hay un canal especial que reconoce los números de línea: LineNumberReader. Este canal sólo
se debe usar en canales de carácter. Java mantiene LineNumberInputStream por compatibilidad con
357
Programación en Java
© Grupo EIDOS
versiones anteriores. LineNumberInputStream no se debe usar porque un canal de bytes puede tener
cualquier tipo de dato y no podemos dar por supuesto que vaya a llevar sólo caracteres. Este canal es
muy sencillo de usar, como vamos a ver en el Código fuente 256.
import java.io.*;
public class ContarLineas{
public static void main(String args[]) throws IOException{
FileReader fichero = new FileReader("entrada.txt");
LineNumberReader entrada = new LineNumberReader(fichero);
String s;
while ((s=entrada.readLine())!=null){
System.out.print("Linea "+(entrada.getLineNumber())+": ");
System.out.print(s);
System.out.println();
}
}
}
Código fuente 256
La creación del canal es como la de todos los de proceso. ENTRADA.TXT debe existir y es un fichero
de texto. Para leer usamos el método ReadLine() que tienen todos los canales de entrada. El método de
LineNumberReader que nos proporciona el número de línea en el que estamos es getLineNumber().
Leemos hasta que la cadena que devuelve readLine() sea nula (aquí no se alcanza el final de fichero).
Un ejemplo de ejecución del Código fuente 256 se puede observar en la Figura 159.
Figura 159
Canales de impresión
Estos canales sólo proporcionan métodos de impresión a otros canales. Nos permiten mostrar los datos
en una gran cantidad de formatos. Son unos canales muy sencillos de usar, por lo que a menudo se
verá un canal de impresión creado a partir de cualquier otro canal para facilitar su uso. Sus métodos
principales son print() y println(), que están sobrecargados para soportar cualquier tipo de dato. Estos
métodos son los que usan System.out y System.err.
358
© Grupo EIDOS
17. Aspectos avanzados de Java: canales y Sockets
Canales de vuelta atrás
Este tipo de canal implementa la posibilidad de retroceder en la lectura de un canal. Hasta ahora los
canales que hemos visto se tratan con avances secuenciales, exceptuando el uso de los métodos mark()
y reset(), que sólo funcionan bien en los canales con buffer. Con PushbackReader y
PushbackInputStream podemos retroceder tantos caracteres o bytes como podamos meter en el buffer
de PushbackReader y PushbackInputStream. Esto significa que podemos leer datos varias veces, ya
que después de retroceder, leeremos otra vez los mismos datos.
Como lenguaje orientado a objetos que es Java, hemos visto cómo separa todos los elementos a la hora
de tratar con datos de fuentes externas. Esto le proporciona una potencia considerable a la hora de
añadir nuevas funcionalidades o nuevos dispositivos de lectura o escritura: en definitiva, nuevos
canales.
El uso de canales está bastante restringido en los applets, ya que desde éstos no podemos acceder a
ningún fichero que esté en la máquina del usuario, y no podemos cargar ni ejecutar ningún programa.
Por lo tanto, todos estos apartados dedicados a los canales están pensados para aplicaciones, así mismo
se refleja en los ejemplos que se adjuntan.
Aplicaciones cliente/servidor en Java
Dentro del entorno de las aplicaciones Java vamos a tratar un tema de gran interés: la arquitectura
cliente/servidor. Más concretamente se va a comentar dentro de este apartado las herramientas que
ofrece Java para poder realizar aplicaciones dentro del entorno de la arquitectura cliente/servidor.
Las comunicaciones en Internet se realizan a través de un protocolo llamado TCP/IP ( Transmission
Control Protocol/Internet Protocol). TCP/IP es un conjunto de protocolos de comunicaciones que
permite a diferentes máquinas conectadas a Internet comunicarse entre sí. El protocolo TCP/IP ofrece
comunicaciones fiables mediante servicios orientados a la conexión (protocolo TCP) y no fiables a
través de servicios no orientados a la conexión (protocolo UDP, User Datagram Protocol).
Un servicio orientado a la conexión significa que permite intercambiar un gran volumen de datos de
una manera correcta, es decir, se asegura que los datos llegan en el orden en el que se mandaron y no
existen duplicados, además tiene mecanismos que le permiten recuperarse ante errores.
Las comunicaciones en Internet utilizando el protocolo TCP/IP se realizan a través de circuitos
virtuales de datos llamados sockets. Un socket básicamente es una "tubería" que se crea para
comunicar a dos programas. Cada programa posee un extremo de la tubería. La clase Socket del
paquete java.net provee una implementación independiente de la plataforma del lado cliente de una
conexión entre un programa cliente y un programa servidor a través de un socket. El lado del servidor
es implementado por la clase ServerSocket.
Por lo tanto para iniciar la conexión desde el lado del cliente se debe instanciar un objeto de la clase
Socket, el constructor utilizado de esta clase tiene como parámetros la dirección IP (Internet Protocol)
o el nombre de la máquina a la que se quiere conectar y el número de puerto en el que el servidor está
esperando las peticiones de los clientes.
Una dirección IP es un número de 32 bits para identificar de forma única una máquina conectada a
Internet. Se divide en grupos de 8 bits y se identifica con su número en notación decimal separado
cada uno de ellos por puntos, a este formato se le denomina tétrada punteada. Java permite manejar
direcciones IP a través de la clase InetAddress, que se encuentra también en el paquete java.net.
359
Programación en Java
© Grupo EIDOS
Debido a que recordar direcciones IP puede ser difícil y poco manejable se suelen identificar con
nombres de máquinas, así la dirección IP 206.26.48.100 se corresponde con el nombre de la máquina
java.sun.com que resulta más fácil de recordar y significativo. Un mismo nombre puede tener
diferentes direcciones IP, en Internet esta correspondencia entre direcciones IP y nombres la gestionan
servidores de nombres que se encargan de traducir los nombres fáciles de recordar a sus direcciones de
32 bits. En un programa Java para obtener el nombre de una dirección IP se utiliza el método
getHostName() de la clase InetAddress.
Una vez comentado el concepto de dirección IP vamos a retomar la explicación justo en el punto en el
que se había dejado. Después de crear el socket, a continuación se deberán obtener los canales de
entrada y de salida del socket. Los canales los acabamos de comentar en apartados anteriores.
Para obtener los canales de entrada y de salida del socket se utilizan los métodos getInputStream() y
getOutputStream() de la clase Socket, una vez que se tienen los canales, el de entrada lo podremos
tratar, por ejemplo, como un objeto de la clase DataInputStream y el de salida como un objeto de la
clase PrintStream.
Cuando ya disponemos de los canales de entrada y salida del socket, ya estamos en disposición de
poder comunicarnos con el servidor, es decir, la aplicación Java que posee el servicio que queremos
utilizar y que tiene el socket del lado del servidor. De todas formas, el servidor se comentará más
adelante dentro de este mismo apartado.
Si lo que queremos es recibir información desde el servidor a través del socket creado, se leerá del
flujo de entrada del socket. Para ello se entra en un bucle del tipo mientras cuya condición es
"mientras se siga recibiendo información a través del flujo de entrada del socket". El cuerpo del bucle
mientras se encargará de tratar esta información de forma adecuada y utilizarla para la tarea que
resulte necesaria.
Si deseamos enviar información al servidor lo haremos a través del flujo de salida del socket,
escribiendo en él la información deseada.
Todo el proceso de comunicaciones se encuentra encerrado en un bloque try{...}catch(...){...} para
atrapar los errores que se produzcan. En este caso se deberán atrapar excepciones de entrada/salida, es
decir, IOException.
Una vez que se ha terminado el proceso de comunicación entre la aplicación cliente y el servidor, se
procede a cerrar los canales de entrada y salida del socket, y también el propio socket (siempre se debe
hacer en este orden).
El siguiente esquema que se muestra es el que suelen presentar los clientes en general:
•
Abrir un socket.
•
Abrir el canal de entrada y el canal de salida del socket.
•
Leer del canal de entrada y escribir en el canal de salida, atendiendo al protocolo del servidor.
•
Cerrar los canales.
•
Cerrar el socket.
El tercer paso es el que más suele variar de un cliente a otro, dependiendo del servidor, los demás
suelen ser iguales.
360
© Grupo EIDOS
17. Aspectos avanzados de Java: canales y Sockets
Para construir la segunda aplicación implicada en este proceso de comunicación, es decir, la aplicación
que realiza la función de servidor, se deberá instanciar un objeto de la clase ServerSocket. Esta es,
como ya se había indicado anteriormente, una clase del paquete java.net que provee una
implementación independiente de la plataforma del lado servidor de una conexión cliente/servidor a
través de un socket.
El constructor del socket del servidor, ServerSocket(), necesita como parámetro un número de puerto
en el que debe ponerse a escuchar las peticiones de los clientes, es decir, mediante este número de
puerto se identificará el servicio que se debe prestar. Si el puerto especificado está ya ocupado se
lanzará una excepción a la hora de crear el socket de servidor.
Cuando se ha creado el socket de servidor a continuación se lanza sobre este socket el método accept()
de la clase ServerSocket.
El método accept() del socket del servidor se bloquea (espera) hasta que un cliente inicia una petición
de conexión en el puerto en el que el servidor está escuchando.
Cuando el método accept() establece con éxito una conexión con el cliente devuelve un nuevo objeto
de la clase Socket al que se le asigna un nuevo puerto local, dejando libre el puerto en el que el
servidor espera las peticiones de los clientes.
Este socket se utiliza como parámetro para el constructor de la clase encargada de tratar al cliente. Esta
clase que implementa el interfaz Runnable, posee un objeto representa un hilo de ejecución paralelo
que es el verdadero encargado de servir al cliente, es decir, es un objeto de la clase Thread.
Múltiples peticiones de los clientes pueden llegar al mismo puerto. Estas peticiones de conexión se
encolan en el puerto, de esta forma el servidor debe aceptar las conexiones secuencialmente. Sin
embargo los clientes pueden ser servidos simultáneamente a través del uso de hilos de ejecución. Un
hilo para procesar cada una de las conexiones de los clientes.
Para cada uno de los clientes que se conectan al servidor se instancia un objeto que implementa el
interfaz Runnable y que inicia un nuevo hilo de ejecución a través de un objeto de la clase Thread.
Una vez que el servidor ha instanciado un hilo de ejecución para tratar al cliente que se acaba de
conectar, vuelve a escuchar en el mismo puerto en el que estaba anteriormente esperando la llegada de
conexiones de otros clientes. De esta forma mientras el objeto de la clase Thread se ocupa de servir a
un cliente, el servidor puede a la misma vez seguir esperando conexiones de otros clientes, ya que se
trata de hilos de ejecución paralelos. Un esquema general que suelen tener los servidores es el
siguiente:
mientras (true) {
aceptar una conexión.
crear un hilo de ejecución que se encargue de servir al cliente.
}
En el caso de que exista algún error en las aceptaciones de conexión de los clientes se saldrá del bucle
mientras y se cerrará el socket del servidor, y se deberá notificar este error.
La clase que se encarga de servir al cliente, como ya se ha mencionado anteriormente, deberá
implementar el interfaz Runnable, por lo tanto deberá implementar el método run() de este interfaz.
Dentro del método run() se implementa el protocolo de comunicación que existe entre el cliente y el
servidor.
Dentro de este método podremos obtener los canales de entrada y de salida del socket en el que está
conectado el cliente, para ello utiliza los métodos getInputStream() y getOutputStream() de la clase
Socket. En este momento se inicia la comunicación con el cliente, atendiendo al protocolo de
361
Programación en Java
© Grupo EIDOS
comunicaciones determinado se escribirá en el flujo de salida o se leerá del flujo de entrada del socket
la información necesaria, ya sean caracteres o bytes. Una vez servido por completo el cliente se
cerrarán el socket y se finalizará la ejecución del hilo.
Si se ha produce algún error en el método run() se dará por terminada la comunicación con el cliente,
se indicará el error a las aplicaciones cliente y servidor y se dispondrá a cerrar los canales de entrada y
de salida y el socket, y finalmente se detendrá la ejecución del hilo.
En este apartado hemos visto de forma general la arquitectura cliente/servidor dentro del lenguaje
Java, se han visto unos esquemas genéricos que podrán ser utilizados para cualquier aplicación
cliente/servidor. Pero para que no quede todo en mera teoría que suele ser un poco aburrida y fácil de
olvidar, en el siguiente apartado vamos a ver un ejemplo sencillo.
Este ejemplo se puede considerar como un pequeño resumen de todo lo visto en estos dos últimos
capítulos, ya que vamos a utilizar multihilos, canales de entrada/salida y sockets. Además se utiliza un
applet, una aplicación y también existe tratamiento de eventos, por lo tanto podemos ir un poco más
allá y considerar que es un ejemplo sencillo que resume de forma escueta todo el presente curso.
Nuestro ejemplo va estar constituido por un cliente y un servidor, el cliente va a ser un applet y el
servidor una aplicación. El cliente va a realizar una petición muy sencilla, (que se puede considerar
absurda, pero para el ejemplo nos sirve) la hora y fecha del servidor.
El applet va a tener una caja de texto en la que va a mostrar la hora y fecha que le devuelva la
aplicación servidor. También ofrece un botón, la pulsación de este botón implica una petición del
cliente al servidor.
En al método actionPerformed() se establece la conexión con el servidor y se obtiene el canal de
entrada, del que se obtendrá la fecha y hora. El código del applet cliente es como se muestra en el
Código fuente 257.
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
public class Cliente extends Applet implements ActionListener{
private Button boton;
private TextField resultado;
//canal de entrada
private BufferedReader entrada;
//socket del cliente
private Socket socket;
public void init(){
resultado=new TextField(40);
add(resultado);
boton=new Button("Obtener Fecha");
add(boton);
boton.addActionListener(this);
}
public void actionPerformed(ActionEvent evento){
try{
//creamos el socket y nos conectamos al servidor
socket=new Socket(InetAddress.getLocalHost(),6789);
//obtenemos el canal de entrada
entrada=new BufferedReader(
new InputStreamReader(socket.getInputStream()));
resultado.setText(entrada.readLine());
//se cierran los canales y el socket
362
© Grupo EIDOS
17. Aspectos avanzados de Java: canales y Sockets
entrada.close();
socket.close();
}catch(UnknownHostException ex){
resultado.setText("Error al establecer la conexión: "+ex);
}catch(IOException ex){
resultado.setText("Error de E/S: "+ex);
}
}
}
Código fuente 257
El código es bastante sencillo y respeta el esquema que comentamos para los clientes genéricos, pero
en este caso no se utiliza ningún canal de salida, ya que no enviamos ningún dato a la aplicación
servidor. Para leer del canal de entrada se utiliza el método readLine() de la clase BufferedReader.
La aplicación servidor consta de dos clases en dos ficheros fuente distintos. La primera clase llamada
Servidor es el servidor propiamente dicho, y su función es la de crear un objeto ServerSocket en el
puerto 6789. A este servidor se conecta el applet anteriormente comentado. El servidor estará a la
escucha para saber cuando se ha conectado un cliente, y cuando se conecta se crea una instancia de la
segunda clase de esta aplicación.
La clase Servidor hereda de la clase Frame para dar una representación gráfica a nuestra aplicación.
Además en la ventana se muestra el número de clientes que se han servidor hasta el momento, cada
vez que pulsemos el botón del applet se creará una nueva petición. El código de esta clase se muestra
en el Código fuente 258.
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
public class Servidor extends Frame{
private ServerSocket sock;
private Label servidos;
public static void main(String args[]){
Socket s=null;
Servidor serv=new Servidor();
//el servidor se pone a escuchar en el socket
//que ha creado
boolean escuchando=true;
int numCliente=0;
while(escuchando){
//espera hasta que un cliente comienza una conexión
//en el puerto especificado
try {
s=serv.sock.accept();
numCliente++;
serv.servidos.setText("Clientes Servidos= "+numCliente);
}catch (IOException e) {
System.out.println("Fallo en la aceptación de llamadas: "+
e.getMessage());
//fuerza la salida del while
continue;
}
//se crea un objeto de la clase que va a tratar la cliente
new TrataCliente(s,numCliente);
}
//se cierra el socket
try{
serv.sock.close();
363
Programación en Java
© Grupo EIDOS
}catch(IOException err){
System.out.println("Error al cerrar el socket");
System.exit(-1);
}
//se cierra la ventana del servidor
serv.dispose();
System.exit(0);
}
//constructor
public Servidor(){
super(" Ejemplo Servidor");
//se crea el socket del servidor en un puerto que no esté ocupado
try{
sock=new ServerSocket(6789);
}catch(IOException err){
System.out.println("Error:\n Problemas al crear el socket: "+err);
System.exit(-1);
}
setSize(200,110);
show();
addWindowListener(new AdaptadorVentana());
servidos=new Label("Clientes Servidos=0");
setLayout(new BorderLayout());
add("Center",servidos);
System.out.println("Iniciado el servidor en el puerto: "
+sock.getLocalPort());
}
class AdaptadorVentana extends WindowAdapter{
public void windowClosing(WindowEvent evento){
try{
sock.close();
}catch(IOException ex){
System.out.println("Error al cerrar el socket: "+ex);
}
dispose();
System.out.println("Apagando el servidor...");
System.exit(0);
}
}
}
Código fuente 258
La segunda clase de la aplicación se llama TrataCliente y tiene como función servir al cliente que se
ha conectado y realizado la petición, es decir, es la clase que calcula la hora del servidor y se la envía
por el canal de salida correspondiente al cliente que se encuentre conectado.
La clase TrataCliente implementa el interfaz Runnable por lo que contiene el método run(), es
precisamente en este método el lugar en el que se va a tratar la petición del cliente. Por cada cliente
conectado se tendrá un hilo de ejecución distinto, y este se creará a través del atributo hilo, que
pertenece a la clase Thread.
En el método run() se crea un canal de salida de la clase PrintWriter, a partir del canal de salida del
socket establecido entre el cliente y el servidor, para enviar la fecha y hora al cliente que haya
realizado la petición. Su código completo es el que vemos en el Código fuente 259.
import
import
import
import
364
java.io.*;
java.awt.*;
java.net.*;
java.util.*;
© Grupo EIDOS
17. Aspectos avanzados de Java: canales y Sockets
public class TrataCliente implements Runnable{
//socket creado para comunicarse con el cliente
private Socket sock=null;
//Flujo de salida del socket por el que se envía
//la fecha y hora actual al cliente
private PrintWriter salida;
//Hilo de ejecución
private Thread hilo=null;
//constructor que crea el hilo y lo ejecuta
public TrataCliente(Socket s, int id){
sock=s;
hilo=new Thread(this,"Cliente "+id);
hilo.start();
System.out.println("Iniciado el proceso: "+hilo.getName());
}
public void run(){
try{
//es el canal de salida del socket
// lo que debe leer el cliente
salida=new PrintWriter(sock.getOutputStream());
salida.println(new Date());
}catch(Exception e){
System.out.println("Error en la transmisión del fichero");
System.exit(-1);
}
finalizar();
}
public void finalizar() {
//se cierra el canal de salida y el socket
try{
salida.close();
sock.close();
}catch(IOException err){
System.out.println("Error al cerrar el socket");
}
System.out.println("Finalizado el proceso: "+hilo.getName());
System.out.println();
//se finaliza y destruye el hilo de ejecución
hilo.stop();
hilo=null;
}
}
Código fuente 259
Un ejemplo de una ejecución de este último ejemplo es el que se puede apreciar en la figura 10.
Figura 160
365
Programación en Java
© Grupo EIDOS
Para probar correctamente este ejemplo, la aplicación servidor y el applet cliente se deben encontrar en
la misma máquina, es decir, ambos se deben hallar en el mismo servidor Web.
366
Si quiere ver más textos en este formato, visítenos en: http://www.lalibreriadigital.com.
Este libro tiene soporte de formación virtual a través de Internet, con un profesor a su
disposición, tutorías, exámenes y un completo plan formativo con otros textos. Si desea
inscribirse en alguno de nuestros cursos o más información visite nuestro campus virtual en:
http://www.almagesto.com.
Si quiere información más precisa de las nuevas técnicas de programación puede suscribirse
gratuitamente a nuestra revista Algoritmo en: http://www.algoritmodigital.com. No deje de
visitar nuestra reviata Alquimia en http://www.eidos.es/alquimia donde podrá encontrar
artículos sobre tecnologías de la sociedad del conocimiento.
Si quiere hacer algún comentario, sugerencia, o tiene cualquier tipo de problema, envíelo a la
dirección de correo electrónico lalibreriadigital@eidos.es.
© Grupo EIDOS
http://www.eidos.es
Descargar