You are on page 1of 549

PRÓLOGO

Introducción

Java a través de ejemplos presenta los conceptos básicos del lenguaje de


programación Java y muestra sus posibilidades más relevantes para el desarrollo de
aplicaciones. El libro se centra en la explicación detallada de los fundamentos del
lenguaje, evitando los temas de carácter especializado.

La didáctica de esta publicación se basa en el aprendizaje guiado mediante


ejemplos comentados y gráficos de apoyo a las explicaciones, evitándose las
aclaraciones puramente teóricas y el uso de un nivel de detalle excesivo en la
información suministrada.

Este libro, por tanto, resulta especialmente adecuado para los lectores que no
poseen unos conocimientos previos del lenguaje o para aquellos cuyo principal
objetivo es adquirir los fundamentos de Java para, posteriormente, poder afrontar
con una base sólida el aprendizaje de aspectos específicos tales como las
comunicaciones, acceso a bases de datos, presentaciones multimedia, etc.

Se incluyen dos apéndices con más de 500 preguntas y sus respuestas


basadas en los conceptos fundamentales de Java. Estas cuestiones permiten que el
lector pueda comprobar la corrección y completitud con los que va asimilando las
diferentes materias.

Los contenidos del libro se han dividido en 9 capítulos que contienen 38


lecciones; cada una de estas lecciones forma una unidad de estudio que conviene
afrontar de manera individualizada. Para facilitar la comprensión de estas unidades,
las lecciones se han diseñado con un tamaño reducido: de unas 8 páginas por
término medio.

En el siguiente apartado se presenta una relación de las lecciones, agrupadas


en capítulos y con la referencia del número de página donde comienza cada una.
XIV JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

Lecciones incluidas en la publicación

Capítulo 1: toma de contacto, variables, tipos de datos y operadores


LECCIÓN 1: Instalación del entorno de desarrollo
LECCIÓN 2: Primer programa en java: “Hola mundo”
LECCIÓN 3: Variables y tipos de datos
LECCIÓN 4: Operadores

Capítulo 2: estructuras de control


LECCIÓN 5: El bucle FOR
LECCIÓN 6: El bucle WHILE
LECCIÓN 7: La instrucción condicional IF
LECCIÓN 8: La instrucción condicional SWITCH
LECCIÓN 9: Ejemplos

Capítulo 3: métodos y estructuras de datos


LECCIÓN 10: Métodos
LECCIÓN 11: Strings
LECCIÓN 12: Matrices (arrays, vectores)
LECCIÓN 13: Ejemplos de programación

Capítulo 4: programación orientada a objetos usando clases


LECCIÓN 14: Definición de clases e instancias
LECCIÓN 15: Sobrecarga de métodos y constructores
LECCIÓN 16: Ejemplos
LECCIÓN 17: Clases utilizadas como Parámetros
LECCIÓN 18: Propiedades y métodos de clase y de instancia
LECCIÓN 19: Paquetes y atributos de acceso
LECCIÓN 20: Ejemplo: máquina expendedora

Capítulo 5: programación orientada a objetos usando herencia


LECCIÓN 21: Herencia
LECCIÓN 22: Ejemplos
LECCIÓN 23: Polimorfismo
LECCIÓN 24: Clases abstractas e interfaces

Capítulo 6: excepciones
LECCIÓN 25: Excepciones predefinidas
LECCIÓN 26: Excepciones definidas por el programador

Capítulo 7: interfaz gráfico de usuario


LECCIÓN 27: Creación de ventanas
LECCIÓN 28: Paneles y objetos de disposición (layouts)
LECCIÓN 29: Etiquetas, campos y áreas de texto
LECCIÓN 30: Cajas de verificación, botones de radio y listas
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) PRÓLOGO XV

LECCIÓN 31: Diseño de formularios


LECCIÓN 32: Diálogos y menús

Capítulo 8: eventos
LECCIÓN 33: Mecanismo de eventos en java
LECCIÓN 34: Eventos de ratón y de movimiento de ratón
LECCIÓN 35: Eventos de teclado y de ventana
LECCIÓN 36: Eventos de acción, enfoque y elemento

Capítulo 9: aplicaciones de ejemplo


LECCIÓN 37: Ejemplo: calculadora
LECCIÓN 38: Ejemplo: editor

Planificación en el estudio de las lecciones

05 06

01 02 03 04 07 09 10

08

11
13 14 15 16 17 18 19
12

20 21 22 23 24 25 26 27 28

29 36
31 32 33 34 37 38
30 36
XVI JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

El diagrama presentado en la página anterior nos muestra la secuencia de las


lecciones contenidas en el libro. El estudio de las lecciones que presentan fondo de
color blanco es importante para conseguir un nivel adecuado de conocimientos en el
lenguaje Java.

Las lecciones del gráfico que tienen fondo gris pueden ser omitidas en caso
de necesidad; esto es posible debido a que se corresponden con ejercicios de
consolidación de los temas anteriores o porque se refieren a características del
lenguaje que se utilizan con menos frecuencia. En cualquier caso, el estudio de estas
lecciones resulta importante para conseguir una mejor asimilación de los conceptos
explicados, por lo que es muy conveniente acudir a ellas una vez comprendidas las
lecciones que les preceden.

Los cuadrados grises contienen lecciones que pueden ser abordadas en


cualquier orden, por ejemplo, después de la lección 10, podemos estudiar en primer
lugar la 12 y después la 11 o viceversa.

Esperamos que estas indicaciones puedan ayudar a ahorrar tiempo a los


lectores que desean estudiar en primer lugar las características más relevantes del
lenguaje, pudiendo profundizar posteriormente en dichos conceptos y en algún otro
menos utilizado en la programación general.
ÍNDICE

PRÓLOGO ......................................................................................... XIII

CAPÍTULO 1: TOMA DE CONTACTO, VARIABLES, TIPOS DE


DATOS Y OPERADORES ........................................ 1
1.1 Instalación del entorno de desarrollo................................... ........................................1
1.2 Primer programa en java: “Hola mundo”........................................................................5
1.3 Variables y tipos de datos...................................................................................................7
1.3.1 Variables ......................................................................................................................7
1.3.2 Tipos de datos.............................................................................................................9
1.3.3 Tipos numéricos enteros .........................................................................................10
1.3.4 Tipos numéricos decimales ....................................................................................12
1.3.5 Tipo booleano...........................................................................................................13
1.3.6 Tipo carácter .............................................................................................................14
1.3.7 Conversión exp lícita de tipos (Casting)...............................................................15
1.4 Operadores ..........................................................................................................................16
1.4.1 Introducción..............................................................................................................16
1.4.2 Operadores aritméticos............................................................................................17
1.4.3 Operadores lógicos ..................................................................................................18
1.4.4 Operadores de comparación...................................................................................19

CAPÍTULO 2: ESTRUCTURAS DE CONTROL ............................. 21

2.1 El bucle FOR ......................................................................................................................21


2.1.1 Sintaxis .......................................................................................................................21
2.1.2 Ejemplos de aprendizaje ..........................................................................................22
2.1.3 Situaciones erróneas.................................................................................................24
2.1.4 Ejemplos de resolución de problemas...................................................................26
2.2 El bucle WHILE ................................................................................................................29
2.2.1 Sintaxis .......................................................................................................................30
VIII JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (jbobi@eui.upm.es)

2.2.2 Ejemplos de aprendizaje..........................................................................................30


2.2.3 Ejemplos de resolución de problemas...................................................................32
2.3 La instrucción condicional IF..........................................................................................35
2.3.1 Sintaxis .......................................................................................................................35
2.3.2 Ejemplos de aprendizaje..........................................................................................36
2.3.3 If anidados.................................................................................................................37
2.3.4 Situaciones erróneas.................................................................................................38
2.3.5 Ejemplo de resolución de problemas ....................................................................39
2.4 La instrucción condicional SWITCH.............................................................................40
2.4.1 Sintaxis .......................................................................................................................40
2.4.2 Ejemplos de aprendizaje..........................................................................................41
2.4.3 Switch anidados........................................................................................................44
2.4.4 Situaciones erróneas.................................................................................................46
2.4.5 Ejemplo de resolución de problemas ....................................................................47
2.5 Ejemplos..............................................................................................................................49
2.5.1 Cálculo de la hipotenusa de un triángulo .............................................................50
2.5.2 Punto de corte de dos rectas situadas en el espacio bidimensional..................52
2.5.3 Soluciones de una ecuación de segundo grado...................................................55

CAPÍTULO 3: MÉTODOS Y ESTRUCTURAS DE DATOS ......... 57


3.1 Métodos...............................................................................................................................57
3.1.1 Sintaxis ......................................................................................................................58
3.1.2 Ejemplo 1...................................................................................................................59
3.1.3 Resultados del ejemplo 1........................................................................................60
3.1.4 Ejemplo 2...................................................................................................................60
3.1.5 Resultados del ejemplo 2........................................................................................61
3.1.6 Paso de argumentos por valor y por referencia ...................................................61
3.2 Strings..................................................................................................................................62
3.2.1 Sintaxis ......................................................................................................................62
3.2.2 Ejemplo básico..........................................................................................................64
3.2.3 Resultado...................................................................................................................65
3.2.4 Ejemplo de utilización de la clase String .............................................................65
3.2.5 Resultados..................................................................................................................66
3.3 Matrices (arrays, vectores)...............................................................................................66
3.3.1 Sintaxis ......................................................................................................................67
3.3.2 Acceso a los datos de una matriz..........................................................................68
3.3.3 Ejemplo 1...................................................................................................................69
3.3.4 Resultados del ejemplo 1 ........................................................................................70
3.3.5 Ejemplo 2...................................................................................................................70
3.3.6 Resultados del ejemplo 2 ........................................................................................72
3.3.7 Ejemplo ......................................................................................................................72
3.4 Ejemplos de programación...............................................................................................74
3.4.1 Obtención de números primos ...............................................................................74
3.4.2 “Revienta claves”.....................................................................................................76
3.4.3 Estadísticas................................................................................................................78
 JESÚS BOBADILLA SANCHO (jbobi@eui.upm.es) ÍNDICE IX

CAPÍTULO 4: PROGRAMACIÓN ORIENTADA A OBJETOS


USANDO CLASES ...................................................... 83

4.1 Definición de clases e instancias.....................................................................................83


4.1.1 Sintaxis .......................................................................................................................83
4.1.2 Representación gráfica ............................................................................................84
4.1.3 Instancias de una clase.............................................................................................84
4.1.4 Utilización de los métodos y propiedades de una clase.....................................86
4.1.5 Ejemplo completo ....................................................................................................87
4.1.6 Resultado...................................................................................................................89
4.2 Sobrecarga de métodos y constructores .........................................................................89
4.2.1 Sobrecarga de métodos............................................................................................89
4.2.2 Ejemplo ......................................................................................................................90
4.2.3 Resultado ...................................................................................................................93
4.2.4 Constructores.............................................................................................................93
4.3 Ejemplos..............................................................................................................................96
4.3.1 Figura genérica .........................................................................................................96
4.3.2 Agenda de teléfono ..................................................................................................98
4.3.3 Ejercicio de logística............................................................................................. 100
4.4 Clases utilizadas como Parámetros.............................................................................. 104
4.4.1 Código ..................................................................................................................... 105
4.4.2 Resultados............................................................................................................... 109
4.5 Propiedades y métodos de clase y de instancia ......................................................... 109
4.5.1 Propiedades de instancia ...................................................................................... 109
4.5.2 Propiedades de clase............................................................................................. 111
4.5.3 Métodos de instancia ............................................................................................ 113
4.5.4 Métodos de clase................................................................................................... 113
4.5.5 Ejemplo que utiliza propiedades de clase......................................................... 115
4.5.6 Código del ejemplo ............................................................................................... 116
4.5.7 Resultados............................................................................................................... 120
4.6 Paquetes y atributos de acceso..................................................................................... 120
4.6.1 Definición de paquetes ......................................................................................... 121
4.6.2 Utilización de las clases de un paquete.............................................................. 122
4.6.3 Proceso de compilación cuando se utilizan paquetes ...................................... 123
4.6.4 Atributos de acceso a los miembros (propiedades y métodos) de una clase124
4.7 Ejemplo: máquina expendedora .................................................................................. 125

CAPÍTULO 5: PROGRAMACIÓN ORIENTADA A OBJETOS


USANDO HERENCIA.............................................. 137

5.1 Herencia............................................................................................................................ 137


5.1.1 Funcionamiento básico......................................................................................... 137
5.1.2 Accesibilidad a los miembros heredados en una subclase............................. 140
5.1.3 Constructores de las subclases ............................................................................ 141
5.1.4 El modificador final.............................................................................................. 145
5.2 Ejemplos........................................................................................................................... 146
X JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (jbobi@eui.upm.es)

5.2.1 Figuras geométricas .............................................................................................. 146


5.2.2 Vehículos................................................................................................................ 150
5.3 Polimorfismo ................................................................................................................... 155
5.3.1 Conceptos............................................................................................................... 155
5.3.2 Ejemplo ................................................................................................................... 157
5.4 Clases abstractas e interfaces........................................................................................ 160
5.4.1 Métodos abstractos................................................................................................ 160
5.4.2 Clases abstractas .................................................................................................... 161
5.4.3 Interfaces................................................................................................................. 164
5.4.4 Ejercicio ................................................................................................................... 167

CAPÍTULO 6: EXCEPCIONES ........................................................ 171

6.1 Excepciones predefinidas .............................................................................................. 171


6.1.1 Introducción ........................................................................................................... 171
6.1.2 Sintaxis y funcionamiento con una sola excepción ......................................... 174
6.1.3 Ejemplo con una sola excepción......................................................................... 176
6.1.4 Sintaxis y funcionamiento con más de una excepción .................................... 177
6.1.5 El bloque finally .................................................................................................... 180
6.1.6 Propagación de excepciones................................................................................ 180
6.1.7 Estado de una excepción ...................................................................................... 182
6.2 Excepciones definidas por el programador ................................................................ 184
6.2.1 Introducción ........................................................................................................... 184
6.2.2 Definición de una excepción definida por el programador............................ 185
6.2.3 Utilización de una excepción definida por el programador ........................... 186
6.2.4 Ejemplo ................................................................................................................... 187

CAPÍTULO 7: INTERFAZ GRÁFICO DE USUARIO .................. 193

7.1 Creación de ventanas...................................................................................................... 193


7.1.1 Introducción ........................................................................................................... 193
7.1.2 Utilización de la clase Frame .............................................................................. 195
7.1.3 Creación y posicionamiento de múltiples ventanas........................................ 196
7.2 Paneles y objetos de disposición (layouts)................................................................. 199
7.2.1 Introducción ........................................................................................................... 199
7.2.2 Utilización básica de paneles .............................................................................. 202
7.2.3 Objeto de disposición (Layout): FlowLayout................................................... 203
7.2.4 Objeto de disposición (Layout): BorderLayout ............................................... 205
7.2.5 Objeto de disposición (Layout): GridLayout.................................................... 208
7.3 Etiquetas, campos y áreas de texto .............................................................................. 211
7.3.1 Introducción ........................................................................................................... 211
7.3.2 Etiqueta (Label) ..................................................................................................... 211
7.3.3 Campo de texto (TextField)................................................................................. 215
7.3.4 Área de texto (TextArea)..................................................................................... 218
7.3.5 Fuentes (Font)........................................................................................................ 219
 JESÚS BOBADILLA SANCHO (jbobi@eui.upm.es) ÍNDICE XI

7.4 Cajas de verificación, botones de radio y listas......................................................... 221


7.4.1 Introducción ........................................................................................................... 221
7.4.2 Cajas de verificación (Checkbox) ....................................................................... 221
7.4.3 Botones de radio (CheckboxGroup)................................................................... 223
7.4.4 Lista (List).............................................................................................................. 226
7.4.5 Lista desplegable (Choice)................................................................................... 229
7.5 Diseño de formularios.................................................................................................... 230
7.5.1 Diseño del interfaz gráfico deseado................................................................... 230
7.5.2 Implementación en una sola clase...................................................................... 232
7.5.3 Implementación en varias clases......................................................................... 235
7.6 Diálogos y menús ........................................................................................................... 240
7.6.1 Introducción ........................................................................................................... 240
7.6.2 Diálogo (Dialog).................................................................................................... 241
7.6.3 Diálogo de carga / almacenamiento de ficheros (FileDialog) ....................... 243
7.6.4 Menús (Menu y MenuBar) .................................................................................. 245

CAPÍTULO 8: EVENTOS .................................................................. 247

8.1 Mecanismo de eventos en java..................................................................................... 247


8.1 Introducción............................................................................................................... 247
8.2 Arquitectura de los eventos..................................................................................... 248
8.3 Interfaces que soportan el mecanismo de eventos.............................................. 250
8.4 Esquema general de programación........................................................................ 253
8.2 Eventos de ratón y de movimiento de ratón............................................................... 257
8.2.1 Introducción ........................................................................................................... 257
8.2.2 Eventos de ratón.................................................................................................... 259
8.2.3 Eventos de movimiento de ratón ........................................................................ 267
8.3 Eventos de teclado y de ventana.................................................................................. 272
8.3.1 Introducción ........................................................................................................... 272
8.3.2 Eventos de teclado................................................................................................. 274
8.3.3 Eventos de ventana................................................................................................ 276
8.4 Eventos de acción, enfoque y elemento...................................................................... 278
8.4.1 Introducción ........................................................................................................... 278
8.4.2 Eventos de acción.................................................................................................. 280
8.4.3 Eventos de enfoque............................................................................................... 284
8.4.4 Eventos de elemento............................................................................................. 287

CAPÍTULO 9: APLICACIONES DE EJEMPLO ........................... 293

9.1 Ejemplo: calculadora ...................................................................................................... 293


9.1.1 Definición del ejemplo ......................................................................................... 293
9.1.2 Diseño del interfaz gráfico de usuario ............................................................... 294
9.1.3 Implementación del interfaz gráfico de usuario ............................................... 296
9.1.4 Diseño del tratamiento de eventos...................................................................... 300
9.1.5 Implementación de las clases de tratamiento de eventos................................ 302
XII JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (jbobi@eui.upm.es)

9.1.6 Diseño del control................................................................................................. 305


9.1.7 Implementación del control................................................................................. 307
9.2 Ejemp lo: editor................................................................................................................ 316
9.2.1 Definición del ejemplo ......................................................................................... 316
9.2.2 Estructura de la aplicación................................................................................... 318
9.2.3 Propiedades de la clase......................................................................................... 321
9.2.4 Constructores.......................................................................................................... 322
9.2.5 Método PreparaMenus.......................................................................................... 324
9.2.6 Método PreparaZonaInferior............................................................................... 325
9.2.7 Clase Colores ......................................................................................................... 325
9.2.8 Tratamiento de las opciones de menú................................................................ 327
9.2.9 Tratamiento de las opciones “Buscar” y “Reemplazar” ................................. 329
9.2.10 Tratamiento de los botones de radio “Color texto” y “Color fondo”......... 331
9.2.11 Tratamiento a las pulsaciones de los botones de colores............................. 331

APÉNDICES

A: CUESTIONES DE AUTOEVALUACIÓN .................................. 333

B: SOLUCIONES A LAS CUESTIONES DE AUTOEVALUACIÓN


............................................................................................ .............. 359

ÍNDICE ALFABÉTICO ............................................................. ....... 361


CAPÍTULO 1

TOMA DE CONTACTO, VARIABLES,


TIPOS DE DATOS Y OPERADORES

1.1 INSTALACIÓN DEL ENTORNO DE DESARROLLO


Para poder realizar programas en lenguaje Java es necesario disponer de un
mínimo de herramientas que nos permitan editar, compilar e interpretar el código
que diseñamos. Para escribir físicamente los programas podemos utilizar cualquier
editor de texto (por ejemplo el bloc de notas, el WordPad, etc.). Para compilar y
ejecutar los programas existen dos opciones:
• Utilizar un entorno integrado de desarrollo (por ejemplo JBuilder de
Borland, Visual J++ de Microsoft, etc.)
• Emplear el software básico de desarrollo (SDK) de Sun Microsystems

La primera opción resulta especialmente interesante para afrontar la creación


de aplicaciones de manera eficiente, puesto que estos entornos facilitan
enormemente el diseño, escritura y depuración de los programas. La segunda opción
es mucho más adecuada para aprender a programar en Java, porque no existe la
generación automática de código que incorporan los entornos integrados de
desarrollo.

El SDK de Sun Microsystems puede ser obtenido de forma gratuita (es un


software de libre distribución) en la dirección www.sun.com; una vez que se dispone
del fichero con el SDK (por ejemplo j2sdk-1_4_0-win.exe) basta con ejecutarlo para
conseguir que el software se instale en su ordenador.

En primer lugar se descomprime automáticamente la información:


2 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

Por defecto, el software se instalará a partir de un directorio cuyo nombre


tiene que ver con la versión del SDK (en este caso j2sdk1.4.0), aunque siempre
puede elegirse una ubicación personalizada (con la opción Browse).

La propia instalación rellenará la variable de entorno CLASSPATH con los


valores adecuados (donde el entorno debe buscar los ficheros compilados .class). La
variable de entorno PATH la deberemos actualizar para que el sistema operativo
encuentre los ficheros ejecutables del SDK, que en nuestro caso están ubicados en el
directorio c:\j2sdk1.4.0\bin (suponiendo que hemos realizado la instalación sobre el
disco c: y el directorio j2sdk1.4.0).

Para asegurarse de que la variable CLASSPATH se encuentra correctamente


establecida, podemos consultar su valor de la siguiente manera:
 BOBADILLA CAPÍTULO 1: CONCEPTOS BÁSICOS, VARIABLES, TIPOS DE DATOS Y OPERADORES 3

Sun Microsystems proporciona, además, ayuda en línea sobre las


características del lenguaje y las clases más comunes para el desarrollo de
aplicaciones. Escribiendo programas en Java resulta necesario instalar esta ayuda
para poder consultar los métodos y clases disponibles. La ayuda puede ser obtenida
en Internet a través del sitio Web de Sun (www.sun.com). Una vez instalada, se
accede a ella a través de su página principal (index.html).Para escribir programas en
Java, el enlace más importante de esta página es: Java 2 Platform API Specification,
correspondiente al apartado API & Language Documentation.

El software de desarrollo SDK funciona en modo texto sobre una ventana de


consola del sistema operativo. En Windows, para acceder a la consola se debe pulsar
sobre el símbolo de MS-DOS (habitualmente accesible a través del botón de Inicio).
Una vez en la consola, dirigirse al directorio de trabajo (en el ejemplo
C:\CursoJava\Introducción), tal y como aparece en la siguiente figura:

Podemos configurar la consola en cuanto al tamaño con el que aparece,


memoria que utiliza, fuentes que emplea, etc. Para realizar esta configuración basta
con acceder al símbolo de MS-DOS y, en lugar de pulsar con el botón izquierdo del
ratón, hacerlo con el derecho y seleccionar Propiedades.
4 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

En concreto, nos interesa seleccionar la solapa Programa y variar el valor


del campo de texto Carpeta de trabajo, introduciendo el directorio sobre el que
solemos trabajar, con ello conseguimos que la consola se abra directamente en ese
directorio. En nuestro ejemplo, hemos introducido el valor
C:\CursoJava\Introducción.

Como veremos en el siguiente apartado, el fichero javac.exe es el


compilador de java. Se encuentra en el directorio c:\j2sdk1.4.0\bin (hacia donde
hemos apuntado la variable PATH). Si todo se encuentra correctamente al invocar el
compilador sin parámetros nos indica la forma de uso correcta, tal y como aparece
en la siguiente figura:
 BOBADILLA CAPÍTULO 1: CONCEPTOS BÁSICOS, VARIABLES, TIPOS DE DATOS Y OPERADORES 5

1.2 PRIMER PROGRAMA EN JAVA: “HOLA MUNDO”


El aprendizaje de todo lenguaje de programación pasa por la etapa
obligatoria de realizar un primer programa, lo más sencillo posible, que muestre:
• La estructura sintáctica mínima a la que obliga el lenguaje
• La manera de introducir, traducir y ejecutar el programa
• La validez del entorno en el que se sustentarán los desarrollos (para
nosotros el SDK)

Nuestro programa únicamente escribirá el texto “Hola mundo”, pero servirá


para asentar cada uno de los puntos expuestos en el párrafo anterior.

Pasos detallados:
1 Abrir un editor de texto (por ejemplo el WordPad)
Habitualmente a través del botón de Inicio: (Inicio → Programas →
Accesorios → WordPad).
2 Introducir el código (sin incluir los números de línea):
1 public class HolaMundo {
2 public static void main (String[] args) {
3 System.out.println("Hola Mundo");
4 }
5 }
Es necesario respetar la condición mayúscula/minúscula de cada letra
del programa, puesto que en este lenguaje una letra en minúscula es
diferente a su correspondiente en mayúsculas.
La línea 1 define la clase (objeto) HolaMundo. Java es un lenguaje
orientado a objetos, donde toda las aplicaciones se estructuran en
grupos de objetos (clases en Java). La clase se define como public
(publica), indicando que será accesible a cualquie r otra clase. El
último carácter de la línea 1 es una llave de comienzo; indica el
comienzo de la clase. La clase termina en la línea 5 (con la llave de
fin).
La línea 2 define un método (procedimiento, subrutina) de la clase
HolaMundo. Este método es espe cial, le indica al entorno de Java el
comienzo de nuestra aplicación. Su nombre (identificador) es: main
(método principal). Este método siempre lleva un parámetro String[]
que identifica un conjunto de literales (textos); por ahora no
emplearemos esta característica, aunque debemos respetar su sintaxis.
El método es público y estático (atributos que veremos en detalle en
los temas siguientes).
El contenido del método main, en nuestro ejemplo, se encuentra
delimitado entre la llave de inicio situada en la línea 2 y la llave de fin
6 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

situada en la línea 4. Únicamente contiene la instrucción situada en la


línea 3, que imprime el literal (texto) que aparece entre comillas y
como parámetro de la llamada al método println. Obsérvese el
carácter ‘;’ que se debe utilizar obligatoriamente para separar
instrucciones.
El sangrado de cada una de las líneas no es necesario, pero resulta
muy conveniente para hacer más legible los programas. Su función
principal es facilitar la identificación visual de cada bloque de código.

3 Grabar el código en el fichero HolaMundo.java y con formato de texto

Es absolutamente necesario que la extensión sea .java, que el fichero


este grabado con formato de texto y que el nombre de la clase
coincida EXACTAMENTE con el nombre del fic hero (en nuestro
caso HolaMundo). Finalmente, en el WordPad nos quedará una
ventana similar a la siguiente:

4 Compilar el programa
En la ventana de MS-DOS, en el directorio donde hemos grabado el
fichero HolaMundo.java, debemos ejecutar el compilador (javac)
poniendo como argumento el nombre del fichero CON LA
EXTENSIÓN .java (javac HolaMundo.java). Si existen errores, se
obtendrá un listado de los mismos. Si no los hay, como en nuestro
caso, no aparece nada:
 BOBADILLA CAPÍTULO 1: CONCEPTOS BÁSICOS, VARIABLES, TIPOS DE DATOS Y OPERADORES 7

Tras la compilación con éxito del programa, obtenemos el fichero


objeto HolaMundo.class:

5 Ejecutar el programa
En el directorio en el que estamos trabajando, ejecutar el intérprete de
java (java) suministrando como parámetro el nombre de la clase
(HolaMundo) SIN la extensión .class (java HolaMundo). Nos
aparecerá el texto “Hola Mundo” correspondiente a la impresión de
datos por consola que codificamos en la línea 3 de nuestro programa:

1.3 VARIABLES Y TIPOS DE DATOS

1.3.1 Variables

Las variables se utilizan para almacenar datos asociados a nombres. Cada


variable tiene un nombre que sirve de referencia para introducir datos o acceder a los
8 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

mismos. Un ejemplo de utilización de variable es: Edad_Pedro = 23; en este caso,


introducimos el valor numérico 23 en la variable con nombre Edad_Pedro. A partir
de ahora podemos utilizar la variable a través de su nombre (Edad_Pedro) para
referirnos a su valor (23), por ejemplo estableciendo Edad_Ana = Edad_Pedro – 2;
donde Ana podría ser la hermana pequeña de Pedro, que es dos años más joven que
él.

Los nombres de las variables, en Java, pueden tener cualquier longitud y


deben comenzar con una letra, el símbolo de subrayado “_” o el dólar “$”. El resto
de los caracteres del nombre pueden ser cualquiera, salvo los que pueden dar lugar a
confusión, como los operadores (+,-,*,etc.). Por ejemplo, sería correcto el nombre
MiAmigoMasQuerido, pero no el nombre MiAmigo+Querido, puesto que en este
último caso el compilador interpretaría que hay que sumar el contenido de la
variable MiAmigo con el contenido de la variable Querido.

Los nombres de variables no deben tener espacios en blanco, puesto que el


compilador identificaría más de una variable; por ejemplo, NumeroDeAciertos es un
nombre de variable correcto, sin embargo Numero De Aciertos no lo es, porque el
compilador identificaría tres variables diferentes: Numero, De y Aciertos.

Por último, los nombres de variables no deben coincidir con palabras


reservadas (tales como public, static, class, void , main, etc.), puesto que estos
identificadores tienen un significado especial para el compilador.

En Java, los identificadores de variables suelen definirse empezando por un


carácter en minúscula, por ejemplo contador. Si en el identificador existe más de
una palabra, los comienzos del resto de las palabras se ponen con mayúsculas,
ejemplo: contadorDeAciertos. En este curso seguiremos una notación parecida,
aunque no idéntica; comenzaremos todas las palabras con mayúsculas:
ContadorDeAciertos.

Para asentar los conceptos expresados en este apartado, veamos una serie de
casos de identificadores de variables correctos e incorrectos:
LaCasaDeLaPradera → identificador correcto
El Hombre Sin Rostro → identificador incorrecto, no debe existir ningún
espacio en blanco en un nombre de variable
3Deseos → identificador incorrecto, el nombre no empieza por una letra
(sino por un número)
TresDeseos → identificador correcto
_4 → identificador correcto
$ → identificador correcto
 BOBADILLA CAPÍTULO 1: CONCEPTOS BÁSICOS, VARIABLES, TIPOS DE DATOS Y OPERADORES 9

$Ganado → identificador correcto


public → identificador incorrecto, public es un nombre reservado por el
lenguaje

Los identificadores deben intentar ser representativos de los datos que


albergan, de esta manera ValorAcumulado, NumeroAlumnos, CantidadEuros, Edad,
Potencia son variables que determinan de forma adecuada el significado de sus
contenidos, mientras que los nombres Variable, Valor, V1, V2 , no son
representativos de sus contenidos.

La longitud de los identificadores no debe ser excesivamente larga, para no


dificultar la legibilidad de las instrucciones, por ejemplo resulta mucho más legible
Stock = LibrosEditados – LibrosVendidos, que StockTotalDeLibrosEnAlmacen =
LibrosEditadosEnElAñoEnCurso – LibrosVendidosYSacadosDelAlmacen.

Los identificadores en mayúsculas se suelen reservar para nombres


(normalmente cortos) de constantes, de esta manera las instrucciones (basadas
mayoritariamente en operaciones con variables) son más legibles, al utilizarse
minúsculas. Compárese Stock = LibrosEditados – LibrosVendidos con STOCK =
LIBROSEDITADOS – LIBROSVENDIDOS.

1.3.2 Tipos de datos

Las variables albergan datos de diferentes tipos (numérico decimal,


numérico entero, caracteres, etc.). Para indicar el tipo de dato que contendrán las
variables debemos “declarar” las mismas, indicando sus tipos. Este mecanismo
permite que el traductor (compilador) realice comprobaciones estáticas de validez,
como por ejemplo que no empleamos en el programa una variable que no ha sido
declarada, que no asignemos un carácter a una variable de tipo numérico, que no
sumemos un carácter a un valor numérico, etc.

A continuación se establece una relación de los tipos de datos primitivos


(los que proporciona el lenguaje):

TIPOS PRIMITIVOS
Nombre del tipo Tamaño en bytes Rango
Tipos numéricos enteros
byte 1 -128 a 127
10 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

short 2 -32768 a 32767


int 4 -231 a 231
long 8 -263 a 263
Tipos numéricos decimales
float 4 -3.4x1038 a 3.4x1038
double 8 -1.7x10308 a 1.7x10308
Tipo carácter
char 2 Conjunto de caracteres
Tipo lógico (booleano)
boolean 1 true, false

Para declarar una va riable se emplea la sintaxis:


tipo identificador;
tipo identificador [=valor];
tipo identificador1,identificador2,identificador3,etc.;
tipo identificador1 = valor1,identificador2 = valor2, etc.;

Por ejemplo:
byte EdadPedro = 60;
short SueldoMensual;
float PrecioEnEuros, Cateto1, Cateto2, Hipotenusa;
boolean Adquirido = false, Finalizado = true;

1.3.3 Tipos numéricos enteros

A continuación se proporcionan diversos programas comentados que


muestran la manera de utilizar los tipos numéricos enteros:

En el primer ejemplo TiposNumericos1, se declaran las variables de tipo


byte EdadJuan y EdadPedro (líneas 3 y 4) y las variables de tipo short SueldoBase y
Complementos (líneas 6 y 7). La línea 9 muestra, a modo de ejemplo, el valor de
SueldoBase, que es 1980.

Obsérvese que se ha seleccionado el tipo byte para albergar edades, por lo


que asumimos que nunca tendremos que introducir un valor superior a 127; del
mismo modo empleamos el tipo short para contener sueldos (en euros), por lo que
no podremos pasar de 32767, lo que puede ser válido si nos referimos a sueldos
 BOBADILLA CAPÍTULO 1: CONCEPTOS BÁSICOS, VARIABLES, TIPOS DE DATOS Y OPERADORES 11

mensuales. Si estamos indicando sueldos anuales, sería conveniente pasar al


siguiente tipo en tamaño (int).

1 public class TiposEnteros1 {


2 public static void main (String[] args) {
3 byte EdadJuan = 20;
4 byte EdadPedro = 22;
5
6 short SueldoBase = 1980;
7 short Complementos = 400;
8
9 System.out.println(SueldoBase);
10 }
11 }

Si al inicializar el valor de una variable nos pasamos en el rango permitido,


el compilador nos dará un error, indicándonos el tipo que deberíamos emplear:

En el siguiente ejemplo TiposEnteros2 se define una variable de tipo entero


(int) y otra de tipo entero largo (long), cada una de ellas con valores adecuados al
tipo de datos que representan. Es importante saber que los valores numéricos enteros
no pueden ser representados con puntos (ejemplo 4.000.000 en lugar de 4000000),
puesto que se confundirían con valores numéricos decimales.

En la línea 4 del ejemplo, se define el valor 5000000000L acabado en la


letra L. Esta letra indica que el valor debe ser tomado como long antes de ser
asignado a la variable. Si no ponemos esta indicación, el valor numérico se toma
como int (por defecto) y el compilador muestra un error indicando que el número es
demasiado grande para pertenecer a este tipo.

1 public class TiposEnteros2 {


2 public static void main (String[] args) {
3 int HabitantesEnMadrid = 4000000;
4 long HabitantesEnElMundo = 5000000000L;
5
6 System.out.println(HabitantesEnElMundo);
12 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

7 }
8 }

1.3.4 Tipos numéricos decimales

Existe una gran cantidad de valores numéricos que, por su naturaleza,


requieren la utilización de decimales; Java proporciona los tipos de datos float y
double para albergar este tipo de valores. Como ejemplos de valores que requieren
decimales podemos establecer: el precio en euros (con centimos) de un artículo, el
peso en kilos (con precisión de gramos) de una persona, la medida en metros (con
precisión de centímetros) de un objeto, etc.

El siguiente ejemplo (TiposDecimales) muestra la manera de declarar y


utilizar variables de tipo float y double. En las líneas 3 y 4 establecemos el precio de
una pieza de pan en 0.87 euros y el del kilo de queso en 1.93 euros; la letra “f” con
la que terminamos las declaraciones le indica al compilador que los literales
numéricos (0.87 y 1.93) son de tipo float, si no pusiéramos esta indicación, el
compilador los tomaría (por defecto) como de tipo double , con lo que la asignación
float = double resultaría fallida.

En la línea de código 6 introducimos en la variable $Bocadillo el precio de


un bocadillo que contiene 150 gramos de queso.

Las líneas 8 y 9 muestran la manera de definir e inicializar variables que


contienen números realmente grandes (6 elevado a 100) y números realmente
pequeños (2.45 elevado a –95), para estos casos utilizamos el tipo double.

1 public class TiposDecimales {


2 public static void main (String[] args) {
3 float $PiezaPan = 0.87f;
4 float $KiloQueso = 1.93f;
5
6 float $Bocadillo = $PiezaPan + $KiloQueso * 0.15f;
7
8 double NumeroHormigas = 6E+100;
9 double DistanciaSubAtomica = 2.45E-95;
10
11 System.out.println($Bocadillo);
12 }
13 }
 BOBADILLA CAPÍTULO 1: CONCEPTOS BÁSICOS, VARIABLES, TIPOS DE DATOS Y OPERADORES 13

1.3.5 Tipo booleano

El tipo de datos lógico (booleano) nos permite declarar y definir variables


cuyo contenido es binario. Sus únicos valores posibles son true (verdadero) y false
(falso). Estas variables resultan especialmente útiles para definir condiciones lógicas
aplicables al control de flujo de los programas, por ejemplo: si (Encontrado) hacer
una cosa, en caso contrario hacer otra, siendo Encontrado una variable de tipo
boolean que se modifica a lo largo del programa.

En el siguiente ejemplo (TipoBooleano) se utilizan diversas variables de este


tipo. En la línea 3 se definen e inicializan (a true y false) dos variables booleanas. En
la línea 5 se establece el contenido de una variable Triste como el contrario (negado)
de la variable Contento , para ello se emplea el operador ! que detallaremos más
adelante.

1 public class TipoBooleano {


2 public static void main (String[] args) {
3 boolean Contento = true, MenorDeEdad = false;
4
5 boolean Triste = !Contento;
6
7 System.out.println(Triste);
8 }
9 }
14 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

1.3.6 Tipo carácter

En Java tenemos la posibilidad de utilizar variables de tipo carácter, capaces


de contener, cada variable, un carácter (‘A’, ‘B’, ‘a’, ‘*‘, ‘8’, etc.). En la línea 2 de
la clase TipoCaracter se declaran las variables AMayuscula y AMinuscula , en donde
introducimos los valores ‘A’ y ‘a’.

La línea 4 define la variable Bmayuscula , introduciendo el valor siguiente al


de Amayuscula, para ello hay que realizar una conversión de tipos que nos sirve para
ilustrar los conceptos que justamente se explican en el apartado siguiente. En la línea
6 se imprime una nueva línea y en la 7 el carácter ‘B’.

1 public class TipoCaracter {


2 public static void main (String[] args) {
3 char AMayuscula = 'A', AMinuscula = 'a';
4 char BMayuscula = (char) (AMayuscula + 1);
5
6 System.out.println('\n');
7 System.out.println(BMayuscula);
8 }
9 }

También existe la posibilidad de utilizar caracteres que no podemos


conseguir con facilidad en nuestro teclado, para ello se utiliza la secuencia de
“escape”: \. Podemos, de esta manera, definir cualquier carácter de codificación
“unicode” (codificación más extensa que ASCII) de la siguiente manera: ‘\uxxxx’,
donde las equis se sustituyen por el valor numérico en hexadecimal del carácter. Por
ejemplo ‘\u0041’ se corresponde con la A mayúscula.

Otros caracteres especiales son:


\b espacio hacia atrás
\n nueva línea
\r retorno de carro
\t tabulador
 BOBADILLA CAPÍTULO 1: CONCEPTOS BÁSICOS, VARIABLES, TIPOS DE DATOS Y OPERADORES 15

1.3.7 Conversión explícita de tipos (Casting)

Resulta muy habitual que en los programas nos encontremos con la


necesidad de mezclar tipos de datos, por ejemplo al trabajar con una biblioteca
matemática que obliga a utilizar tipos de datos double , cuando nosotros las variables
las tenemos de tipo float; o más sencillo todavía, al intentar sumar un 1 (por defecto
int) a una variable de tipo short. Java, en algunos casos, realiza una conversión
implícita de datos, sin embargo, por regla general, nos veremos obligados a
programar conversiones explícitas de tipos.

Para modificar el tipo de un valor, basta con indicar el nuevo tipo entre
paréntesis antes del valor, por ejemplo:
(byte ) 1 → convierte el 1 (int) a byte
(double) MiVariableDeTipoFloat → convierte a double una variable de
tipo float
(short) (VariableDeTipoByte + VariableDeTipoByte) → convierte a short el
resultado de sumar dos variables de tipo byte. Obsérvese que al sumarse dos
variables de tipo byte, el resultado puede que no “quepa” en otra variable de
tipo byte.

En el ejemplo siguiente: Casting, se realizan dos conversiones explícitas de


tipo: la primera en la línea 4, donde al sumarse EdadJuan (byte) a 1 (int) el resultado
es int y por lo tanto lo debemos convertir al tipo de la variable EdadPedro (byte ); en
este caso debemos estar muy seguros de que EdadJuan es distinto a 127 (límite del
tipo byte). En la línea 11 convertimos a short, debido a que la operación de suma
trabaja con el tipo (int) y por lo tanto realiza una conversión implícita de short a int
en las variables SueldoBase y Complementos. El casting que realizamos nos
convierte de int (resultado de la suma) a short (tipo de la variable SueldoTotal).

1 public class Casting {


2 public static void main (String[] args) {
3 byte EdadJuan = 20;
4 byte EdadPedro = (byte) (EdadJuan + 1);
5
6 short SueldoBase = 1980;
7 short Complementos = 400;
8
9 short SueldoTotal;
10
11 SueldoTotal = (short) (SueldoBase + Complementos);
12
13 System.out.println(SueldoTotal);
14 }
15 }
16 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

A menudo, en los programas escritos en Java se utiliza intensivamente el


tipo int, cuando, en principio, sería más adecuado hacer uso de otros tipos que se
ajusten mejor en tamaño a la naturaleza de los datos. La razón es evitar operaciones
de conversión explicita de tipos.

1.4 OPERADORES

1.4.1 Introducción

Los operadores nos permiten realizar operaciones sobre los operandos


estudiados en el apartado anterior. Existen operadores unarios y binarios; un ejemplo
de operador unario que ya hemos utilizado es la negación lógica (!), lo vimos al
explicar el tipo de datos booleano y lo empleamos en la instrucción: boolean Triste
= !Contento; donde el operador negación se aplica a un único operando (la variable
Contento). El ejemplo de operador binario que más hemos utilizado es la suma, que
se aplica sobre dos operandos.

Los operadores tienen unas reglas de precedencia que resulta importante


tener en cuenta. En el apartado anterior, calculábamos el precio de un bocadillo de la
siguiente manera: float $Bocadillo = $PiezaPan + $KiloQueso * 0.15f; en este
caso utilizamos dos operadores binarios: la suma (+) y la multiplicación (*). El
operador de multiplicación tiene mayor precedencia que el de suma, por lo que, de
forma correcta, primero se obtiene el precio del queso y luego se realiza la suma del
pan más el queso. Si el operador de suma se hubiera aplicado primero, se habría
sumado $PiezaPan + $KiloQueso y, posteriormente, el resultado se habría
multiplicado por 0.15f, lo que nos proporcionaría un resultado incorrecto. Cuando
existen varios operadores con la misma precedencia, se aplican de izquierda a
derecha.

La precedencia de los operadores se puede variar usando los paréntesis. Las


operaciones que se encuentren entre paréntesis se realizan primero. En el ejemplo
del bocadillo, si suponemos que ya contábamos con 0.1f kilos de queso, el nuevo
precio lo podemos obtener de la siguiente manera: float $Bocadillo = $PiezaPan +
$KiloQueso * (0.15f-0.1f); primero se realizará la resta, después la multiplicación y
por último la suma.

Los operadores pueden ser clasificados atendiendo al tipo de operandos


sobre los que actúan, de esta manera realizaremos una breve descripción de los
operadores aritméticos, lógicos y de comparación, obviando a los operadores de bits
que se utilizan con muy poca frecuencia.
 BOBADILLA CAPÍTULO 1: CONCEPTOS BÁSICOS, VARIABLES, TIPOS DE DATOS Y OPERADORES 17

1.4.2 Operadores aritméticos

Los operadores aritméticos más comunes son la suma (+) , resta (-),
multiplicación (*) y división (/) binarios, aunque también se utilizan los operadores
unarios (+) y (-), y el operador binario que obtiene el módulo de una división (%).
Finalmente, disponemos de operadores aritméticos de preincremento,
postincremento, predecremento y postdecremento, que permiten acortar el tamaño
de ciertas instrucciones y facilitar la optimización de código por parte del
compilador.
Operación sintaxis significado
Preincremento ++Variable; Variable = Variable + 1; (antes de asignación)
Postincremento Variable++; Variable = Variable + 1; (después de asignación)
Predecremento --Variable; Variable = Variable – 1; (antes de asignación)
Postdecremento Variable--; Variable = Variable – 1; (después de asignación)

Para ilustrar los conceptos expuestos se proporciona la clase


OperadoresAritmeticos. La línea 3 hace uso de la multiplicación y suma binarios,
con resultado 7.2, mostrado en la línea 16. En la línea 4, primero actúa el operador
unario (-), después el operador binario (/) y por último la suma; el resultado,
mostrado en la línea 17 es –2. Las líneas de código 5 y 6 muestran, respectivamente,
la división y resto enteros, obteniéndose resultados 5 y 1 en las líneas 18 y 19.

Las líneas 8 a 14 del código utilizan los operadores de pre/post


incremento/decremento. A la variable PostIncremento de la línea 8 se le asigna el
valor 9, debido a que primero se realiza la asignación PostIncremento = Nueve y
posteriormente la operación de postincremento. La variable PreIncremento (en la
línea 10) albergará el valor 10, puesto que primero se realiza el preincremento y
después la asignación. Como cabe esperar, la variable PostDecremento recibe el
valor 9 y la variable PreDecremento el valor 8.

1 public class OperadoresAritmeticos {


2 public static void main (String[] args) {
3 float Impuesto = 2.2f * 1.0f + 5.0f;
4 int Impuesto2 = -8 + 12 / 2;
5 int Cociente = 16 / 3;
6 int Resto = 16 % 3;
7 int Nueve = 9;
8 int PostIncremento = Nueve++;
9 Nueve = 9;
10 int PreIncremento = ++Nueve;
11 Nueve = 9;
12 int PostDecremento = Nueve--;
13 Nueve = 9;
14 int PreDecremento = --Nueve;
15
18 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

16 System.out.println(Impuesto);
17 System.out.println(Impuesto2);
18 System.out.println(Cociente);
19 System.out.println(Resto);
20 System.out.println(PostIncremento);
21 System.out.println(PreIncremento);
22 System.out.println(PostDecremento);
23 System.out.println(PreDecremento);
24 }
25 }

1.4.3 Operadores lógicos

Los operadores lógicos nos permiten combinar operandos booleanos,


obteniendo, así mismo, resultados booleanos. Los operandos lógicos y sus
significados son:

Operador Sintaxis Ejemplo Funcionamiento


Negación ! Calor = !Frio !
false true
true false
Y && Oportunidad = Bueno && && false true
Bonito && Barato false 0 0
true 0 1
O || Mojado = Llueve || Riego || false true
false 0 1
true 1 1

En la clase OperadoresLogicos se programan las operaciones incluidas en la


columna “Ejemplo” de la tabla anterior. El resultado en todos los casos es: true.

1 public class OperadoresLogicos {


2 public static void main (String[] args) {
3 boolean Calor, Frio = false, Oportunidad, Bueno = true,
4 Bonito = true, Barato = true, Llueve = true,
 BOBADILLA CAPÍTULO 1: CONCEPTOS BÁSICOS, VARIABLES, TIPOS DE DATOS Y OPERADORES 19

5 Riego = false;
6
7 Oportunidad = Bueno && Bonito && Barato;
8
9 System.out.println(!Frio);
10 System.out.println(Oportunidad);
11 System.out.println(Llueve || Riego);
12 }
13 }

1.4.4 Operadores de comparación

Los operadores de comparación se utilizan frecuentemente para dirigir la


evolución del flujo de control en los programas. A continuación se proporciona una
tabla en la que se definen estos operadores:
Operador Sintaxis Ejemplo
Menor < (EdadJuan < 18)
Menor o igual <= (EdadJuan <= EdadPedro)
Mayor > (Hipotenusa > 8.0f * 6.2f + 5.7f)
Mayor o igual >= (Cateto1 >= Cateto2)
Igual == (Contador == 8)
Distinto != (Contador != 8)
Instancia de instanceof (Valor instanceof float)

El operador instanceof nos indica si una variable pertenece un tipo dado


(siendo el tipo una clase o array). Las clases y arrays se explicarán más adelante.En
el ejemplo siguiente se hace uso de las comparaciones mostradas en la tabla. Todos
los resultados que se muestran en las líneas 6 a 11 son true, salvo la 10 que es false.

1 public class OperadoresComparacion {


2 public static void main (String[] args) {
3 int EdadJuan = 6, EdadPedro = 21, Contador = 14;
4 float Hipotenusa = 105.6f, Cateto1 = 13.2f,
5 Cateto2 = 5.7f;
6
7 System.out.println(EdadJuan < 18);
8 System.out.println(EdadJuan <= EdadPedro);
9 System.out.println(Hipotenusa > 8.0f * 6.2f + 5.7f);
10 System.out.println(Cateto1 >= Cateto2);
11 System.out.println(Contador == 8);
12 System.out.println(Contador != 8);
13
14 }
15 }
20 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)
CAPÍTULO 2

ESTRUCTURAS DE CONTROL

2.1 EL BUCLE FOR


Hasta ahora, todos los programas que hemos visto son estrictamente
secuenciales, es decir, a la ejecución de una instrucción inexorablemente le sigue la
ejecución de la siguiente instrucción en secuencia. Esta situación es excesivamente
restrictiva, puesto que lo habitual es que surja la necesidad de variar el flujo de
control del programa para tomar decisiones y/o repetir cómputos.

En la mayor parte de los lenguajes de programación existe una serie de


instrucciones que permiten variar la secuencialidad del flujo de control. En Java las
podemos dividir en el grupo de instrucciones condicionales, el grupo de
instrucciones repetitivas y las llamadas a métodos. El bucle for que se explica en
este apartado pertenece al grupo de instrucciones repetitivas.

2.1.1 Sintaxis

Cuando deseamos ejecutar un grupo de instrucciones un número


determinado de veces, la instrucción for es la que mejor se adapta a esta tarea. La
sintaxis de esta instrucción es:
for (inicialización; condición de continuidad; expresión de variación) {
Instrucciones a ejecutar de forma repetitiva
}

La semántica (significado) de la instrucción es la siguiente: se inicializa una


variable (inicialización), se evalúa la condición de continuidad y, si se cumple, se
ejecutan las instrucciones situadas entre las llaves, finalmente se ejecuta la expresión
22 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

de variación y se repite el ciclo hasta que la condición de continuidad se evalúa


como false. Este proceso se puede entender mucho mejor con una serie de ejemplos:

2.1.2 Ejemplos de aprendizaje

1 public class BucleFor1 {


2 public static void main (String[] args) {
3 int i;
4 for (i=1;i<=4;i=i+1) {
5 System.out.println(i);
6 }
7 }
8 }

En el ejemplo anterior (BucleFor1) se declara una varia ble de tipo int en la


línea 3, que se utiliza en la línea 4 (for). El bucle for contiene sus tres secciones
obligatorias:
• Inicialización: i=1
• Condición de continuidad: i<=4
• Expresión de incremento: i=i+1

De esta manera el bucle se ejecuta de la siguiente forma:

Primera iteración i almacena el valor 1 i<=4 se evalúa como true Se imprime: 1


Segunda iteración i almacena el valor 2 i<=4 se evalúa como true Se imprime: 2
Tercera iteración i almacena el valor 3 i<=4 se evalúa como true Se imprime: 3
Cuarta iteración i almacena el valor 4 i<=4 se evalúa como true Se imprime: 4
Finalización i almacena el valor 5 i<=4 se evalúa como false ----------------

En el ejemplo anterior podemos cambiar, si lo deseamos, las líneas de


código 3 y 4 de la siguiente manera: for (int i=1; i<=4; i++); en este caso declaramos
la variable i dentro del bucle y utilizamos el operador de postincremento. También
podemos prescindir de las llaves, puesto que el bloque de instrucciones que se
ejecutan repetitivamente, en este caso, se compone de una única instrucción.
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 2: ESTRUCTURAS DE CONTROL 23

La expresión de variación puede definirse de cualquier manera que nos sea


útil. Por ejemplo, si deseamos imprimir los 10 primeros números impares negativos,
podemos programar el siguiente bucle:

1 public class BucleFor2 {


2 public static void main (String[] args) {
3 for (int i=-1;i>-20;i=i-2)
4 System.out.println(i);
5 }
6 }

Primera iteración i almacena el valor -1 i>-20 se evalúa como true Se imprime: -1


Segunda iteración i almacena el valor -3 i>-20 se evalúa como true Se imprime: -3
.................... ....................... ............................ ...............
Décima iteración i almacena el valor -19 i>-20 se evalúa como true Se imprime: -19
Finalización i almacena el valor -21 i>-20 se evalúa como ----------------
false

Es habitual, en programación, utilizar en la medida de lo posible bucles


sencillos, aunque esto conlleve que las instrucciones interiores al bucle contengan
expresiones un poco más complejas. Los bucles sencillos (secuenciales) facilitan la
depuración del código cuando existen errores en los programas. El ejemplo
BucleFor2 puede ser codificado de la siguiente manera:

1 public class BucleFor3 {


2 public static void main (String[] args) {
3 for (int i=1;i<=10;i++)
4 System.out.println(1-i*2);
5 }
6 }

Otra posible solución es:

1 public class BucleFor4 {


2 public static void main (String[] args) {
3 int ImparNegativo = -1;
4 for (int i=0;i<=9;i++){
5 System.out.println(ImparNegativo);
6 ImparNegativo = ImparNegativo - 2;
7 }
8 }
9 }
24 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

En este caso no podemos prescindir de las llaves de comienzo y final (llaves


delimitadoras) de bloque, puesto que si lo hiciéramos sólo se ejecutaría
repetitivamente la instrucción 5. La instrucción 6 se ejecutaría una sola vez, al
acabar el bucle.

2.1.3 Situaciones erróneas

En este apartado vamos a ilustrar una serie de actuaciones erróneas que son
muy habituales entre las personas que empiezan a programar. Resulta especialmente
conveniente prestar atención en este apartado para evitar, en la medida de lo posible,
codificar los programas con errores.

La primera situación que vamos a analizar son los bucles infinitos (producto
siempre de un error en la programación). En el siguiente ejemplo (BucleFor5) se
produce esta situación, debido a que la condición de continuidad del bucle nunca se
evalúa como false. Analizando el programa, llegamos fácilmente a la conclusión de
que la variable i albergará únicamente valores pares positivos, por lo que la
condición de continuidad i!=21 nunca se evaluará como false y por lo tanto nunca se
saldrá del bucle.

1 public class BucleFor5 {


2 public static void main (String[] args) {
3 for (int i=0;i!=21;i=i+2)
4 System.out.println(i);
5 }
6 }

Para cortar la ejecución de un programa basta con pulsar la combinación de


teclas Ctrl.+C con la consola de MS-DOS activa (donde se ejecuta el programa). La
tecla Ctrl. se debe pulsar primero, y manteniendo esta tecla pulsada presionar la
tecla C.

El siguiente programa es correcto. Imprime los primeros 20 números enteros


positivos:

1 public class BucleFor6 {


2 public static void main (String[] args) {
3 for (int i=1;i!=21;i=i+1)
4 System.out.println(i);
5 }
6 }
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 2: ESTRUCTURAS DE CONTROL 25

En BucleFor6 la condición de continuidad es correcta; la iteración se


produce para i=1, 2, 3, ..., 20. Con i valiendo 21, la condición se evalúa como false
(21!=21 es false). Si pusiéramos como condición de finalización i < 21, la semántica
del programa no variaría, sin embargo, i>20, por ejemplo, nos sacaría del bucle de
forma inmediata, puesto que en la primera iteración la condición de continuidad se
evaluaría como false. Aquí tenemos otro ejemplo típico de programación errónea: un
bucle for cuya condición de evaluación, mal programada, nos saca del bucle en la
primera iteración.

for (int i=1;i>20;i=i+1)

El siguiente programa (BucleFor7) contiene un error muy común y muy


difícil de detectar. El problema reside en haber mezclado la expresión de
postincremento (i++) con la de asignación (i=i+1), obteniendo la expresión,
errónea, i=i++.

1 public class BucleFor7 {


2 public static void main (String[] args) {
3 for (int i=1;i!=21;i=i++)
4 System.out.println(i);
5 }
6 }

El programa codificado en la clase BucleFor8 realiza la suma de los 1000


primeros números naturales (1+2+3+4+ .... +1000), imprimiendo por cada suma el
resultado parcial obtenido. Este programa está correctamente codificado.

1 public class BucleFor8 {


2 public static void main (String[] args) {
3 int Suma = 0;
4 for (int i=1;i<=1000;i++) {
5 Suma = Suma + i;
6 System.out.println(Suma);
7 }
8 }
9 }

Las instrucciones que se repiten (1000 veces) son las que se encuentran
entre los delimitadores { } asociados a la instrucción repetitiva for; por ello tanto la
instrucción 5 como la 6 se ejecutan 1000 veces. En cada vuelta del bucle se añade el
valor de i (1,2,3,4, ..., 1000) al resultado de la suma anterior (guardado en la variable
Suma). La evolución de las variables es la siguiente:

Primera i almacena el Suma almacena i<=1000 se evalúa Se imprime: 1


iteración valor 1 el valor 1 como true
26 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

Segunda i almacena el Suma almacena i<=1000 se evalúa Se imprime: 3


iteración valor 2 el valor 3 como true
Tercera i almacena el Suma almacena i<=1000 se evalúa Se imprime: 6
iteración valor 3 el valor 6 como true
.................... ...............
....................... ............................
Iteración 1000 i almacena el Suma almacena i<=1000 se evalúa Se imprime:
valor 1000 el valor 500500 como true 500500
Finalización i almacena el ---------------- i<=1000 se evalúa ----------------
valor 1001 ----------- como false

Si en el programa anterior olvidáramos las llaves delimitadoras del ámbito


(alcance) del bucle for, sólo se ejecutaría dentro del bucle la línea 5. En este caso
obtenemos el mismo resultado que en el ejemplo anterior, pero sin la impresión de
los resultados parciales de las sumas. La instrucción 6 se ejecuta al terminar el bucle
y nos imprime únicamente el resultado final.

1 public class BucleFor9 {


2 public static void main (String[] args) {
3 int Suma = 0;
4 for (int i=1;i<=1000;i++)
5 Suma = Suma + i;
6 System.out.println(Suma);
7 }
8 }

Omitir las llaves de un bucle es un error habitual cuando se comienza a


programar en Java, y normalmente las consecuencias son mucho peores que las
ocurridas en el ejemplo anterior.

2.1.4 Ejemplos de resolución de problemas

Factorial de un número

El primer ejemplo que se propone en esta sección es hallar el factorial de un


número. El valor factorial se consigue de la siguiente manera:
Factorial de k (k!) = k * (k-1) * (k-2) * ... * 2 * 1
Ejemplo: 4! = 4 * 3 * 2 * 1 = 24

Solución:
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 2: ESTRUCTURAS DE CONTROL 27

1 public class Factorial {


2 public static void main (String[] args) {
3 int Numero = 6;
4 int Factorial = 1;
5 for (int i=2;i<=Numero;i++)
6 Factorial = Factorial * i;
7 System.out.println(Factorial);
8 }
9 }

El valor del que queremos obtener el factorial lo almacenamos en la variable


Numero (línea 3), después inicializamos la variable Factorial a 1, que nos servirá de
acumulador. El bucle for comienza en 2, aumenta secuencialmente y termina en el
valor almacenado en Numero, de esta manera conseguimos pasar por la secuencia: 2,
3, 4, 5, 6, que son los valores que debemos multiplicar (el 1, realmente no es
necesario).

En la línea 6 acumulamos los valores parciales de la multiplicación de la


secuencia (2,3,4,5,6). La línea 7, ya fuera del bucle, nos imprime el resultado (720).

Para hallar el factorial de cualquier otro número, basta con variar el valor
con el que inicializamos la variable Numero, en la línea 6. Obsérvese que el factorial
de 1 (que es cero) no se saca con este método, y sobre todo, que si el valor de
Numero es muy grande, podemos desbordar el rango del tipo int, por lo que sería
conveniente emplear el tipo long.

Problema de logística

Supongamos que una importante empresa de electrodomésticos nos contrata


para resolver problemas de logística. El primer caso práctico que nos plantean es el
siguiente:

En las grandes ciudades el precio del suelo es muy caro, por lo que comprar
o alquilar grandes superficies de almacenamiento de electrodomésticos resulta
prohibitivo en el centro de la ciudad. La solución es alejarse del núcleo urbano, sin
embargo, cuanto más nos alejamos, más nos cuesta el precio de distribución que
cada día hay que abonar a los transportistas que nos trasladan los electrodomésticos
de la periferia al centro (donde se realizan la mayoría de las compras).

La estrategia que adoptaremos es la siguiente:


28 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

1 Adquirir un almacén pequeño en el centro de la ciudad (para 200


electrodomésticos, por término medio).
2 Adquirir almacenes en anillos concéntricos de 5 kilómetros a partir
del centro de la ciudad, cada almacén podrá contener un stock del
doble de electrodomésticos que el almacén anterior (es decir, de 400
electrodomésticos a 5 Km. del núcleo urbano, de 800
electrodomésticos a 10 kilómetros, etc.).

Se pide: indicar a cuantos kilómetros se encontrará el último almacén en una


ciudad que requiere una capacidad total de 100000 electrodomésticos en stock. Los
100000 electrodomésticos estarán repartidos entre todos los almacenes adquiridos.

Solución:

1 public class Logistica {


2 public static void main (String[] args) {
3 int CapacidadAlmacen = 200;
4 int CapacidadTotal = CapacidadAlmacen;
5 for (int i=2;i<=10;i++) {
6 CapacidadAlmacen = CapacidadAlmacen * 2;
7 CapacidadTotal = CapacidadTotal + CapacidadAlmacen;
8 System.out.println(i+": "+CapacidadTotal);
9 }
10 }
11 }

En la línea 3 establecemos la capacidad del primer almacén


(CapacidadAlmacen) a 200, en la línea 4 establecemos la capacidad total
(CapacidadTotal) que por ahora tenemos como la capacidad del primer almacén (el
del centro urbano). La línea 5 codifica un bucle que itera 10 veces: esperamos que
con 10 almacenes consigamos la capacidad total de 100000 electrodomésticos, si no
fuera así deberíamos aumentar el número de iteraciones.

La línea 6 actualiza la capacidad del siguiente almacén como el doble de la


capacidad del almacén actual. La línea 7 acumula en la variable CapacidadTotal el
número de electrodomésticos que sumaban los almacenes anteriores con los que
puede albergar el almacén actual. La línea 8 nos imprime los resultados parciales,
obsérvese que podemos imprimir varias variables en la misma línea.

El resultado de la ejecución del programa se muestra en la siguiente ventana:


 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 2: ESTRUCTURAS DE CONTROL 29

Se puede observar como con 9 almacenes (incluido el del centro urbano) se


alcanza la capacidad acumulada de 100000 electrodomésticos (concretamente
102200); por lo tanto hemos tenido que comprar 8 almacenes fuera del centro
urbano, situándose el último a 8*5 = 40 kilómetros de la ciudad.

Para realizar este ejercicio hubiera sido mucho más elegante emplear una
estructura de control de flujo en bucle que nos permitiera iterar mientras (o hasta)
que se cumpla una condición determinada: (que CapacidadTotal >=100000). El
bucle for no nos resuelve esta situación adecuadamente, puesto que está diseñado
para indicar a priori el número de iteraciones, aunque tampoco resulta imposible
utilizarlo de otra manera:

1 public class Logistica2 {


2 public static void main (String[] args) {
3 int CapacidadAlmacen = 200;
4 int CapacidadTotal = CapacidadAlmacen;
5 int i;
6 for (i=2;CapacidadTotal<100000;i++) {
7 CapacidadAlmacen = CapacidadAlmacen * 2;
8 CapacidadTotal = CapacidadTotal + CapacidadAlmacen;
9 }
10 System.out.println(i+": "+CapacidadTotal);
11 }
12 }

En cualquier caso, para resolver este ejercicio, resulta más apropiado utilizar
las estructuras de control que se explican en el siguiente apartado.

2.2 EL BUCLE WHILE


El bucle while nos permite repetir la ejecución de una serie de instrucciones
mientras que se cumpla una condición de continuidad. Su uso resulta recomendable
cuando no conocemos a priori el número de iteraciones que debemos realizar.
30 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

2.2.1 Sintaxis

El bucle while tiene dos posibles sintaxis:

while (condición de continuidad) {


Instrucciones a ejecutar de forma repetitiva
}

do {
Instrucciones a ejecutar de forma repetitiva
} while (condición de continuidad);

En ambos casos se itera mientras que la “condición de continuidad” se


cumpla, abandonándose el bucle cuando la condición se evalúa como false. En el
primer caso puede ocurrir que las instrucciones interiores del bucle nunca se
ejecuten (si la primera vez que se evalúa la condición resulta false); en el segundo
caso las instrucciones interiores al bucle se ejecutan al menos una vez.

2.2.2 Ejemplos de aprendizaje

En el siguiente ejemplo se muestra una implementación muy sencilla del


bucle while en la que se pretende imprimir los números 1, 2, 3 y 4. Puesto que
conocemos a priori el número de iteraciones sería más adecuado utilizar un bucle
for, pero se ha escogido este ejemplo sencillo para mostrar una primera
implementación del bucle while.

1 public class BucleWhile1 {


2 public static void main (String[] args) {
3 int i=1;
4 while (i<=4) {
5 System.out.println(i);
6 i++;
7 }
8
9 }
10 }

En BucleWhile1 se declara una variable de tipo int en la línea 3 y se


inicializa a 1; esta variable actuará como contador de iteraciones en el bucle. En la
línea 4 se establece la condición de continuidad del bucle (se itera mientras que
i<=4). La línea 5 se encarga de imprimir el valor del índice y la línea 6 de
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 2: ESTRUCTURAS DE CONTROL 31

incrementarlo. Nótese como se están codificando las distintas expresiones y


condiciones del bucle for: for (i=1; i<=4;i++), en las líneas 3, 4 y 6.

Un error muy frecuente cuando se codifica un bucle while es olvidar


incrementar el contador (línea 6), generando un bucle infinito. En nuestro ejemplo
también crearíamos un bucle infinito se olvidáramos las llaves delimitadoras del
ámbito del bucle.

Detalle de la ejecución de BucleWhile1:

Antes del while i almacena el valor 1


Primera iteración i<=4 se evalúa como true Se imprime: 1 i almacena el valor 2
Segunda iteración i<=4 se evalúa como true Se imprime: 2 i almacena el valor 3
Tercera iteración i<=4 se evalúa como true Se imprime: 3 i almacena el valor 4
Cuarta iteración i<=4 se evalúa como true Se imprime: 4 i almacena el valor 5
Finalización i<=4 se evalúa como false ----------------- ----------------

A continuación se muestra la forma alternativa de utilizar un bucle while: do


{ } while(condición); la semántica en este ejemplo es la misma que en el anterior (se
imprimen los valores 1 a 4). El detalle de la ejecución no varía respecto a la
mostrada en BucleWhile1, salvo que la condición de continuidad se evalúa al final
en lugar de al comienzo del bucle.

1 public class BucleWhile2 {


2 public static void main (String[] args) {
3 int i=1;
4 do {
5 System.out.println(i);
6 i++;
7 } while (i<=4);
8 }
9 }

Ahora vamos a resolver la siguiente cuestión: ¿Cuántos números naturales


(1, 2, 3, 4...) debemos sumar en secuencia para obtener al menos un valor de
100000?, es decir: ¿hasta qué valor llegará el sumatorio 1+2+3+4+5+..... para que la
suma alcance al valor 100000?

Este problema lo podemos resolver de forma muy simple haciendo uso del
bucle while:

1 public class BucleWhile3 {


2 public static void main (String[] args) {
3 int Suma=0;
4 int i=0;
5 do {
32 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

6 i++;
7 Suma = Suma + i;
8 } while (Suma<100000);
9 System.out.println(i);
10 }
11 }

En la línea 3 se declara e inicializa el acumulador Suma al valor 0 y en la


línea 4 el contador i al valor 0. En la línea 6 se incrementa i y en la línea 7 se
acumula el valor del contador sobre la variable Suma. Las iteraciones continúan
mientras Suma<100000 (línea 8).

Detalle de la ejecución de BucleWhile3:

Antes del while i almacena el Suma almacena el


valor 0 valor 0
Primera i almacena el Suma almacena el Suma <100000 se evalúa
iteración valor 1 valor 1 como true
Segunda i almacena el Suma almacena el Suma <100000 se evalúa
iteración valor 2 valor 3 como true
.......................... ............................. .................................. .............................................
.... .....
Iteración 447 i almacena el Suma almacena el Suma <100000 se evalúa
valor 447 valor 100128 como false

2.2.3 Ejemplos de resolución de problemas

Determinación de si un número es primo

Utilizando un algoritmo muy sencillo aunque poco eficaz, podemos saber si


un número es primo de la siguiente manera: dividimos el número entre todos los
anteriores (salvo el 1) y si no es divisible entre ninguno, entonces es primo. Una
posible implementación de este algoritmo es:
1 public class Primo {
2 public static void main (String[] args) {
3 int PosiblePrimo = 17;
4 int Divisor = 2;
5 boolean Primo = true;
6
7 do {
8 Primo = (PosiblePrimo % Divisor)!=0;
9 Divisor++;
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 2: ESTRUCTURAS DE CONTROL 33

10 } while(Divisor<PosiblePrimo && Primo);


11 System.out.println(Primo);
12 }
13 }

En la línea 3 declaramos y definimos el valor del número del que deseamos


saber si es primo. En la línea 4 inicializamos una variable Divisor a 2, éste será el
contador por el que se irá dividiendo sucesivamente nuestro PosiblePrimo. En la
línea 8 determinamos si PosiblePrimo es divisible entre Divisor, para ello
obtenemos su módulo (operación %) y lo comparamos con 0, si no es divisible, por
ahora el número puede ser primo. Vamos incrementando Divisor (en la línea 9) y
continuamos el bucle (línea 10) mientras que Divisor sea menor que PosiblePrimo y
Primo nos indique que por ahora el número puede ser primo.

Cuando se sale del bucle (línea 11) o bien Divisor ha alcanzado a


PosiblePrimo, en cuyo caso el número es primo (Primo es true), o bien la variable
Primo ha tomado el valor false, en cuyo caso PosiblePrimo no es un número primo.

Detalle de la ejecución de BucleWhile3:

Antes del while Primo almacena Divisor almacena el


el valor true valor 2
Primera Primo almacena Divisor almacena el La condición se evalúa
iteración el valor true valor 3 como true
Segunda Primo almacena Divisor almacena el La condición se evalúa
iteración el valor true valor 4 como true
.......................... ............................. ...................................... .........................................
.........
Iteración 15 Primo almacena Divisor almacena el La condición se evalúa
el valor true valor 17 como false

Problema de logística

El problema que aquí se plantea ha sido resuelto en la lección anterior


haciendo uso del bucle for, la solución utilizando el bucle while es muy parecida y
mucho más apropiada: supongamos que una importante empresa de
electrodomésticos nos contrata para resolver problemas de logística. El primer caso
práctico que nos plantean es el siguiente:

En las grandes ciudades el precio del suelo es muy caro, por lo que comprar
o alquilar grandes superficies de almacenamiento de electrodomésticos resulta
prohibitivo en el centro de la ciudad. La solución es alejarse del núcleo urbano, sin
34 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

embargo, cuanto más nos aleja mos, más nos cuesta el precio de distribución que
cada día hay que abonar a los transportistas que nos trasladan los electrodomésticos
de la periferia al centro (donde se realizan la mayoría de las compras).

La estrategia que adoptaremos es la siguiente:


1 Adquirir un almacén pequeño en el centro de la ciudad (para 200
electrodomésticos, por término medio).
2 Adquirir almacenes en anillos concéntricos de 5 kilómetros a partir
del centro de la ciudad, cada almacén podrá contener un stock del
doble de electrodomésticos que el almacén anterior (es decir, de 400
electrodomésticos a 5 Km. del núcleo urbano, de 800
electrodomésticos a 10 kilómetros, etc.).

Se pide: indicar a cuantos kilómetros se encontrará el último almacén en una


ciudad que requiere una capacidad total de 100000 electrodomésticos en stock. Los
100000 electrodomésticos estarán repartidos entre todos los almacenes adquiridos.

Solución:

1 public class LogisticaWhile {


2 public static void main (String[] args) {
3 int CapacidadAlmacen = 200, Km = 0;
4 int CapacidadTotal = CapacidadAlmacen;
5 do {
6 CapacidadAlmacen = CapacidadAlmacen * 2;
7 CapacidadTotal = CapacidadTotal + CapacidadAlmacen;
8 Km = Km +5;
9 } while(CapacidadTotal<100000);
10 System.out.println(Km+": "+CapacidadTotal);
11 }
12 }

En la línea 3 establecemos la capacidad del primer almacén


(CapacidadAlmacen) a 200 y los kilómetros de distancia (Km) a 0, en la línea 4
establecemos la capacidad total (CapacidadTotal) que por ahora tenemos como la
capacidad del primer almacén (el del centro urbano). Las líneas 5 y 9 codifican un
bucle que itera mientras la capacidad acumulada (CapacidadTotal) sea menor que
100000.

La línea 6 actualiza la capacidad del siguiente almacén como el doble de la


capacidad del almacén actual. La línea 7 acumula en la variable CapacidadTotal el
número de electrodomésticos que sumaban los almacenes anteriores con los que
puede albergar el almacén actual. La línea 8 actualiza los kilómetros a los que se
encuentra el primer almacén.
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 2: ESTRUCTURAS DE CONTROL 35

El resultado de la ejecución del programa nos muestra 40 kilómetros y una


capacidad acumulada de 102200 electrodomésticos.

2.3 LA INSTRUCCIÓN CONDICIONAL IF

Para poder programar aplicaciones no nos basta con ejecutar instrucciones


secuencialmente, ni siquiera aunque tengamos la posibilidad de definir bucles;
también resulta esencial poder tomar decisiones en base a condiciones. Las
instrucciones condicionales nos permiten ejecutar distintas instrucciones en base a la
evaluación de condiciones.

2.3.1 Sintaxis

La instrucción if puede emplearse de diversas maneras:

if (condición)
Instrucción

if (condición) {
Instrucciones
}

if (condición)
Instrucción de la rama “then”
else
Instrucción de la rama “else”

if (condición) {
Instrucciones de la rama “then”
} else {
Instrucciones de la rama “else”
}

En el primer caso, la instrucción se ejecuta sólo si la condición se evalúa


como true. En el segundo caso, el conjunto de instrucciones sólo se ejecuta si la
condición se evalúa como true. Como el lector habrá observado, el primer caso no es
más que una situación particular del segundo, en el que, al existir una sola
instrucción se pueden omitir las llaves (tal y como hacíamos con los bucles).
36 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

En las dos últimas sintaxis de la instrucción if, se introduce una “rama” else,
cuyo significado es: si la condición se evalúa como true se ejecuta el grupo de
instrucciones de la primera rama (llamémosla “then”), en caso contrario (la
condición se evalúa como false) se ejecuta el grupo de instrucciones de la segunda
rama (la rama “else”).

Obviamente se pueden programar situaciones en las que sólo hay una


instrucción en la rama “then” y varias en la rama “else” o viceversa. En general
podemos tomar como sintaxis la del último caso de los 4 presentados, sabiendo que
la rama “else” es opcional y que si sólo existe una instrucción en alguna rama del if,
podemos prescindir del uso de las llaves en esa rama.

2.3.2 Ejemplos de aprendizaje

El primer ejemplo presenta la forma más simple de instrucción condicional.


Establecemos una condición sencilla y una instrucción que se ejecuta si la condición
se evalúa como cierta.

1 public class If1 {


2 public static void main (String[] args) {
3 int EdadJuan = 20, EdadAna =25;
4
5 if (EdadJuan<EdadAna)
6 System.out.println ("Juan es mas joven que Ana");
7 }
8 }

Si empleamos las dos ramas del if:

1 public class If2 {


2 public static void main (String[] args) {
3 int EdadJuan = 20, EdadAna =25;
4
5 if (EdadJuan<EdadAna)
6 System.out.println ("Juan es mas joven que Ana");
7 else
8 System.out.println ("Juan no es mas joven que Ana");
9 }
10 }

Cuando necesitamos más de una instrucción en alguna rama, no debemos


olvidar las llaves que delimitan estas instrucciones:
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 2: ESTRUCTURAS DE CONTROL 37

1 public class If3 {


2 public static void main (String[] args) {
3 float Presion = 2.3f;
4
5 if (Presion > 2f) {
6 System.out.println ("Abrir valvula de seguridad");
7 System.out.println ("Reducir la temperatura");
8 } else
9 System.out.println ("Todo en orden");
10 }
11 }

Las condiciones pueden programarse todo lo complejas que sea necesario:

1 public class If4 {


2 public static void main (String[] args) {
3 float Presion = 2.3f, Temperatura = 90f;
4
5 if (Presion > 2f && Temperatura > 200f) {
6 System.out.println ("Abrir valvula de seguridad");
7 System.out.println ("Reducir la temperatura");
8 System.out.println ("Llamar a los bomberos");
9 } else
10 System.out.println ("Todo en orden");
11 }
12 }

2.3.3 if anidados

En muchas ocasiones resulta conveniente insertar un if dentro de otro if. Si,


por ejemplo, quisiéramos saber si Juan es mayor, menor o de la misma edad que
Ana, normalmente recurriríamos a la utilización de if anidados:

1 public class If5 {


2 public static void main (String[] args) {
3 int EdadJuan = 30, EdadAna =25;
4
5 if (EdadJuan<EdadAna)
6 System.out.println ("Juan es mas joven que Ana");
7 else
8 if (EdadJuan==EdadAna)
9 System.out.println ("Juan tiene la edad de Ana");
10 else
38 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

11 System.out.println ("Juan es mayor que Ana");


12 }
13 }

En If5, en la línea 3, se declaran dos variables de tipo int y se inicializan a 30


y 25; representarán las edades de Juan y de Ana. En la línea 5 se pregunta si la edad
de Juan es menor que la de Ana; si lo es, se imprime el mensaje adecuado, y si no,
pueden ocurrir dos cosas: que tengan la misma edad o que Ana sea mayor; esto
obliga a realizar en este punto una nueva pregunta: ¿Tienen la misma edad?, la línea
8 realiza esta pregunta dentro de la rama else del primer if.

El ejemplo anterior se podría haber resuelto sin hacer uso de sentencias


condicionales anidadas:

1 public class If6 {


2 public static void main (String[] args) {
3 int EdadJuan = 30, EdadAna =25;
4
5 if (EdadJuan < EdadAna)
6 System.out.println ("Juan es mas joven que Ana");
7
8 if (EdadJuan > EdadAna)
9 System.out.println ("Juan es mayor que Ana");
10
11 if (EdadJuan == EdadAna)
12 System.out.println ("Juan tiene la edad de Ana");
13
14 }
15 }

La solución de la clase If6 resuelve el mismo problema que el ejemplo de la


clase If5; además la solución es más legible y fácil de depurar en caso de errores, sin
embargo es importante darse cuenta de que en este último caso el ordenador siempre
tiene que evaluar tres condiciones, mientras que en la solución aportada en If5 basta
con evaluar 1 ó 2 condiciones (dependiendo de las edades de Juan y Ana), por lo
cual los if anidados proporcionan una solución más eficiente.

2.3.4 Situaciones erróneas

Utilizando instrucciones if, los principales errores de programación que se


cometen son:
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 2: ESTRUCTURAS DE CONTROL 39

Situación errónea Comentario


Omisión de los paréntesis en las Los paréntesis son sintácticamente obligatorios en las
condiciones condiciones de todas las instrucciones
Confundir el operador relacional Este es un error muy típico y difícil de detectar,
== con el operador de recordar que en las asignaciones se utiliza un símbolo
asignación = de igual y en las comparaciones dos
Colocar mal los if anidados Es conveniente repasar las llaves que se colocan en
cada rama de un if anidado
No tener en cuenta el orden de Ante cualquier duda utilizar paréntesis
precedencia de los operadores
en las condiciones

El segundo de los errores es tan común que merece la pena comentarlo en


más detalle. En la línea 8 de la clase If5 utilizamos el operador de comparación
(==). Si en esta línea, por error, hubiéramos introducido el operador de asignación
(=), obtendríamos un error de compilación (que nos facilitaría la depuración del
código). En otros contextos no seríamos tan afortunados y se realizaría la asignación
desvirtuando la comparación.

2.3.5 Ejemplo de resolución de problemas

Encontrar el menor de tres valores

Dados tres valores A, B y C, podemos determinar cual es el menor


realizando las preguntas anidadas pertinentes. Basta con evaluar dos condiciones
para obtener un resultado válido:

1 public class If7 {


2 public static void main (String[] args) {
3 int A = 10, B = 5, C = 20;
4
5 if (A < B)
6 if (A < C)
7 System.out.println ("A es el menor");
8 else
9 System.out.println ("B es el menor");
10 else
11 if (C < B)
12 System.out.println ("C es el menor");
13 else
14 System.out.println ("B es el menor");
15 }
16 }
40 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

Una solución más fácil de entender, pero mucho más costosa en ejecución
(por el número de condiciones a evaluar) es:

1 public class If8 {


2 public static void main (String[] args) {
3 int A = 10, B = 5, C = 20;
4
5 if (A <= B && A <= C)
6 System.out.println ("A es el menor");
7
8 if (B <= A && B <= C)
9 System.out.println ("B es el menor");
10
11 if (C <= A && C <= B)
12 System.out.println ("C es el menor");
13
14 }
15 }

2.4 LA INSTRUCCIÓN CONDICIONAL SWITCH

Cuando en una condición existen diversas posibilidades, nos vemos


obligados a programar usando if anidados, lo que complica la realización y
depuración de código. Para facilitar la programación en estas situaciones, se
proporciona la instrucción condicional switch, que permite definir un número
ilimitado de ramas basadas en una misma condición.

2.4.1 Sintaxis

switch (expresión) {
case valor1:
Instrucciones;
break;

case valor1:
Instrucciones;
break;

...........................
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 2: ESTRUCTURAS DE CONTROL 41

default:
Instrucciones;
break;
}

Cuando el flujo de control del programa llega a la instrucción switch, lo


primero que se hace es evaluar la expresión, después se va comparando el valor de
cada cláusula case con el resultado de la evaluación de la expresión. Cuando en una
cláusula case coinciden los valores, se ejecutan las instrucciones asociadas hasta
alcanzar la sentencia break. Si no se incluye el break en un case, se ejecutan todas
las instrucciones siguientes (correspondientes a los siguie ntes grupos case) hasta que
se encuentra un break o se termina la instrucción switch.

La cláusula default es muy útil, nos sirve para indicar que se ejecuten sus
instrucciones asociadas en el caso de que no se haya ejecutado previamente ningún
otro grupo de instrucciones.

La sentencia break asociada al último case (o default) no es necesaria,


puesto que el final de la ejecución de instrucciones del switch viene marcado tanto
por las instrucciones break como por el fin físico de la instrucción switch.

Es importante tener en cuenta que la expresión asociada a la instrucción


switch sólo debe generar valores de tipo: char, byte , short o int.

2.4.2 Ejemplos de aprendizaje

Nuestro primer ejemplo (Switch1) nos muestra una instrucción switch (línea
5) con una expresión de tipo int. Según el valor de la expresión sea 1, 2 ó 3, se
imprimirán diferentes mensajes. Cuando el valor de la expresión es diferente a 1, 2 y
3 (línea 18) se imprime un mensaje genérico (línea 19). En el ejemplo, si la
instrucción 12 no existiera, se imprimirían dos mensajes: “Medalla de plata” y
“Medalla de bronce”.

1 public class Switch1 {


2 public static void main (String[] args) {
3 int Puesto = 2;
4
5 switch (Puesto) {
6 case 1:
7 System.out.println("Medalla de oro");
8 break;
9
42 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

10 case 2:
11 System.out.println("Medalla de plata");
12 break;
13
14 case 3:
15 System.out.println("Medalla de bronce");
16 break;
17
18 default:
19 System.out.println("Gracias por participar");
20 break;
21
22 }
23
24 }
25 }

Nuestro segundo ejemplo implementa un control de accionamiento de


accesos, donde se puede ordenar la apertura, cierre o comprobación de circuitos de
una puerta. En un programa más amplio se le pediría al usuario la introducción de un
carácter que indique la acción requerida, digamos ‘a’: abrir, ‘c’: cerrar, ‘t’: test de
circuitos; en nuestro caso suplimos la introducción del comando por la línea 3.

La clase Switch2 es muy parecida a la clase Switch1, salvo en el tipo de


datos que utilizamos (char).

1 public class Switch2 {


2 public static void main (String[] args) {
3 char Caracter = 't';
4
5 switch (Caracter) {
6 case 'a':
7 System.out.println("Abrir puerta");
8 break;
9
10 case 'c':
11 System.out.println("Cerrar puerta");
12 break;
13
14 case 't':
15 System.out.println("Comprobar circuitos");
16 break;
17
18 default:
19 System.out.println("Opcion no contemplada");
20 break;
21
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 2: ESTRUCTURAS DE CONTROL 43

22 }
23
24 }
25 }

Podemos ampliar el ejemplo anterior para que admita también los comandos
en mayúsculas. Obsérvese la nueva sintaxis:

1 public class Switch3 {


2 public static void main (String[] args) {
3 char Caracter = 'C';
4
5 switch (Caracter) {
6 case 'a':
7 case 'A':
8 System.out.println("Abrir puerta");
9 break;
10
11 case 'c':
12 case 'C':
13 System.out.println("Cerrar puerta");
14 break;
15
16 case 't':
17 case 'T':
18 System.out.println("Comprobar circuitos");
19 break;
20
21 default:
22 System.out.println("Opcion no contemplada");
23 break;
24
25 }
26
27 }
28 }

La expresión del switch puede programarse todo lo compleja que sea


necesario:

1 public class Switch4 {


2 public static void main (String[] args) {
3 int Valor = 341;
4
5 switch ((4*Valor+17)%3) {
6 case 0:
44 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

7 System.out.println("Primera opcion");
8 break;
9
10 case 1:
11 System.out.println("Segunda opcion");
12 break;
13
14 case 2:
15 System.out.println("Tercera opcion");
16 break;
17
18 }
19
20 }
21 }

2.4.3 switch anidados

Resulta muy habitual tener que realizar selecciones basadas en dos niveles;
el siguiente ejemplo muestra una de estas situaciones: estamos realizando las
páginas Web de un concesionario de vehículos de la marca SEAT y en un momento
dado el usuario puede escoger entre las siguientes opciones:

Amarillo Blanco Rojo


Ibiza SI SI SI
Córdoba NO SI NO
Toledo NO SI SI

Para programar la elección de opción por parte del usuario, resulta adecuado
emplear una instrucción switch anidada:

1 public class Switch5 {


2 public static void main (String[] args) {
3 char Modelo = 'c', Color = 'r';
4 boolean Disponible = false;
5
6 switch (Modelo) {
7 case 'i':
8 switch (Color){
9 case 'a':
10 Disponible = true;
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 2: ESTRUCTURAS DE CONTROL 45

11 break;
12 case 'b':
13 Disponible = true;
14 break;
15 case 'r':
16 Disponible = true;
17 break;
18 default:
19 System.out.println("Opcion no contemplada");
20 break;
21 }
22 break;
23
24 case 'c':
25 switch (Color) {
26 case 'a':
27 Disponible = false;
28 break;
29 case 'b':
30 Disponible = true;
31 break;
32 case 'r':
33 Disponible = false;
34 break;
35 default:
36 System.out.println("Opcion no contemplada");
37 break;
38 }
39 break;
40
41 case 't':
42 switch (Color) {
43 case 'a':
44 Disponible = false;
45 break;
46 case 'b':
47 Disponible = true;
48 break;
49 case 'r':
50 Disponible = true;
51 break;
52 default:
53 System.out.println("Opcion no contemplada");
54 break;
55 }
56 break;
57
58 default:
59 System.out.println("Opcion no contemplada");
46 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

60 break;
61 }
62
63 if (Disponible)
64 System.out.println("Opcion disponible");
65 else
66 System.out.println("Opcion no disponible");
67
68 }
69 }

En el ejercicio anterior (Switch5) la primera instrucción switch consulta el


modelo del vehículo (‘i’: Ibiza, ‘c’:Córdoba, ‘t’: Toledo), los switchs anidados se
encargan de tratar la opción de color seleccionada (‘a’: amarillo, ‘b’: blanco,
‘r’:rojo). Aunque este ejercicio muestra adecuadamente la manera de anidar
instrucciones switch, posiblemente el lector preferirá la siguiente solución:

1 public class Switch6 {


2 public static void main (String[] args) {
3 char Modelo = 'c', Color = 'r';
4
5 if ( (Modelo=='c' && (Color=='a' || Color=='r')) ||
6 (Modelo=='t' && Color=='a')
7 )
8 System.out.println("Opcion no disponible");
9 else
10 System.out.println("Opcion disponible");
11 }
12 }

2.4.4 Situaciones erróneas

Utilizando instrucciones switch, los principales errores de programación que


se cometen son:
Situación errónea Comentario
Omisión de los paréntesis en las Los paréntesis son sintácticamente obligatorios en las
condiciones condiciones de todas las instrucciones
Utilización de tipos no Sólo se permiten los tipos char, byte, short e int.
permitidos en la expresión
Olvidar alguna sentencia break Si olvidamos una sentencia break , la ejecución de las
instrucciones pasa a la siguiente cláusula case (en caso
de que la haya)
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 2: ESTRUCTURAS DE CONTROL 47

2.4.5 Ejemplo de resolución de problemas

Realizar un tratamiento cada día de la semana

La estructura de la instrucción switch es muy sencilla en nuestro ejemplo,


basta con una clá usula case por cada día laborable y una cláusula default para el
sábado y el domingo. También podríamos haber puesto dos cláusulas case para los
días 6 y 7 y emplear la opción default para tratar el posible error en el que
DiaSemana contenga un valor distinto del 1 al 7.

1 public class Switch7 {


2 public static void main (String[] args) {
3 byte DiaSemana = 4;
4
5 switch (DiaSemana) {
6 case 1:
7 System.out.println("¡Que duro es el lunes!");
8 break;
9
10 case 2:
11 System.out.println("Dia de jugar al squash");
12 break;
13
14 case 3:
15 System.out.println("Mitad de la semana");
16 break;
17
18 case 4:
19 System.out.println("Famoso jueves del soltero");
20 break;
21
22 case 5:
23 System.out.println("¡Viernes bendito!");
24 break;
25
26 default:
27 System.out.println("El fin de semana a la playa");
28 break;
29
30 }
31 }
32 }
48 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

Determinar el número de días de un mes

En la clase Switch8, se definen las variables que utilizamos en el programa


(Mes del que queremos obtener los días, NumDias donde el programa devuelve el
resultado esperado y el dato de si el año es o no Bisisesto, que se supone
actualizado). En primer lugar comprobamos que la variable Mes tiene un valor
adecuado (en la línea 6) y si es así entramos en la instrucción switch, que tiene tres
grupos de instrucciones: las correspondientes a febrero, las asignadas a abril, junio,
septiembre y noviembre (los meses de 30 días) y las correspondientes al resto de los
meses (con 31 días).

1 public class Switch8 {


2 public static void main (String[] args) {
3 byte Mes = 2, NumDias = 0;
4 boolean Bisisesto = false;
5
6 if (Mes>=1 && Mes<=12)
7 switch (Mes) {
8 case 2:
9 if (Bisisesto)
10 NumDias = 29;
11 else
12 NumDias = 28;
13 break;
14 case 4:
15 case 6:
16 case 9:
17 case 11:
18 NumDias = 30;
19 break;
20 default:
21 NumDias = 31;
22 break;
23 }
24 System.out.println(NumDias);
25 }
26 }

Este programa pude hacerse más legible utilizando nombres de constantes.


En Switch9 definimos las constantes (con atributo final) ENERO, FEBRERO, etc.
(líneas 3, 4 y 5) y posteriormente las empleamos en las cláusulas case:

1 public class Switch9 {


2 public static void main (String[] args) {
3 final byte ENERO=1,FEBRERO=2,MARZO=3,ABRIL=4,
MAYO=5,JUNIO=6,JULIO=7,AGOSTO=8,SEPTIEMBRE=9,
OCTUBRE=10,NOVIEMBRE=11,DICIEMBRE=12;
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 2: ESTRUCTURAS DE CONTROL 49

4 byte Mes = 2;
5 boolean Bisisesto = false;
6
7 switch (Mes) {
8 case FEBRERO:
9 if (Bisisesto)
10 System.out.println(29);
11 else
12 System.out.println(28);
13 break;
14
15 case ABRIL:
16 case JUNIO:
17 case SEPTIEMBRE:
18 case NOVIEMBRE:
19 System.out.println(30);
20 break;
21
22 case ENERO:
23 case MARZO:
24 case MAYO:
25 case JULIO:
26 case AGOSTO:
27 case OCTUBRE:
28 case DICIEMBRE:
29 System.out.println(31);
30 break;
31
32 default:
33 System.out.println("Numero de mes erroneo");
34 break;
35 }
36 }
37 }

2.5 EJEMPLOS
En esta lección se realizan tres ejemplos en los que se combinan buena parte
de los conceptos explicados en las lecciones anteriores. Los ejemplos seleccionados
son:
1. Cálculo de la hipotenusa de un triángulo
2. Soluciones de una ecuación de segundo grado
50 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

3. Obtención del punto de corte de dos rectas en el espacio


bidimensional

Para permitir que el usuario pueda seleccionar, en cada caso, los parámetros
necesarios (longitudes de los catetos, coordenadas de los vectores en las rectas, etc.)
se proporciona una clase de entrada de datos por teclado desarrollada por el autor: la
clase Teclado, que consta de los métodos:
• public static String Lee_String()
• public static long Lee_long()
• public static int Lee_int()
• public static int Lee_short()
• public static byte Lee_byte()
• public static float Lee_float()
• public static double Lee_double()

De esta manera, para leer un valor de tipo byte, podemos utilizar una
instrucción similar a: byte Edad = Teclado.Lee_byte();

En estos ejemplos también será necesario hacer uso de algún método de la


biblioteca matemática que proporciona el entorno de desarrollo de Java. Esta
biblioteca tiene como nombre Math , y entre sus métodos se encuentran:
• public static double cos(double a) → coseno
• public static double sin(double a) → seno
• public static double sqrt(double a) → raíz cuadrada
• public static double pow(double a, double b) → a elevado a b
• etc.

2.5.1 Cálculo de la hipotenusa de un triángulo

En este ejercicio se pretende calcular la hipotenusa (H) de un triángulo


equilátero en donde el usuario nos proporciona como datos (por teclado) el valor de
cada uno de sus catetos (C1 y C2). No es necesario tener en cuenta ninguna
condición de datos de entrada erróneos.

H = √ C12 * C22
C1
C2
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 2: ESTRUCTURAS DE CONTROL 51

Plantilla para comenzar el código

1 public class Hipotenusa {


2 public static void main(String[] args) {
3 }
4 }

El código de la plantilla se corresponde a la estructura mínima necesaria


para ejecutar un programa situado dentro del método main.

Ejercicio resuelto

1 public class Hipotenusa {


2 public static void main(String[] args) {
3 System.out.println("Introduce el primer cateto: ");
4 double Cateto1 = Teclado.Lee_double();
5 System.out.println("Introduce el segundo cateto: ");
6 double Cateto2 = Teclado.Lee_double();
7
8 double Hipotenusa =
Math.sqrt(Cateto1*Cateto1+Cateto2*Cateto2);

9 System.out.println("La hipotenusa mide: "


+ Hipotenusa);
10 }
11 }

Comentarios

En las líneas 4 y 6 se introduce el valor de los dos catetos del triángulo,


haciendo uso de nuestra clase de entrada de datos Teclado. Los valores que
introduce el usuario se almacenan en las variables Cateto1 y Cateto2, ambas de tipo
double para posibilitar su uso directo en los métodos de la clase Math.

La línea de código número 8 introduce en la variable Hipotenusa, de tipo


double, el resultado de la operación: raíz cuadrada de la suma de los catetos al
cuadrado. La clase Math (java.lang.Math ) permite realizar operaciones numéricas
básicas, tales como funciones trigonométricas.
52 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

Resultados

2.5.2 Obtención del punto de corte de dos rectas situadas en el


espacio bidimensional

La determinación del punto de corte de dos rectas es


matemáticamente muy fácil de enunciar: una recta definida por dos puntos (x1,y1) y
(x2,y2) se puede representar de la siguiente manera:

X1, Y1

X2, Y2

Cualquier punto de la recta puede obtenerse multiplicando un número real por los
vectores (X2-X1) e (Y2-Y1):

X1, Y1

X2, Y2

De esta manera, la ecuación de la recta, en notación paramétrica puede ser


definida como:
• X = X1 + (X2-X1) * t
• Y = Y1 – (Y2-Y1) * t
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 2: ESTRUCTURAS DE CONTROL 53

Siendo t el valor real a partir del cual podemos obtener cualquier punto de la
recta. Si dibujamos dos rectas no paralelas:

X4, Y4 X = X1 + (X2-X1) * t
Y = Y1 – (Y2-Y1) * t
X1, Y1

X3, Y3 X2, Y2 X = X3 + (X4-X3) * r


Y = Y4 – (Y4-Y3) * r

El punto de corte es el que cumple que la X y la Y en las dos rectas son los
mismos:
X1 + (X2-X1) * t = X3 + (X4-X3) * r
Y1 – (Y2-Y1) * t = Y4 – (Y4-Y3) * r

Despejando:

t = [(Y4-Y3)*(X3-X1) – (Y3-Y1)*(X4-X3)] / [(Y4-Y3)*(X2-X1)-(Y2-


Y1)*(X4-X3)]

Ejercicio resuelto

1 public class PuntoDeCorte {


2 public static void main (String[] args) {
3
4 int X1, X2, Y1, Y2, X3, X4, Y3, Y4;
5 System.out.print("Introduce el valor de X1:");
6 X1 = Teclado.Lee_int();
7 System.out.println();
8
9 System.out.print("Introduce el valor de X2:");
10 X2 = Teclado.Lee_int();
11 System.out.println();
12
13 System.out.print("Introduce el valor de Y1:");
14 Y1 = Teclado.Lee_int();
15 System.out.println();
16
17 System.out.print("Introduce el valor de Y2:");
18 Y2 = Teclado.Lee_int();
19 System.out.println();
20
21 System.out.print("Introduce el valor de X3:");
54 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

22 X3 = Teclado.Lee_int();
23 System.out.println();
24
25 System.out.print("Introduce el valor de X4:");
26 X4 = Teclado.Lee_int();
27 System.out.println();
28
29 System.out.print("Introduce el valor de Y3:");
30 Y3 = Teclado.Lee_int();
31 System.out.println();
32
33 System.out.print("Introduce el valor de Y4:");
34 Y4 = Teclado.Lee_int();
35 System.out.println();
36
37 int Denominador = (Y4-Y3)*(X2-X1) - (Y2-Y1)*(X4-X3);
38 if (Denominador == 0)
39 System.out.println("Las rectas son paralelas");
40 else {
41 int Numerador = (Y4-Y3)*(X3-X1) - (Y3-Y1)*(X4-X3);
42 float t = (float) Numerador / (float) Denominador;
43 float CorteX = X1 + (X2-X1)*t;
44 float CorteY = Y1 + (Y2-Y1)*t;
45 System.out.println("Punto de corte: " + CorteX +
46 ", "+ CorteY);
47 }
48
49 }
50 }

Comentarios

En la línea 4 se declaran los 8 valores enteros que determinarán a las dos


rectas. Entre las líneas 5 y 35 se pide al usuario que introduzca el valor de cada
coordenada de cada punto de las rectas; obsérvese el uso de la clase Teclado. En la
línea 37 se declara y calcula el denominador de la ecuación necesaria para obtener el
parámetro t. Si el denominador es cero, no existe punto de corte, porque las rectas
son paralelas (líneas 38 y 39), en caso contrario, calculamos el numerador (línea 41)
y el parámetro t (línea 42), teniendo en cuenta que t no es entero, sino real (float).
Finalmente, conocido t, podemos obtener las coordenadas X e Y correspondientes al
punto de corte que nos piden (líneas 43 y 44).
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 2: ESTRUCTURAS DE CONTROL 55

Resultados

2.5.3 Soluciones de una ecuación de segundo grado

Dada una ecuación genérica de grado 2: aX2 + bX + c = 0, sus soluciones


son: X = -b ± (√ (b2 –4ac)) / 2a

Una ecuación de grado 2 define una parábola, que puede no cortar el eje x,
cortarlo en un único punto o bien cortarlo en dos puntos.

Ejercicio resuelto

1 public class EcuacionGrado2 {


2 public static void main (String[] args) {
3
4 double a, b, c;
5 System.out.print("Introduce el valor de a:");
6 a = Teclado.Lee_double();
7 System.out.println();
8
9 System.out.print("Introduce el valor de b:");
10 b = Teclado.Lee_double();
11 System.out.println();
12
13 System.out.print("Introduce el valor de c:");
14 c = Teclado.Lee_double();
15 System.out.println();
16
17
18 double Auxiliar = b*b-4d*a*c;
56 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

19 if (Auxiliar<0)
20 System.out.println("No existen raices reales");
21 else
22 if (Auxiliar == 0d) {
23 System.out.print("Solo existe una raiz: ");
24 System.out.println(-b/2d*a);
25 } else { // Auxiliar mayor que cero
26 Auxiliar = Math.sqrt(Auxiliar);
27 double Raiz1 = (-b + Auxiliar) / (2 * a);
28 double Raiz2 = (-b - Auxiliar) / (2 * a);
29 System.out.println("Raices: " + Raiz1 +
30 ", "+ Raiz2);
31 }
32
33 }
34 }

Comentarios

En la línea 4 se declaran los parámetros de la ecuación: a, b y c. Las líneas 5


a 15 se emplean para que el usuario pueda introducir los valores deseados en los
parámetros. La línea 18 declara y define el valor de una variable Auxiliar que
contendrá el término sobre el que hay que calcular la raíz cuadrada. Si Auxiliar es
menor que cero, no existen soluciones reales de la ecuación (la parábola se
encuentra encima del eje X); si Auxiliar es cero existe una única solución (líneas 22
a 24) y por fin, si Auxiliar es mayor que cero, existen dos soluciones (dos puntos de
corte de la parábola con el eje X), que se calculan en las líneas 26 a 28.

Resultados
CAPÍTULO 3

MÉTODOS Y ESTRUCTURAS DE
DATOS

3.1 MÉTODOS
Los métodos (procedimientos, funciones, subrutinas) nos permiten
encapsular un conjunto de instrucciones de manera que puedan ser ejecutadas desde
diferentes puntos de la aplicación. Por ejemplo, puede resultar útil crear un método
que convierta de libras a kilos, de manera que, cada vez que se necesite realizar esta
conversión se pueda invocar al método sin preocuparse de los detalles con los que
está implementado.

Cuando se utiliza un método ya creado, se realiza una llamada al mismo,


provocando la ejecución de sus instrucciones y devolviendo, posteriormente, el flujo
de control al programa que llama al método. Gráficamente:

Obviando en este momento los detalles sintácticos, la evolución del flujo es


el siguiente:
58 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

1. Se ejecutan las instrucciones del programa llamante hasta llegar a la llamada al


método
2. Se hace la llamada al método (En el ejemplo “ConvierteAKilos” )
3. Se ejecutan las instrucciones del método
4. Se traspasa el valor devuelto (si lo hay) al programa llamante. En nuestro
ejemplo se asigna a la variable PesoEnKilos.
5. Se continúa la ejecución del programa principal a partir de la siguiente
instrucción a la llamada.

No todos los métodos devuelven valores al programa principal, sólo lo


hacen si es necesario. Por ejemplo, podemos crear una serie de métodos que dibujan
figuras geométricas en pantalla:

void DibujaCirculo(int XCentro, int YCentro, int Radio){


........
}

void DibujaRecta(int X1, int X2, int Y1, int Y2) {


.........
}
..........

Ninguno de los métodos anteriores necesitan devolver un valor al programa


que los llama. En este caso, en lugar de indicar un tipo concreto de dato antes del
nombre del método (como en ConvierteAKilos), se pone la palabra reservada void.

Los métodos pueden contener parámetros o no. El método ConvierteAKilos


contiene (entre los paréntesis) el parámetro Peso, de tipo float. Este parámetro se
utiliza como base para conocer las libras correspondientes al argumento que se
incluye en la llamada (en nuestro ejemplo PesoEnLibras). Aunque un método no
incluya parámetros es necesario mantener los paréntesis después del nombre, de esta
manera podemos diferenciar la llamada a un método de un nombre de variable: es
diferente Edad=EdadJuan que Edad=EdadJuan().

3.1.1 Sintaxis
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 3: MÉTODOS Y ESTRUCTURAS DE DATOS 59

Ejemplo:

No hay que confundir argumentos con parámetros; los parámetros son


variables que utiliza el método como valores de partida para sus cálculos. Su
visibilidad y ámbito (existencia) se limitan a los del propio método. Los argumentos
son valores que se establecen en el programa llamante y que se traspasan (por valor
o referencia como veremos más adelante) al método llamado.

La instrucción return Resultado; debe ser la última del método.

Cuando el método no devuelve un valor, como ya se ha comentado, hay que


poner la palabra reservada void como tipo de retorno. En el método podemos
prescindir de la instrucción return o utilizarla sin un valor asociado; es decir:
return;.

3.1.2 Ejemplo 1

El siguiente ejemplo (Metodo1) define un método que calcula el tamaño de


la hipotenusa de un triángulo equilátero en función del tamaño de sus dos catetos.
Este método es invocado dos veces por el programa principal.

1 public class Metodo1 {


2
3 static double Hipotenusa(double Cateto1, double Cateto2) {
4 double Auxiliar;
5 Auxiliar = Cateto1*Cateto1 + Cateto2*Cateto2;
6 return Math.sqrt(Auxiliar);
7 }
8
9 public static void main(String[] args) {
10 System.out.println(Hipotenusa(5.0d,3.0d));
11 System.out.println(Hipotenusa(9.0d,2.0d));
12 }
13 }
60 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

En la línea 3 se define el método Hipotenusa, que devuelve el tamaño de la


hipotenusa mediante un valor de tipo double . El atributo static se explicará con
detalle en las siguientes lecciones; su uso en el ejemplo es obligatorio. El método
tiene dos parámetros de tipo double: Cateto1 y Cateto2. En las líneas 5 y 6 se
calcula el valor de la hipotenusa, siendo en la línea 6 donde se devuelve el valor
hallado al programa llamante.

En el programa principal (main), se realizan dos llamadas al método


Hipotenusa: una en la línea 10 (con argumentos 5 y 3) y otra en la línea 11 (con
argumentos 9 y 2).

3.1.3 Resultados del ejemplo 1

3.1.4 Ejemplo 2

En el siguiente ejemplo se emplea un método que no devuelve ningún valor


(tipo de retorno void). El método imprime en consola el String que se le pasa como
argumento, precedido y seguido de dos asteriscos.

1 public class Metodo2 {


2
3 static void Imprimir(String Mensaje) {
4 System.out.println("** " + Mensaje + " **");
5 }
6
7 public static void main(String[] args) {
8 Imprimir("Hola amigo");
9 System.out.println("Hola amigo");
10 Imprimir("12.6");
11 }
12 }

En la línea 3 se define el método Imprimir, que no devuelve ningún valor y


que tiene como parámetro un String (estructura de datos que veremos en breve). Su
funcionalidad se codifica en la línea 4, imprimiéndose el parámetro Mensaje entre
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 3: MÉTODOS Y ESTRUCTURAS DE DATOS 61

asteriscos. En las líneas 8 y 10 del programa llamante (método main) se invoca al


método llamado con argumentos “Hola Amigo” y “12.6” (respectivamente).

3.1.5 Resultados del ejemplo 2

3.1.6 Paso de argumentos por valor y por referencia

Los argumentos de tipos básicos (primitivos) del lenguaje se pasan (a los


parámetros) por valor, esto significa que no se traspasan los propios datos, sino una
copia de los mismos. La idea central que hay que recordar, es que los argumentos de
tipo byte, short, int, long, char, float, double y boolean nunca se modifican en el
programa llamante (aunque sus copias varíen en el método llamado). En el siguiente
diagrama, EdadPedro es un argumento de tipo int, que se pasa por valor (copia) al
parámetro Edad del método Incrementar. El método varía su parámetro (que es una
copia del argumento EdadPedro), tomando el valor 24; este valor es el que se
devuelve al programa principal y se almacena en EdadPedroIncrementada. Tal y
como cabía esperar, al finalizarse la llamada, EdadPedro tiene el valor 23,
EdadPedroIncrementada tiene 24 y Edad ya no existe, puesto que el método
termino de ejecutarse y se liberó la memoria que utilizaba.

En el paso de argumentos por referencia, lo que se copia no es el valor del


argumento (en el siguiente ejemplo V1, V2, V3,... ), sino su apuntador (dirección) a
la estructura de datos; cuando decimos que se modifica el valor del parámetro,
realmente lo que queremos decir es: “se modifica el valor de la estructura de datos
donde apunta el parámetro”, que es el mismo lugar donde apunta el argumento.
62 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

Cuando se realiza un paso de argumentos por referencia, los argumentos


varían en la misma medida que varían los parámetros. Si queremos asegurarnos de
que los parámetros no puedan modificarse podemos declararlos con el atributo final.

3.2 STRINGS
Los Strings, también llamados literales o cadenas de caracteres, nos
permiten declarar, definir y operar con palabras y frases. Su utilización es muy
común en los programas, de hecho, nosotros, de una forma implícita los hemos
usado en todos los ejemplos anteriores al escribir frases en consola por medio del
método System.out.println(“El String a imprimir”); también declaramos una
estructura de datos basada en el String como parámetro del método main.

Los Strings no forman parte de los tipos nativos de Java, sino que existe una
clase String (java.lang.String).

3.2.1 Sintaxis

String InstanciaDeString = “Palabra o frase entre comillas dobles”;


String OtroString = "El chico llamo 'pesado' a su amigo...";
String NoVacio = “”;
String Vacio = null;

Un String se define como una serie de caracteres delimitados por comillas


dobles. Las comillas simples pueden formar parte del contenido del String.

Una instancia de un objeto String es una posición de memoria que apunta


hacia una estructura de datos que contiene el conjunto de caracteres que define el
String, de esta manera, un String declarado, pero sin definir, todavía no apunta hacia
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 3: MÉTODOS Y ESTRUCTURAS DE DATOS 63

ninguna estructura de datos; su valor es null. Esto es diferente a una instancia del
objeto String que apunta hacia un conjunto de caracteres vacío.

Es importante darse cuenta de que si tenemos dos instancias del objeto


String apuntando hacia contenidos idénticos, eso no significa que sean iguales:

String InstanciaDeString = “El amigo de los osos”;


String Greenpeace = “El amigo de los osos”;

(InstanciaDeString == Greenpeace) → falso

Dos Strings serán iguales (aplicando el operador de comparación) si apuntan


hacia la misma estructura de datos:

String Greenpeace = “El amigo de los osos”;


String Adena = Greenpeace;

(Adena == Greenpeace) → verdadero

Para poder comparar dos Strings por su contenido (y no por su referencia),


podemos utilizar el método equals que contiene la clase String. Este método
compara carácter a carácter los contenidos de las dos referencias suministradas.
64 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

Ejemplo:
boolean Iguales = InstanciaDeString.equals(Greenpeace);
System.out.println(Iguales); → verdadero

3.2.2 Ejemplo básico

El primer ejemplo que se proporciona implementa todos los conceptos


expuestos en el apartado anterior:

1 public class String1 {


2 public static void main (String[] args) {
3 String InstanciaDeString = "El amigo de los osos";
4 String OtroString = "El chico llamo 'pesado'
5 a su amigo...";
6 String Vacio = null;
7 String NoVacio = "";
8 String AnimalesPreferidos = "osos";
9 String Greenpeace = "El amigo de los " +
AnimalesPreferidos;
10 String Adena = Greenpeace;
11
12 System.out.println(InstanciaDeString);
13 System.out.println(OtroString);
14 System.out.println(Vacio);
15 System.out.println(NoVacio);
16 System.out.println(Greenpeace);
17 System.out.println(InstanciaDeString==Greenpeace);
18 System.out.println(Adena==Greenpeace);
19
20 boolean Iguales = InstanciaDeString.equals(Greenpeace);
21 System.out.println(Iguales);
22
23 }
24 }

A parte de los conceptos explicados en el apartado anterior, el ejemplo


incluye (en la línea 9) una característica fundamental de los Strings: se pueden
concatenar utilizando la operación suma (+); esta es la razón por la que hemos
podido poner en ejemplos anteriores líneas de código como:
System.out.println("Raices: " + Raiz1 + ", "+ Raiz2); en realidad estábamos
concatenando Strings (e implícitamente realizando conversiones de distintos tipos de
datos a String).
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 3: MÉTODOS Y ESTRUCTURAS DE DATOS 65

3.2.3 Resultado

3.2.4 Ejemplo de utilización de la clase String

El siguiente ejemplo muestra parte de los métodos más importantes de la


clase String, que nos sirven para obtener información y manipular objetos de este
tipo:

public int length(); Devuelve la longitud del String


public String toLowerCase(); Devuelve un String basado en el objeto base
convertido en minúsculas
public String toUpperCase(); Devuelve un String basado en el objeto base
convertido en mayúsculas
public String substring(int Devuelve el substring formado por los caracteres
Comienzo, int Final); situados entre las posiciones Comienzo y Final-1
(posiciones numeradas a partir de 0)
public int indexOf(char Devuelve la posición donde se encuentra la primera
Caracter, int Comienzo); ocurrencia de Carácter, comenzando la búsqueda a
partir de la posición Comienzo. Si no se encuentra
ninguna ocurrencia de Carácter, se devuelve el valor -
1

1 public class String2 {


2
3 public static void main(String[] args) {
4 String Frase = Teclado.Lee_String();
5 System.out.println(Frase.length());
6
7 String FraseMayusculas = Frase.toUpperCase();
8 System.out.println(FraseMayusculas);
9
10 String FraseMinusculas = Frase.toLowerCase();
11 System.out.println(FraseMinusculas);
12
13 String Caracteres3Al8 = Frase.substring(3,8);
66 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

14 System.out.println(Caracteres3Al8);
15
16 int NumPalabras=0, Posicion=0;
17
18 while(Posicion!=-1) {
19 Posicion = Frase.indexOf(" ",Posicion+1);
20 NumPalabras++;
21 }
22 System.out.println(NumPalabras);
23
24 }
25 }

En la línea 4 se introduce en la variable Frase una sentencia que el usuario


teclea. Inmediatamente (línea 8) se calcula su longitud, después se convierte en
mayúsculas (líneas 7 y 8), en minúsculas (líneas 10 y 11) y se obtiene el substring
situado entre las posiciones 3 y 7 (comenzando a contar desde cero). El bucle
definido en la línea 18 se encarga de contar las palabras que tiene la frase
introducida por el usuario; su funcionamiento se basa en el método indexOf aplicado
al carácter en blanco, que actúa como separador de palabras. El bucle termina
cuando no se hallan más caracteres en blanco (resultado –1).

3.2.5 Resultados

3.3 MATRICES (ARRAYS, VECTORES)


En muchas ocasiones es necesario hacer uso de un conjunto ordenado de
elementos del mismo tipo. Cuando el número de elementos es grande, resulta muy
pesado tener que declarar, definir y utilizar una variable por cada elemento.
Pongamos por caso la gestión de notas de una asignatura en una universidad, donde
puede haber 1000 alumnos matriculados en dicha asignatura. Sería inviable tener
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 3: MÉTODOS Y ESTRUCTURAS DE DATOS 67

que declarar 1000 variable diferentes, insertar las notas con 1000 instrucciones
diferentes, etc.

Para soluc ionar este tipo de situaciones, los lenguajes de programación


poseen estructuras de datos que permiten declarar (y utilizar) con un solo nombre un
conjunto de variables ordenadas de un mismo tipo; estas estructuras de datos son las
matrices.

3.3.1 Sintaxis

tipo[] Variable; // declara una matriz de variables del tipo indicado (ejemplo:
// float[] Notas;)
tipo Variable[]; // otra alternativa sintáctica para declarar la misma matriz
// que la línea anterior,
// en adelante utilizaremos únicamente la sintaxis de la primera línea

tipo[] Variable = new tipo[Elementos]; // declara y define una matriz de


//“Elementos” elementos y tipo “tipo”
// (ejemp lo: float[] Notas = new float[1000];

tipo[] [] [] ... Variable =


new tipo[ElementosDim1][ElementosDim2][ElementosDim3].....; //Matriz
// multidimensional

tipo[] Variable = {Valor1, Valor2, Valor3, .....}; // Declaración e inicialización de


// una matriz

Ejemplos:

float[] Temperaturas; // Se declara una matriz Temperaturas de elementos


// de tipo float
Temperaturas = new float[12]; // Se define la variable Temperaturas como
// una matriz de 12 elementos de tipo float

Obsérvese la utilización de la palabra reservada new, que se emplea para


crear un objeto del tipo especificado. Las dos instrucciones anteriores se podrían
fundir en:
float[] Temperaturas = new float[12]; // Temperaturas de los 12 meses del año
float[] [] Temperaturas = new float[12] [31]; // Temperaturas de cada día en los
// 12 meses del año
68 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

float[] TemperaturasMadrid = {3.4f, 5.6f, 17.5f, 19.2f, 21.0f, 25.6f, 35.3f, 39.5f,
25.2f, 10.0f, 5.7f, 4.6f};
String[] Nombres = {“Pinta”,”Niña”,”Santamaría”};

Este es el momento de poder entender el parámetro que siempre utilizamos


en el método main: String[] args, con el que definimos una matriz unidimensional
de Strings. El nombre de la matriz es args. En esta matriz se colocan los posibles
parámetros que deseamos pasar al invocar el programa.

3.3.2 Acceso a los datos de una matriz

En las matrices, los elementos se colocan de forma ordenada, linealmente,


numerando las posiciones individuales que componen la matriz a partir del valor de
índice 0. Por ejemplo, nuestra matriz Temperaturas se puede representar de la
siguiente manera:

float[] Temperaturas = new float[12]; // Temperaturas de los 12 meses del año

Para almacenar la temperatura media de febrero (pongamos 7.5 grados),


utilizamos la sentencia de asignación:
Temperaturas[1] = 7.5f;

Para almacenar las de enero y diciembre:


Temperaturas[0] = 5.3f;
Temperaturas[11]=6.5f;

En este momento, la matriz Temperaturas tiene los siguiente valores:

Para hallar la temperatura media de los meses de invierno:


float MediaInvierno = (Temperaturas[0] +Temperaturas[1] +
Temperaturas[2]) / 3f;
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 3: MÉTODOS Y ESTRUCTURAS DE DATOS 69

Una de las grandes ventajas que presentan las matrices es la posibilidad que
existe para recorrer sus elementos con una instrucción repetitiva:
for (int i=0; i<12; i++)
System.out.println(Temperaturas[i]);

3.3.3 Ejemplo 1

En el ejemplo Matriz1 se define una matriz unidimensional de notas y otra


de nombres, la primera de elementos de tipo float y la segunda de elementos de tipo
String, ambas matrices dimensionadas a 10 elementos. Se introducen los nombres y
las notas obtenidas, se imprime la lista y posteriormente se imprime el listado de
aprobados:

1 public class Matriz1 {


2 public static void main(String[] args) {
3 float[] Notas =
{5.8f,6.2f,7.1f,5.9f,3.6f,9.9f,1.2f,10.0f,4.6f,5.0f};
4
5 String[] Nombres = new String[10];
6 Nombres[0]="Pedro";Nombres[1]="Ana";Nombres[2]="Luis";
7 Nombres[3]="Luis";Nombres[4]="Juan";Nombres[5]="Eva";
8 Nombres[6]="Mari";Nombres[7]="Fran";Nombres[8]="Luz";
9 Nombres[9]="Sol";
10
11 for (int i=0;i<Nombres.length;i++)
12 System.out.println(Nombres[i] + " : " + Notas[i]);
13
14 System.out.println();
15
16 byte Aprobados = 0;
17 String NombresAprobados = new String();
18 for (int i=0;i<Nombres.length;i++)
19 if (Notas[i]>=5.0){
20 Aprobados++;
21 NombresAprobados = NombresAprobados+" "+Nombres[i];
22 }
23 System.out.println("Aprobados: " + Aprobados);
24 System.out.println("Aprobados:" + NombresAprobados);
25
26 }
27 }
70 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

En la línea 3 se declara la matriz Notas y se inicializa con 10 valores float


entre 0 y 10. En la línea 5 se declara la matriz Nombres, que contiene 10 variables
ordenadas de tipo String. En las líneas 6 a 9 se introducen los 10 nombres de los
alumnos.

La línea 11 implementa un bucle for que recorre el rango de posiciones de


índice entre 0 y 9. Obsérvese el uso de la propie dad length del objeto Array, que nos
permite conocer el número de elementos de una matriz unidimensional (en este caso
10). En la línea 12, dentro del bucle, se imprime el nombre y la nota de cada alumno.

Entre las líneas 16 y 22 se determina el número de alumnos que han


aprobado, guardándose sus nombres en el String: NombresAprobados, que ha sido
definido como un nuevo (new) objeto del tipo String (línea 17).

El bucle de la línea 18 itera 10 veces recorriendo la matriz Notas,


consultándose en cada iteración si la nota del alumno en cuestión (i) es un aprobado
(línea 19), en caso de que lo sea se incrementa un contador de aprobados: Aprobados
y se concatena el nombre del nuevo “afortunado” a la lista de los aprobados
anteriores (línea 21). Finalmente se imprimen los resultados (líneas 23 y 24).

3.3.4 Resultados del ejemplo 1

3.3.5 Ejemplo 2

Este ejemplo se basa en el anterior, aumentando sus funcionalidades. Se


proporciona un método Imprimir, que visualiza por consola los contenidos de los
elementos de la Matriz de tipo String que se le pasa como parámetro. También se
codifica el método EntreNotas, que admite como parámetros dos notas y una matriz
de floats, devolviendo el numero de elementos de la matriz que se encuentran en el
rango de notas sumin istrado.
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 3: MÉTODOS Y ESTRUCTURAS DE DATOS 71

El método Imprimir, que no devuelve ningún valor, se codifica entre las


líneas 3 y 6. El método EntreNotas, que devuelve un valor de tipo byte, se encuentra
entre las líneas 8 y 16; en la línea 11 se recorre toda la matriz de notas,
preguntándose en cada iteración si la nota se encuentra en el rango especificado
(línea 12). La variable Contador acumula el número de notas que cumplen las
condiciones.

En el programa principal (main) se declaran e inicializan las matrices de


nombres y notas, se hace una llamada al método Imprimir y tres al método
EntreNotas: una para obtener los aprobados, otra para los suspensos y la última para
las matrículas (líneas 28, 31 y 34).

1 public class Matriz2 {


2
3 public static void Imprimir(String[] Nombres) {
4 for (int i=0;i<Nombres.length;i++)
5 System.out.println(Nombres[i]);
6 }
7
8 public static byte EntreNotas(float Nota1, float Nota2,
9 float[] Notas) {
10 byte Contador = 0;
11 for (int i=1;i<Notas.length;i++){
12 if ((Notas[i]>=Nota1)&&(Notas[i]<=Nota2))
13 Contador++;
14 }
15 return Contador;
16 }
17
18 public static void main(String[] args) {
19 float[] Notas =
{5.8f,6.2f,7.1f,5.9f,3.6f,9.9f,1.2f,10.0f,4.6f,5.0f};
20 String[] Nombres = new String[10];
21 Nombres[0]="Pedro";Nombres[1]="Ana";Nombres[2]="Luis";
22 Nombres[3]="Rover";Nombres[4]="Juan";Nombres[5]="Eva";
23 Nombres[6]="Mari";Nombres[7]="Fran";
24 Nombres[8]="Luz";Nombres[9]="Sol";
25
26 Imprimir(Nombres);
27 System.out.println("Aprobados: ");
28 System.out.println(EntreNotas(5.0f,10.0f,Notas));
29
30 System.out.println("Suspensos: ");
31 System.out.println(EntreNotas(0.0f,4.9f,Notas));
32
33 System.out.println("Matriculas: ");
34 System.out.println(EntreNotas(10.0f,10.0f,Notas));
35
72 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

36 }
37 }

3.3.6 Resultados del ejemplo 2

3.3.7 Ejemplo

El último ejemplo de esta lección, basado en los anteriores, permite pasar


como parámetro una matriz bidimensional a los métodos Imprimir y EntreNotas.
También se utiliza una constante (atributo final) NUM_ALUMNOS, definida en la
línea 22, para dimensionar el tamaño de la matriz bidimenional.

La matriz se ha definido de tipo String, lo que encaja muy bien con los
campos que tienen que albergar nombres, sin embargo, las notas hay que
transformarlas de String a tipo float, lo que se realiza con facilidad gracias al método
parseFloat de la clase Float (líneas 12 y 13). La operación contraria también nos
resulta necesaria; en la línea 31 se convierte de float a String utilizando el método
toString de la clase Float.

Obsérvese como los argumentos de tipo matriz se colocan sin corchetes


(líneas 34, 37, 40 y 43), mientras que en los parámetros hay que definir tanto el tipo
(con corchetes) como el nombre del parámetro (líneas 3 y 9).

A lo largo de todo el ejemplo aparece la manera de utilizar una matriz


bidimensional, colocando adecuadamente los índices en cada dimensión
(delimitadas por corchetes).

1 public class Matriz3 {


2
3 public static void Imprimir(String[][] Alumnos) {
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 3: MÉTODOS Y ESTRUCTURAS DE DATOS 73

4 for (int i=0;i<Alumnos[0].length;i++)


5 System.out.println(Alumnos[0][i] +
": "+Alumnos[1][i]);
6 }
7
8 public static byte EntreNotas(float Nota1, float Nota2,
9 String[][] Alumnos) {
10 byte Contador = 0;
11 for (int i=0;i<Alumnos[0].length;i++){
12 if ((Float.parseFloat(Alumnos[1][i])>=Nota1)&&
13 (Float.parseFloat(Alumnos[1][i])<=Nota2)) {
14 Contador++;
15 System.out.println(Alumnos[0][i]+": "+
Alumnos[1][i]);
16 }
17 }
18 return Contador;
19 }
20
21 public static void main(String[] args) {
22 final byte NUM_ALUMNOS = 5;
23 String[][] Calificaciones = new String[2][NUM_ALUMNOS];
24
25
26 for (int i=0;i<NUM_ALUMNOS;i++){
27 System.out.println("Nombre: ");
28 Calificaciones[0][i] = Teclado.Lee_String();
29
30 System.out.println("Nota: ");
31 Calificaciones[1][i] =
Float.toString(Teclado.Lee_float());
32 }
33
34 Imprimir(Calificaciones);
35
36 System.out.println("Aprobados: ");
37 System.out.println(EntreNotas(5.0f,10.0f,
Calificaciones));
38
39 System.out.println("Suspensos: ");
40 System.out.println(EntreNotas(0.0f,4.9f,
Calificaciones));
41
42 System.out.println("Matriculas: ");
43 System.out.println(EntreNotas(10.0f,10.0f,
Calificaciones));
44
45 }
46 }
74 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

3.4 EJEMPLOS DE PROGRAMACIÓN


En esta lección se presentan tres ejemplos resueltos y comentados, en los
que se hace uso de la mayor parte de los conceptos explicados en las lecciones
anteriores. En el primero de estos ejemplos se calculan números primos, en el
segundo se generan claves utilizando bucles anidados y en el tercer ejemplo se hace
uso de métodos que admiten matrices lineales como argumentos.

3.4.1 Obtención de números primos

En este ejercicio se pretende obtener un listado de los ‘n’ primeros números


primos, siendo ‘n’ un valor que definimos como constante en el programa. Los
números primos deben guardarse en una matriz lineal de enteros.

La obtención de los números primos se basará en la siguiente propiedad:


para saber si un número es primo, no es necesario dividirlo por todos los valores
anteriores, basta con constatar que no es divisible por ningún número primo menor
que su propio valor, por ejemplo: el 17 es primo, porque no es divisible entre 2, 3, 5,
7 , 11 y 13 (que son los primos anteriores); no es necesario dividir 17 entre 4, 6, 8, 9,
10, 12, 14, 15 y 16 para saber que es primo.

Solución

1 public class GeneraPrimos {


2 public static void main (String[] args) {
3 final int NUM_PRIMOS = 60;
4 int[] Primos = new int[NUM_PRIMOS];
5 Primos[0] = 2;
6 int PrimosHallados = 1;
7 int PosiblePrimo = 3;
8 int Indice=0;
9 boolean Primo = true;
10
11 do {
12 while (Primo && (Indice<PrimosHallados)) {
13 if (PosiblePrimo % Primos[Indice] == 0)
14 Primo = false;
15 else
16 Indice++;
17 }
18
19 if (Primo) {
20 Primos[PrimosHallados] = PosiblePrimo;
21 PrimosHallados++;
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 3: MÉTODOS Y ESTRUCTURAS DE DATOS 75

22 }
23
24 Primo = true;
25 PosiblePrimo++;
26 Indice = 0;
27
28 } while (PrimosHallados<NUM_PRIMOS );
29
30 for (int i=0; i<NUM_PRIMOS;i++) {
31 System.out.print(Primos[i] + " ");
32 if (i % 10 ==0)
33 System.out.println();
34 }
35
36 }
37 }

La clase GeneraPrimos calcula los primeros NUM_PRIMOS (línea 3)


primos. Estos primos se irán almacenando en una matriz Primos de valores enteros
(línea 4). Como nuestro algoritmo se basa en dividir entre los primos anteriores,
introducimos el primer primo (el número 2) que nos servirá de base para ir
generando los demás; esta asignación se realiza en la línea 5. En la línea 6 se indica
que ya tenemos un número primo almacenado en la matriz.

La línea 7 nos inicializa un contador PosiblePrimo a 3, que es el siguiente


valor sobre el que tenemos que determinar si es primo. En la línea 8 se inicializa la
variable Indice, que nos indica el siguiente primo hallado (y almacenado en la matriz
Primos) por el que hay que dividir el valor PosiblePrimo. En la línea 9 se inicializa
una variable Primo que nos indicará en todo momento si PosiblePrimo va siendo
divisible entre los primos hallados hasta el momento.

El bucle situado en las líneas 11 y 28 va iterando hasta que se han hallado


los NUM_PRIMOS buscados. Cuando se sale de este bucle los primos han sido
calculados y, a continuación, en el bucle for de la línea 30 se imprimen en grupos de
10.

El bucle while de la línea 12 comprueba si PosiblePrimo es o no un número


primo,

La instrucción condicional de la línea 13 es el núcleo del programa: calcula


el resto de la división de PosiblePrimo con cada primo hallado previamente (y
almacenado en la matriz Primos). Si el resto es cero, PosiblePrimo no es un número
primo (Primo = false); si el resto es distinto de cero habrá que volver a probar con el
siguiente primo de nuestra matriz. Esta instrucción se ejecuta (en el while de la línea
12) mientras PosiblePrimo siga siendo primo (Primo es true) e Indice no haya
alcanzado el número de primos que hemos hallado previamente.
76 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

Del bucle anterior se puede salir por dos causas: que PosiblePrimo haya
pasado todas las comprobaciones de divisibilidad y por lo tanto es primo (Primo es
true), o que no haya pasado las comprobaciones y por lo tanto no es primo (Primo es
false). En el primer caso se actualiza la matriz Primos y el contador asociado:
PrimosHallados (líneas 20 y 21). En cualquier caso, al salir del bucle, se preparan
las variables para comprobar el siguiente valor en secuencia (líneas 24, 25 y 26).

Resultados

3.4.2 “Revienta claves”

Este ejemplo muestra como combinar caracteres, con el fin de generar todas
las posibles claves que se pueden utilizar con un juego de caracteres permitido y un
tamaño máximo establecido. El juego de caracteres que utilizaremos son los
números y letras (mayúsculas y minúsculas), junto a unos pocos caracteres
especiales de uso común.

Los caracteres de nuestro “abecedario” los podemos obtener con el siguiente


programa en Java:

1 public class RevientaClaves2 {


2
3 public static void main (String[] args) {
4
5 char Caracter = '0';
6 for (int i=1;i!=76;i++) {
7 System.out.print(Caracter + " ");
8 Caracter++;
9 }
10 }
11 }
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 3: MÉTODOS Y ESTRUCTURAS DE DATOS 77

Empezando por el carácter numérico ‘0’ (línea 5), imprimimos 75 caracteres


en secuencia. El resultado es:

Solución

1 public class RevientaClaves {


2
3
4 static boolean Permiso(String Clave) {
5 return (Clave.equals("0A:g"));
6 }
7
8
9 public static void main (String[] args) {
10
11 String Clave = "";
12 char Car1 = '0', Car2 = '0', Car3 = '0', Car4 = '0';
13 boolean Encontrada = false;
14
15 do {
16 do {
17 do {
18 do {
19 Clave = "" + Car1 + Car2 + Car3 + Car4;
20 System.out.println("*"+Clave+"*");
21 if (Permiso(Clave)) {
22 System.out.println ("Permiso concedido");
23 Encontrada = true;
24 }
25 Car4++;
26 } while (Car4 !=’z’ && !Encontrada);
27 Car4 = '0';
28 Car3++;
29 } while (Car3 !=’z’ && !Encontrada);
30 Car3 = '0';
31 Car2++;
32 } while (Car2 !=’z’ && !Encontrada);
33 Car2 = '0';
34 Car1++;
35 } while (Car1 !=’z’ && !Encontrada);
36
78 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

37
38 }
39 }

En las líneas 4 a 6 implementamos un método que simula la entrada al


sistema: la línea 5 compara el String que se introduce como parámetro con una clave
prefijada.

En la línea 11 se inicializa el String Clave, en el que introduciremos las


diferentes claves que generemos por programa, y con las que supuestamente
intentamos “introducirnos” en un sistema. En la línea 12 se inicializan 4 variables de
tipo carácter, con las que vamos a generar la clave (en este ejemplo, siempre de
longitud 4). En la línea 13 establecemos una variable lógica Encontrada, que
utilizaremos para finalizar las iteraciones cuando la clave se encuentre.

En las líneas 15, 16, 17 y 18 se inician 4 bucles, uno por cada carácter (1º,
2º, 3º, 4º) de cada clave generada, de tal forma que cada carácter va aumentando
(líneas 25, 28, 31 y 34) en cada iteración de su bucle correspondiente. Cada iteración
de un bucle exterior debe inicializar el carácter del bucle inferior (líneas 27, 30 y
33).

En el bucle más interno se intenta acceder al “sistema” (línea 21); cuando se


consigue se imprime un mensaje y se almacena el valor true en la variable
Encontrada (línea 23), con lo que los bucles dejan de iterar y el programa finaliza.

Resultado

3.4.3 Estadísticas

En este ejemplo se muestra, especialmente, como utilizar matrices lineales


de elementos. Se definen matric es de temperaturas medias de cada mes de un año en
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 3: MÉTODOS Y ESTRUCTURAS DE DATOS 79

Madrid y Granada (con valores inventados), también se define una matriz de 28


elementos que contiene presiones atmosféricas.

En el ejemplo se suministran tres métodos: uno para hallar la media de los


valores de la matriz que se le pasa como parámetro, otro para dar una medida de
variación respecto a la media y el tercero para obtener los valores extremos
(mínimo y máximo) de la matriz. Estos métodos funcionan con corrección,
independientemente del tamaño de la matriz que se les suministre.

Solución

1 public class Estadisticas {


2
3 static float Media(float[] Matriz) {
4 float Suma = 0;
5 for (int i=0; i<Matriz.length; i++)
6 Suma = Suma + Matriz[i];
7 return Suma/Matriz.length;
8 }
9
10 static float Variacion(float[] Matriz) {
11 float Suma = 0;
12 float Media = Media(Matriz);
13 for (int i=0; i<Matriz.length; i++)
14 Suma = Suma + Math.abs(Media - Matriz[i]);
15 return Suma/Matriz.length;
16 }
17
18 static float[] MenorMayor(float[] Matriz) {
19 float[] Auxiliar = new float[2]; //Contendra el menor
//y el mayor valor
20 Auxiliar[0] = Matriz[0]; //Por ahora el menor es
//el primero
21 Auxiliar[1] = Matriz[0]; //Por ahora el mayor es
//el primero
22 for (int i=0; i<Matriz.length; i++) {
23 if (Matriz[i] < Auxiliar[0])
24 Auxiliar[0] = Matriz[i];
25 if (Matriz[i] > Auxiliar[1])
26 Auxiliar[1] = Matriz[i];
27 }
28 return Auxiliar;
29 }
30
31 public static void main (String[] args) {
32
33 float[] TemperaturasMadrid =
80 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

34 {6.7f, 5.8f, 14.5f, 15.0f, 18.5f, 22.3f,


35.6f, 38.0f, 28.6f, 19.4f, 14.6f, 8.5f};

35 float[] TemperaturasGranada =
36 {5.2f, 4.6f, 12.5f, 19.3f, 16.2f, 24.5f,
37.2f, 37.0f, 29.3f, 18.2f, 10.3f, 7.5f};

37 float[] PresionFebrero =
38 {800.2f, 810.5f, 815.2f, 825.6f, 837.4f, 850.2f, 860.4f,
39 855.2f, 847.2f, 820.4f, 810.5f, 805.2f, 790.3f, 795.1f,
40 790.4f, 786.6f, 780.6f, 770.3f, 770.4f, 777.7f, 790.3f,
41 820.2f, 830.7f, 840.1f, 845.6f, 859.4f, 888.2f, 899.4f};
42
43 float[] mM = new float[2]; // Contendra el menor y
// el mayor valor
44 System.out.println("Media temperaturas de Madrid: " +
45 Media(TemperaturasMadrid) );
46 System.out.println("Variacion temperaturas de Madrid:
47 " + Variacion(TemperaturasMadrid) );
48 mM = MenorMayor(TemperaturasMadrid);
49 System.out.println("Menor y mayor temperatura de
50 Madrid: "+ mM[0] + " " + mM[1] );
51 System.out.println();
52
53 System.out.println("Media temperaturas de Granada: " +
54 Media(TemperaturasGranada) );
55 System.out.println("Variacion temperaturas de Granada:
56 " + Variacion(TemperaturasGranada) );
57 mM = MenorMayor(TemperaturasGranada);
58 System.out.println("Menor y mayor temperatura de
59 Granada: "+mM[0] + " " + mM[1] );
60 System.out.println();
61
62 System.out.println("Media presion de febrero:
"+Media(PresionFebrero));
63 System.out.println("Variacion presion de febrero: " +
64 Variacion(PresionFebrero) );
65 mM = MenorMayor(PresionFebrero);
66 System.out.println("Menor y mayor presion de febrero:
67 " + mM[0] + " " + mM[1] );
68 }
69 }

En las líneas 33 a 41 del programa principal se declaran y definen las


matrices que contienen las temperaturas medias de los meses de un año en Madrid y
Granada y las presiones medias de cada día de febrero. Estas matrices
(TemperaturasMadrid , TemperaturasGranada, PresionFebrero) se utilizan como
argumentos que se les pasa a los métodos definidos antes del programa principal.
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 3: MÉTODOS Y ESTRUCTURAS DE DATOS 81

Los métodos implementados son Media , Variación y MenorMayor, este


último devuelve una matriz lineal de valores float. El primer elemento contiene el
menor valor de la matriz que se le ha pasado, mientras que en el segundo elemento
se coloca el mayor de los valores de la matriz.

La línea 43 (en el programa principal) declara una matriz lineal (mM) de 2


valores de tipo float, que nos servirá para recoger los valores mínimos y máximos
(mM[0] y mM[1]) que nos devuelve el método MenorMayor.

En la línea 45 se obtiene la temperatura media del año en “Madrid”,


invocando al método Media con el argumento TemperaturasMadrid. En la línea 47
se obtiene una medida de la variación de la temperaturas del año en “Madrid”,
invocando al método Variacion con el argumento TemperaturasMadrid. En la línea
48 se obtienen las temperaturas mínima y máxima de la media de los meses en
Madrid, para ello se utiliza el método MenorMayor, con argumento
TemperaturasMadrid y recogiendo el resultado en la matriz mM.

El resto de las líneas hasta el final del programa principal repiten la


secuencia explicada, pero utilizando en este caso como argumentos las matrices
TemperaturasGranada y PresionFebrero.

Del método Media (situado entre las líneas 3 y 8) cabe resaltar la utilización
de la propiedad length, asociada al array Matriz. Utilizando esta propiedad, que nos
indica el número de elementos de la matriz lineal, conseguimos que el método
Media sea independiente del tamaño de la matriz: obsérvese como funciona
adecuadamente con TemperaturasMadrid, de 12 elementos y con PresionFebrero,
de 28 elementos. Esta propiedad la utilizamos también en los demás métodos del
ejemplo.

El método Variación, implementado entre las líneas de código 10 y 16,


necesita el valor medio de los valores de la matriz que se le suministra como
argumento. Este valor lo obtiene en la línea 12, llamando al método Media.
Obsérvese como no existe ambigüedad entre la variable Media y la llamada al
método Media (.....). El funcionamiento de este método se codifica en la línea 14,
hallándose la diferencia (variación), en valor absoluto, de cada valor de la matriz
respecto a la media de todos los valores.

El método MenorMayor (línea 18) presenta la novedad de devolver una


matriz (aunque sea de sólo dos elementos) en lugar de un valor de tipo primitivo del
lenguaje. Para su implementación, inicialmente se considera el primer elemento de
Matriz como el mayor y el menor de todo el array (líneas 20 y 21) y después se
utiliza un bucle (línea 22) que va recorriendo la matriz y actualizando los valores del
menor y mayor elementos encontrados (líneas 24 y 26). Finalmente, en la línea 28,
se devuelve el array Auxiliar que contiene los dos valores buscados.
82 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

Resultados
CAPÍTULO 4

PROGRAMACIÓN ORIENTADA A
OBJETOS USANDO CLASES

4.1 DEFINICIÓN DE CLASES E INSTANCIAS


Las clases son los objetos que Java utiliza para soportar la programación
orientada a objetos. Constituyen la estructura básica sobre la que se desarrollan las
aplicaciones.

Una clase permite definir propiedades y métodos relacionados entre sí.


Habitualmente, las propiedades son las variables que almacenan el estado de la clase
y los métodos son los programas que se utilizan para consultar y modificar el
contenido de las propiedades.

Un ejemplo de clase podría ser un semáforo de circulación, cuyo estado se


guarde en una propiedad EstadoSemaforo de tipo String que pueda tomar los valores
“Verde”, “Amarillo” y “Rojo”. Como métodos de acceso a la propiedad podríamos
definir: PonColor(String C olor) y String DimeColor().

4.1.1 Sintaxis

AtributoAcceso class NombreClase {


// propiedades y métodos
}

Obviando el significado del atributo de acceso, que se explicará más tarde,


un ejemplo concreto de clase en Java podría ser:
84 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

public class Semaforo {


private String EstadoSemaforo = “Rojo”;

public void PonColor (String Color) {


EstadoSemaforo = Color;
}

public String DimeColor() {


return EstadoSemaforo;
}

} // Fin de la clase Semaforo

4.1.2 Representación gráfica

Gráficamente, la clase Semaforo la podríamos definir de la siguiente


manera:

La propiedad EstadoSemaforo, con atributo private, no es accesible


directamente desde el exterior de la clase, mientras que los métodos, con atributo
public, si lo son. Desde el exterior de la clase podemos acceder a la propiedad
EstadoSemaforo a través de los métodos PonColor y DimeColor.

4.1.3 Instancias de una clase

Cuando definimos una clase, estamos creando una plantilla y definiendo un


tipo. Con el tipo definido y su plantilla de código asociada (sus propiedades y
métodos) podemos crear tantas entidades (instancias) de la clase como sean
necesarias; de esta manera, en nuestro ejemplo, podemos crear varios semáforos
 BOBADILLA CAPÍTULO 4: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO CLASES 85

(instancias de la clase Semáforo), y hacer evolucionar el estado de estos “semáforos”


de forma independiente.

Si deseamos disponer de diversos semáforos independientes entre sí, en el


sentido de que cada semáforo pueda encontrarse en un estado diferente a los demás,
obligatoriamente debemos crear (instanciar) cada uno de estos semáforos.

Para declarar un objeto de una clase dada, empleamos la sintaxis habitual:


Tipo Variable;

En nuestro caso, el tipo se refiere al nombre de la clase:


Semaforo MiSemaforo;

De esta manera hemos creado un apuntador (MiSemaforo) capaz de


direccionar un objeto (una instancia) de la clase Semaforo:

MiSemaforo

Para crear una instancia de la clase Semaforo, empleamos la palabra


reservada new, tal y como hacíamos para crear una instancia de una matriz; después
invocamos a un método que se llame igual que la clase. Estos métodos se
denominan constructores y se explicarán un poco más adelante.
MiSemaforo = new Semaforo();

También podemos declarar e instanciar en la misma instrucción:


Semaforo MiSemaforo = new Semaforo();

En cualquier caso, el resultado es que disponemos de una variable


MiSemaforo que direcciona un objeto creado (instanciado) de la clase Semaforo:

Podemos crear tantas instancias como necesitemos:


Semaforo MiSemaforo = new Semaforo();
Semaforo OtroSemaforo = new Semaforo();
86 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

Semáforo SemaforoDeMiCalle = new Semaforo();

El resultado es que disponemos del tipo Semaforo (de la clase Semaforo) y


de tres instancias (MiSemaforo, OtroSemaforo, SemaforoDeMiCalle ) de la clase:

Es importante ser consciente de que en este momento existen tres variables


diferentes implementando la propiedad EstadoSemaforo; cada una de estas variables
puede contener un valor diferente, por ejemplo, cada semáforo puede presentar una
luz distinta (“Verde”, “Rojo”, “Amarillo”) en un instante dado.

4.1.4 Utilización de los métodos y propiedades de una clase

Para designar una propiedad o un método de una clase, utilizamos la


notación punto:
Objeto.Propiedad
Objeto.Metodo()
 BOBADILLA CAPÍTULO 4: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO CLASES 87

De esta forma, si deseamos poner en verde el semáforo


SemaforoDeMiCalle , empleamos la instrucción:
SemaforoDeMiCalle.PonColor(“Verde”);

De igual manera podemos actuar con las demás instancias de la clase


Semaforo:
MiSemaforo.PonColor(“Rojo”);
OtroSemaforo.PonColor(“Verde”);

Para consultar el estado de un semáforo:


System.out.println( OtroSemaforo.DimeColor() );
if (MiSemaforo.DimeColor().equals(“Rojo”))
String Luz = SemaforoDeMiCalle.DimeColor();

En nuestro ejemplo no podemos acceder directamente a la propiedad


EstadoSemaforo, por ser privada. En caso de que fuera pública se podría poner:
String Luz = SemaforoDeMiCalle.EstadoSemaforo; // sólo si EstadoSemaforo es
// accesible (en nuestro ejemplo NO lo es)

4.1.5 Ejemplo completo

En esta sección se presenta el ejemplo del semáforo que hemos ido


desarrollando. Utilizaremos dos clases: la clase definida (Semaforo) y otra que use
esta clase y contenga el método main (programa principal). A esta última clase la
llamaremos PruebaSemaforo.

1 public class Semaforo {


2 String EstadoSemaforo = "Rojo";
3
4 public void PonColor (String Color) {
5 EstadoSemaforo = Color;
6 }
7
8 public String DimeColor() {
9 return EstadoSemaforo;
10 }
11
12 } // Fin de la clase Semaforo
88 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

13 public class PruebaSemaforo {


14 public static void main (String[] args) {
15 Semaforo MiSemaforo = new Semaforo();
16 Semaforo SemaforoDeMiCalle = new Semaforo();
17 Semaforo OtroSemaforo = new Semaforo();
18
19 MiSemaforo.PonColor("Rojo");
20 OtroSemaforo.PonColor("Verde");
21
22 System.out.println( OtroSemaforo.DimeColor() );
23 System.out.println( SemaforoDeMiCalle.DimeColor() );
24
25 if (MiSemaforo.DimeColor().equals("Rojo"))
26 System.out.println ("No Pasar");
27
28 }
29 }

En las líneas 3, 4 y 5 de la clase PruebaSemaforo se declaran e instancian


las variables MiSemaforo, SemaforoDeMiCalle y OtroSemaforo. En las líneas 7 y 8
se asignan los textos Rojo y Verde en las propiedades EstadoSemaforo de las
instancias MiSemaforo y OtroSemaforo. La propiedad EstadoSemaforo de la
instancia SemaforoDeMiCalle contendrá el valor “Rojo”, con el que se inicializa
(línea 2 de la clase Semaforo).

En las líneas 10 y 11 se obtienen los valores de la propiedad


EstadoSemaforo, a través del método DimeColor() y se imprimen. En la línea 13 se
compara (equals) el valor obtenido en DimeColor() con el literal “Rojo”.

Cuando, por ejemplo, se invoca a los métodos PonColor y DimeColor de la


instancia OtroSemaforo, internamente se produce el siguiente efecto: el literal
“Verde” es el argumento en la llamada al método PonColor, asociado a la instancia
OtroSemaforo; el argumento pasa al parámetro Color del método, y de ahí a la
variable (propiedad) EstadoSemaforo (por la asignación de la línea 5 en la clase
Semaforo). En sentido contrario, cuando se invoca al método DimeColor() asociado
a la instancia OtroSemaforo, el valor de la propiedad EstadoSemaforo se devuelve
(retorna) a la instrucción llamante en el programa principal (línea 9 de la clase
Semaforo).
 BOBADILLA CAPÍTULO 4: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO CLASES 89

4.1.6 Resultado

4.2 SOBRECARGA DE MÉTODOS Y CONSTRUCTORES

4.2.1 Sobrecarga de métodos

La sobrecarga de métodos es un mecanismo muy útil que permite definir en


una clase varios métodos con el mismo nombre. Para que el compilador pueda
determinar a qué método nos referimos en un momento dado, los parámetros de los
métodos sobrecargados no pueden ser idénticos.

Por ejemplo, para establecer las dimensiones de un objeto (anchura,


profundidad, altura) en una medida dada (“pulgadas”, “centímetros”, ...) podemos
definir los métodos:
Dimensiones(double Ancho, double Alto, double Profundo, String Medida)
Dimensiones(String Medida, double Ancho, double Alto, double Profundo)
Dimensiones(double Ancho, String Medida, double Alto, double Profundo)
Dimensiones(double Ancho, double Alto, String Medida, double Profundo)

Cuando realicemos una llamada al método Dimensiones(....), el compilador


podrá determinar a cual de los métodos nos referimos por la posición del parámetro
de tipo String. Si definiéramos el siguiente nuevo método sobrecargado el
compilador no podría determinar a qué método nos referimos al intentar resolver la
llamada
Dimensiones(double Alto, double Ancho, double Profundo, String Medida)

Un método se determina por su firma. La firma se compone del nombre del


método, número de parámetros y tipo de parámetros (por orden de colocación). De
los 5 métodos sobrecargados que hemos definido, el primero y el último presentan la
misma firma, por lo que el compilador generará un error al compilar la clase.
90 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

Como se ha mencionado, los métodos sobrecargados pueden contener


distinto número de parámetros:
Dimensiones(String Medida)
Dimensiones(double Ancho, double Alto, double Profundo)

Los últimos dos métodos definidos son compatibles con todos los anteriores
y tendrían sentido si suponemos dos métodos adicionales que los complementen:
Dimensiones3D(double Ancho, double Alto, double Profundo)
TipoMedida(String Medida)

4.2.2 Ejemplo

1 public class Objeto3D {


2 private double X = 0d;
3 private double Y = 0d;
4 private double Z = 0d;
5 private String TipoMedida = "centimetro";
6
7 public void Dimensiones3D(double Ancho, double Alto,
double Profundo) {
8 X = Ancho; Y = Alto; Z = Profundo;
9 }
10
11 public void TipoMedida(String Medida) {
12 TipoMedida = Medida;
13 }
14
15 public void Dimensiones(double Ancho, double Alto,
16 double Profundo,String Medida) {
17 Dimensiones3D(Ancho,Alto,Profundo);
18 TipoMedida(Medida);
19 }
20
21 public void Dimensiones(String Medida, double Ancho,
22 double Alto, double Profundo) {
23 Dimensiones(Ancho,Alto,Profundo,Medida);
24 }
25
26 public void Dimensiones(double Ancho, String Medida,
27 double Alto, double Profundo) {
28 Dimensiones(Ancho,Alto,Profundo,Medida);
29 }
30
31 public void Dimensiones(double Ancho, double Alto,
32 String Medida,double Profundo) {
 BOBADILLA CAPÍTULO 4: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO CLASES 91

33 Dimensiones(Ancho,Alto,Profundo,Medida);
34 }
35
36 public void Dimensiones(String Medida) {
37 TipoMedida(Medida);
38 }
39
40 public void Dimensiones(double Ancho, double Alto,
double Profundo) {
41 Dimensiones3D(Ancho,Alto,Profundo);
42 }
43
44 public double DimeAncho() {
45 return X;
46 }
47
48 public double DimeAlto() {
49 return Y;
50 }
51
52 public double DimeProfundo() {
53 return Z;
54 }
55
56 public String DimeMedida() {
57 return TipoMedida;
58 }
59
60 } // Fin de la clase Objeto3D

En las líneas 2, 3, 4 y 5 se declaran y definen valores iniciales para las


propiedades privadas X, Y, Z y TipoMedida. En la línea 7 se define el método
Dimensiones3D, que permite asignar valores a las tres dimensiones espaciales de un
objeto. En la línea 11 se define el método TipoMedida, que permite asignar un valor
a la propiedad del mismo nombre.

La línea 15 define el primer método del grupo de 6 métodos sobrecargados


Dimensiones. El cuerpo de este método (líneas 17 y 18) aprovecha la existencia de
los métodos anteriores, para evitar la repetición de código. Los 3 métodos
Dimensiones siguientes (líneas 21, 26 y 31) simplemente hacen una llamada al
primero, ordenando adecuadamente los argumentos de la invocación.

Los dos últ imos métodos sobrecargados Dimensiones (líneas 36 y 40) hacen
llamadas a los métodos más convenientes para realizar su función.
92 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

Los últimos 4 métodos (DimeAlto, DimeAncho, DimeProfundo,


DimeMedida) nos permiten conocer el valor de las propiedades de la cla se,
aumentando la funcionalidad de Objetos3D.

A continuación se muestra el código de una clase (PruebaObjeto3D) que


utiliza a la clase Objeto3D:

1 public class PruebaObjeto3D {


2 public static void main (String[] args) {
3 Objeto3D Caja = new Objeto3D();
4 Objeto3D Esfera = new Objeto3D();
5 Objeto3D Bicicleta = new Objeto3D();
6
7 Caja.Dimensiones(20.0,12.5,30.2,"centimetros");
8 Esfera.Dimensiones(10.0,"pulgadas",10.0,10.0);
9 Bicicleta.Dimensiones(90.0,30.0,20.0);
10
11 System.out.println(Bicicleta.DimeMedida());
12 System.out.println(Bicicleta.DimeAlto());
13
14 Bicicleta.Dimensiones("pulgadas");
15
16 System.out.println(Bicicleta.DimeMedida());
17 System.out.println(Bicicleta.DimeAlto());
18
19 }
20 }

En las líneas 3, 4 y 5 se declaran y definen tres instancias (Caja, Esfera,


Bicicleta ) de la clase Objeto3D. En las líneas 7, 8 y 9 se invocan diversas
ocurrencias del método sobrecargado Dimensiones. Como en la instancia Bicicleta
no se define el tipo de sus medidas, prevalece “centímetro” que ha sido asignada en
la instrucción 5 de la clase Objeto3D.

Las líneas 11 y 12 imprimen la medida y altura de la instancia Bicicleta


(esperamos “centimetro” y 30.0). En la línea 14 se varía el tipo de medida empleada,
lo que se reflejará en la línea 16.
 BOBADILLA CAPÍTULO 4: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO CLASES 93

4.2.3 Resultado

4.2.4 Constructores

Los constructores son métodos que nos sirven para iniciar los objetos al
definirse las instancias de los mismos. Habitualmente, el cometido de los
constructores es asignar valores iniciales a las propiedades de la clase, es decir,
situar a la clase instanciada en un estado concreto.

La sintaxis de los constructores es la misma que la de los métodos, salvo que


no tienen la referencia del atributo de acceso y nunca devuelven ningún valor
(tampoco se pone la palabra reservada void), además su nombre debe coincidir con
el nombre de la clase.

Los constructores suelen estar sobrecargados, para permitir más


posibilidades de inicialización de las instancias de las clases.

Los constructores nos permiten, a la vez, crear instancias y establecer el


estado inicial de cada objeto instanciado, a diferencia de lo que hemos realizado en
el ejercicio anterior, donde primero debíamos instanciar los objetos y
posteriormente, en otras instrucciones, establecer su estado inicial.

El ejemplo anterior, utilizando constructores, nos quedaría de la siguiente


manera:

1 public class Objeto3DConConstructor {


2 private double X = 0d;
3 private double Y = 0d;
4 private double Z = 0d;
5 private String TipoMedida = "centimetro";
6
7 public void Dimensiones3D(double Ancho, double Alto,
double Profundo) {
8 X = Ancho; Y = Alto; Z = Profundo;
94 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

9 }
10
11 public void TipoMedida(String Medida) {
12 TipoMedida = Medida;
13 }
14
15 Objeto3DConConstructor(double Ancho, double Alto,
16 double Profundo, String Medida) {
17 Dimensiones3D(Ancho,Alto,Profundo);
18 TipoMedida(Medida);
19 }
20
21 Objeto3DConConstructor(String Medida, double Ancho,
22 double Alto, double Profundo) {
23 this(Ancho,Alto,Profundo,Medida);
24 }
25
26 Objeto3DConConstructor(double Ancho, String Medida,
27 double Alto, double Profundo) {
28 this(Ancho,Alto,Profundo,Medida);
29 }
30
31 Objeto3DConConstructor(double Ancho, double Alto,
32 String Medida, double Profundo) {
33 this(Ancho,Alto,Profundo,Medida);
34 }
35
36 Objeto3DConConstructor(String Medida) {
37 TipoMedida(Medida);
38 }
39
40 Objeto3DConConstructor(double Ancho, double Alto,
double Profundo) {
41 Dimensiones3D(Ancho,Alto,Profundo);
42 }
43
44 public double DimeAncho() {
45 return X;
46 }
47
48 public double DimeAlto() {
49 return Y;
50 }
51
52 public double DimeProfundo() {
53 return Z;
54 }
55
 BOBADILLA CAPÍTULO 4: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO CLASES 95

56 public String DimeMedida() {


57 return TipoMedida;
58 }
59
60 } // Fin de la clase Objeto3DConConstructor

Las líneas 15, 21, 26, 31, 36 y 40 definen los constructores de la clase.
Como se puede observar, se omite la palabra void en su definición; tampoco se pone
el atributo de acceso. Por lo demás, se codifican como métodos normales, salvo el
uso de la palabra reservada this: this se refiere a “esta” clase (en nuestro ejemplo
Objeto3DConConstructor). En el ejemplo indicamos que se invoque a los
constructores de la clase cuya firma coincida con la firma de las instrucciones
llamantes.

La diferencia entre los constructores definidos y los métodos Dimensiones


de la clase Objeto3D se aprecia mejor en las instanciaciones de los objetos:

1 public class PruebaObjeto3DConConstructor {


2 public static void main (String[] args) {
3
4 Objeto3DConConstructor Caja = new
5 Objeto3DConConstructor(20.0,12.5,30.2,"centimetros");
6
7 Objeto3DConConstructor Esfera = new
8 Objeto3DConConstructor(10.0,"pulgadas",10.0,10.0);
9
10 Objeto3DConConstructor Bicicleta = new
11 Objeto3DConConstructor(90.0,30.0,20.0);
12
13 System.out.println(Bicicleta.DimeMedida());
14 System.out.println(Bicicleta.DimeAlto());
15
16 Bicicleta.TipoMedida("pulgadas");
17
18 System.out.println(Bicicleta.DimeMedida());
19 System.out.println(Bicicleta.DimeAlto());
20
21 }
22 }

En las líneas 4 y 5 se declara una instancia Caja de la clase


Objeto3DConConstructor. La instancia se inicializa utilizando el constructor cuya
firma es: Objeto3DConConstructor(double,double,double,String).
96 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

En las líneas 7 y 8 se declara una instancia Esfera de la clase


Objeto3DConConstructor. La instancia se inicializa utilizando el constructor cuya
firma es: Objeto3DConConstructor(double,String,double,double).

En las líneas 10 y 11 se declara una instancia Bicicleta de la clase


Objeto3DConConstructor. La instancia se inicializa utilizando el constructor cuya
firma es: Objeto3DConConstructor(double,double,double).

4.3 EJEMPLOS
En esta lección se presentan tres ejemplos: “figura genérica”, “agenda de
teléfono” y “ejercicio de logística”, en los que se implementan clases que engloban
las propiedades y métodos necesarios para facilitar la resolución de los problemas
planteados.

4.3.1 Figura genérica

Este primer ejemplo muestra una clase Figura, en la que se puede establecer
y consultar el color y la posición del centro de cada instancia de la clase.

En la línea 4 se declara la propiedad ColorFigura, de tipo Color. Color es


una clase de Java (que se importa en la línea 1). En la línea 5 se declara el vector
(matriz lineal) Posición, que posee dos componentes: Posición[0] y Posición[1],
que representan respectivamente al valor X e Y de la posición del centro de la
figura.

En la línea 7 se define un constructor de la clase en el que se puede


establecer el color de la figura. Este método hace una llamada a EstableceColor
(línea 16) en donde se actualiza la propiedad ColorFigura con el valor del parámetro
color.

En la línea 11 se define un segundo constructor en el que se establece el


color y la posición del centro de la figura. Este constructor hace una llamada al
método EstableceCentro (línea 24) en donde se actualiza la propiedad Posición de la
clase (this) con el valor del parámetro Posición.
 BOBADILLA CAPÍTULO 4: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO CLASES 97

Para completar la clase Figura, se establecen los métodos de acceso


DimeColor (línea 20) y DimeCentro (línea 29), a través de los cuales se puede
obtener los valores de las propiedades ColorFigura y Posición. Nótese que desde el
exterior de esta clase no se puede acceder directamente a sus propiedades, que tienen
atributo de acceso private .

Código

1 import java.awt.Color;
2
3 public class Figura {
4 private Color ColorFigura;
5 private int[] Posicion = new int[2];
6
7 Figura(Color color) {
8 EstableceColor(color);
9 }
10
11 Figura(Color color, int[] Posicion) {
12 EstableceColor(color);
13 EstableceCentro(Posicion);
14 }
15
16 public void EstableceColor(Color color) {
17 ColorFigura = color;
18 }
19
20 public Color DimeColor() {
21 return ColorFigura;
22 }
23
24 public void EstableceCentro(int[] Posicion) {
25 this.Posicion[0] = Posicion[0];
26 this.Posicion[1] = Posicion[1];
27 }
28
29 public int[] DimeCentro() {
30 return Posicion;
31 }
32
33 }
98 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

4.3.2 Agenda de teléfono

En este ejemplo se implementa el control que puede tener un teléfono para


mantener las últimas llamadas realizadas y para poder acceder a las mismas.

Código

1 public class Telefono {


2 private int Max_Llamadas;
3 private String[] LlamadasHechas;
4
5 private int NumLlamadaHecha = -1;
6
7 Telefono(int Max_Llamadas) {
8 this.Max_Llamadas = Max_Llamadas;
9 LlamadasHechas = new String[Max_Llamadas];
10 }
11
12 public void Llamar(String Numero) {
13 // Hacer la llamada
14 NumLlamadaHecha = (NumLlamadaHecha+1)%Max_Llamadas;
15 LlamadasHechas[NumLlamadaHecha] = Numero;
16 }
17
18 public String UltimaLlamada() {
19 return Llamada(0);
20 }
21
22 public String Llamada(int n) {// La ultima llamada es n=0
23 if (n<=NumLlamadaHecha)
24 return LlamadasHechas[NumLlamadaHecha-n];
25 else
26 return LlamadasHechas[Max_Llamadas-(n-NumLlamadaHecha)];
27 }
28
29 }

En la línea 2 de la clase Telefono se declara una propiedad Max_Llamadas,


donde se guardará el número de llamadas que el teléfono almacena. En la línea
siguiente se declara la matriz lineal (vector) de literales que contendrá los últimos
Max_Llamadas teléfonos marcados.

El constructor de la línea 7 permite definir el número de llamadas que


almacena el teléfono. En la línea 8 se presenta una idea importante: cuando se utiliza
 BOBADILLA CAPÍTULO 4: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO CLASES 99

la estructura this.Propiedad, el código se refiere a la variable Propiedad de la clase


(this). En nuestro ejemplo, en la línea 8, this.Max_Llamadas hace referencia a la
propiedad privada Max_Llamadas de la clase Telefono, mientras que Max_Llamadas
hace referencia al parámetro del constructor.

En la línea 9 se crea y dimensiona el vector LlamadasHechas. Nótese que en


esta implementación, para crear un registro de llamadas, hay que instanciar el objeto
Telefono haciendo uso del constructor definido.

El método de la línea 12 introduce el Numero “marcado” en el vector


LlamadasHechas. Para asegurarnos de que se almacenan las últimas Max_Llamadas
hacemos uso de una estructura de datos en forma de buffer circular, esto es, si por
ejemplo Max_Llamadas es 4, rellenaremos la matriz con la siguiente secuencia:
LlamadasHechas[0], LlamadasHechas[1], LlamadasHechas[2],
LlamadasHechas[3], LlamadasHechas[0], LlamadasHechas[1], etc. Este efecto 0,
1, 2, 3, 0, 1, ... lo conseguimos con la operación módulo (%) Max_Llamadas, como
se puede ver en la línea 14.

En el método Llamar, la propiedad NumLlamadaHecha nos sirve de


apuntador a la posición de la matriz que contiene el número de teléfono
correspondiente a la última llamada realizada.

El método situado en la línea 22 devuelve el número de teléfono al que


llamamos en último lugar (n=0), penúltimo lugar (n=1), etc.

El método definido en la línea 18 (UltimaLlamada) devuelve el último


número de teléfono llamado.

Para utilizar la clase Telefono se ha implementado la clase PruebaTelefono.


En primer lugar se declaran y definen dos teléfonos (ModeloBarato y ModeloMedio )
con capacidades de almacenamiento de llamadas 2 y 4 (líneas 3 y 4); posteriormente
se realizan una serie de llamadas con ModeloBarato, combinadas con consultas de
las mismas a través de los métodos UltimaLlamada y Llamada.

A partir de la línea 13 se hace uso del teléfono ModeloMedio , realizándose 6


llamadas y consultando en la línea 15 la última llamada y en la línea 20 la penúltima
llamada.

Código de prueba

1 public class PruebaTelefono {


2 public static void main(String[] args) {
100 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

3 Telefono ModeloBarato = new Telefono(2);


4 Telefono ModeloMedio = new Telefono(4);
5
6 ModeloBarato.Llamar("670879078");
7 ModeloBarato.Llamar("670674590");
8 System.out.println(ModeloBarato.UltimaLlamada());
9 ModeloBarato.Llamar("670234590");
10 ModeloBarato.Llamar("670069423");
11 System.out.println(ModeloBarato.Llamada(1));
12
13 ModeloMedio.Llamar("670879078");
14 ModeloMedio.Llamar("670674590");
15 System.out.println(ModeloMedio.UltimaLlamada());
16 ModeloMedio.Llamar("670234590");
17 ModeloMedio.Llamar("670069423");
18 ModeloMedio.Llamar("670069498");
19 ModeloMedio.Llamar("670069499");
20 System.out.println(ModeloMedio.Llamada(1));
21
22 }
23 }

Resultados

4.3.3 Ejercicio de logística

En este ejemplo se plantea la siguiente situación: una empresa dispone de 3


almacenes de grandes contenedores. El primer almacén tiene una capacidad de 2
contenedores, el segundo de 4 y el tercero de 8. El primero se encuentra muy cerca
de una vía (carretera) principal, el segundo se encuentra a 10 kilómetros de la
carretera principal y el tercero a 20 kilómetros.

Los camioneros o bien llegan con un contenedor (uno solo por camión) o
bien llegan con el camión vacío con la intención de llevarse un contenedor. En
 BOBADILLA CAPÍTULO 4: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO CLASES 101

cualquier caso, siempre ha existido un vigilante al comienzo del camino que lleva a
los almacenes que le indicaba a cada camionero a que almacén debía dirigirse a
depositar el contenedor que traía o a recoger un contenedor, en caso de llegar sin
carga.

El vigilante, con muy buena lógica, siempre ha indicado a los camioneros el


almacén más cercano donde podían realizar la operación de carga o descarga,
evitando de esta manera largos trayectos de ida y vuelta a los almacenes más lejanos
cuando estos desplazamientos no eran necesarios.

Como el buen vigilante está a punto de jubilarse, nos encargan la realización


de un programa informático que, de forma automática, le indique a los camioneros el
almacén al que deben dirigirse minimizando los costes de combustible y tiempo. Los
camioneros, una vez que llegan a la barrera situada al comienzo del camino, pulsan
un botón (‘m’) si van a meter un contenedor, o un botón ‘s’ si lo van a sacar. El
programa les indicará en un panel el almacén (1, 2 ó 3) al que se deben dirigir.

Para resolver esta situación, utilizaremos dos clases: una a la que


llamaremos LogisticaAlmacen, que permitirá la creación de una estructura de datos
almacén y sus métodos de acceso necesarios. Posteriormente crearemos la clase
LogisticaControl1Contenedor que utilizando la primera implementa la lógica de
control expuesta en el enunciado del ejercicio.

La clase LogísticaAlmacen puede implementarse de la siguiente manera:

Código

1 public class LogisticaAlmacen {


2 private byte Capacidad;
3 private byte NumeroDeHuecos;
4
5 LogisticaAlmacen(byte Capacidad) {
6 this.Capacidad = Capacidad;
7 NumeroDeHuecos = Capacidad;
8 }
9
10 public byte DimeNumeroDeHuecos() {
11 return (NumeroDeHuecos);
12 }
13
14 public byte DimeCapacidad() {
15 return (Capacidad);
16 }
17
102 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

18 public boolean HayHueco() {


19 return (NumeroDeHuecos != 0);
20 }
21
22 public boolean HayContenedor() {
23 return (NumeroDeHuecos != Capacidad);
24 }
25
26 public void MeteContenedor() {
27 NumeroDeHuecos--;
28 }
29
30 public void SacaContenedor() {
31 NumeroDeHuecos++;
32 }
33
34 } // LogisticaAlmacen

La clase LogisticaAlmacen es muy sencilla y muy útil. El estado del


almacén puede definirse con dos propiedades: Capacidad y NumeroDeHuecos
(líneas 2 y 3). En este caso hemos declarado las variables de tipo byte, por lo que la
capacidad máxima de estos almacenes es de 256 elementos. Si quisiéramos una clase
almacén menos restringida, podríamos cambiar el tipo de las variables a short o int.

Sólo hemos programado un constructor (en la línea 5), que nos permite
definir la capacidad (Capacidad) del almacén. En la línea 6 asignamos a la
propiedad Capacidad de la clase(this.Capacidad) el valor que nos indica el
parámetro Capacidad del constructor. En la línea 7 determinamos que el almacén,
inicialmente se encuentra vacío (tantos huecos como capacidad).

Los métodos 10 y 14 nos devuelven (respectivamente) los valores de las


propiedades NumeroDeHuecos y Capacidad.

El método de la línea 18 (HayHueco) nos indica si tenemos la posibilidad de


meter un elemento en el almacén, es decir NumeroDeHuecos es distinto de 0. El
método de la línea 22 (HayContenedor) nos indica si existe al menos un elemento en
el almacén, es decir, no hay tantos huecos como capacidad.

Finalmente, los métodos MeteContenedor y SacaContenedor (líneas 26 y


30) actualizan el valor de la propiedad NumeroDeHuecos. Obsérvese que estos
métodos no realizan ninguna comprobación de si el almacén está lleno (en el primer
caso) o vacío (en el segundo), esta comprobación la deberá realizar el programador
que utilice la clase, invocando a los métodos HayHueco y HayContenedor
(respectivamente).
 BOBADILLA CAPÍTULO 4: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO CLASES 103

A continuación se muestra el código de la clase que realiza el control de


acceso a los almacenes, utilizando la clase LogisticaAlmacen:

Código de prueba

1 public class LogisticaControl1Contenedor {


2 public static void main(String[] args){
3 LogisticaAlmacen Almacen1 = new
LogisticaAlmacen((byte)2);
4 LogisticaAlmacen Almacen2 = new
LogisticaAlmacen((byte)4);
5 LogisticaAlmacen Almacen3 = new
LogisticaAlmacen((byte)8);
6
7 String Accion;
8
9 do {
10 Accion = Teclado.Lee_String();
11 if (Accion.equals("m")) // meter contenedor
12 if (Almacen1.HayHueco())
13 Almacen1.MeteContenedor();
14 else
15 if (Almacen2.HayHueco())
16 Almacen2.MeteContenedor();
17 else
18 if (Almacen3.HayHueco())
19 Almacen3.MeteContenedor();
20 else
21 System.out.println("Hay que esperar a que
22 vengan a quitar un contenedor");
23 else // sacar contenedor
24 if (Almacen1.HayContenedor())
25 Almacen1.SacaContenedor();
26 else
27 if (Almacen2.HayContenedor())
28 Almacen2.SacaContenedor();
29 else
30 if (Almacen3.HayContenedor())
31 Almacen3.SacaContenedor();
32 else
33 System.out.println("Hay que esperar a que
34 vengan a poner un contenedor");
35 } while (!Accion.equals("Salir"));
36 }
37 } // clase
104 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

En las líneas 3, 4 y 5 se declaran e instancian los almacenes Almacen1,


Almacen2 y Almacen3, con capacidades 2, 4 y 8.

En la línea 9 se entra en un bucle, que normalmente sería infinito: while


(true), aunque ha sido programado para terminar cuando se teclea el literal “Salir”
(línea 35). En este bucle se está esperando a que el primer camionero que llegue
pulse el botón “m” o el botón “s”, en nuestro caso que pulse la tecla “m” o cualquier
otra tecla (líneas 10, 11 y 23).

Si se pulsa la tecla “m”, con significado “meter contenedor”, en primer


lugar se pregunta si HayHueco en el Almacen1 (línea 12), si es así se le indica al
camionero que se dirija al primer almacén y se actualiza el estado del almacén
invocando al método MeteContenedor (línea 13). Si no hay hueco en el primer
almacén (línea 14), se “prueba” suerte con Almacen2 (línea 15); en el caso de que
haya hueco se mete el contenedor en este almacén (línea 16). Si no hay hueco en el
almacén 2 se intenta en el tercer y último almacén (línea 18).

El tratamiento para sacar un contenedor (líneas 24 a 34) es análogo al de


meter el contendor.

4.4 CLASES UTILIZADAS COMO PARÁMETROS


Utilizar una clase como parámetro en un método hace posible que el método
utilice toda la potencia de los objetos de java, independizando las acciones
realizadas de los objetos (clases) sobre las que las realiza. Por ejemplo, podríamos
escribir un método, llamado ControlIgnicion (Cohete MiVehiculoEspacial), donde
se realicen complejas operaciones sobre el componente software (clase)
MiVehiculoEspacial, de la clase Cohete que se le pasa como parámetro.

En el ejemplo anterior, el mismo método podría simular el control de la


ignición de distintos cohetes, siempre que estos estén definidos como instancias de
la clase Cohete:
Cohete Pegasus, Ariane5;
..........................................
ControlIgnicion(Ariane5);
ControlIgnicion(Pegasus);

Como ejemplo de clases utilizadas como parámetros, resolveremos la


siguiente situación:
 BOBADILLA CAPÍTULO 4: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO CLASES 105

Una empresa se encarga de realizar el control informático de la entrada-


salida de vehículos en diferentes aparcamientos. Cada aparcamiento dispone de un
número fijado de plazas y también de puertas de entrada/salida de vehículos.

Se pide realizar el diseño de un software orientado a objetos que controle los


aparcamientos. Para simplificar no consideraremos peticiones de entrada-salida
simultáneas (concurrentes).

4.4.1 Código

En primer lugar se define una clase Almacen, con la misma funcionalidad


que la clase LogisticaAlmacen explicada en la lección anterior:

1 public class Almacen {


2 private short Capacidad;
3 private short NumeroDeElementos = 0;
4
5 Almacen(short Capacidad) {
6 this.Capacidad = Capacidad;
7 }
8
9 public short DimeNumeroDeElementos() {
10 return (NumeroDeElementos);
11 }
12
13 public short DimeCapacidad() {
14 return (Capacidad);
15 }
16
17 public boolean HayElemento() {
18 return (NumeroDeElementos != 0);
19 }
20
21 public boolean HayHueco() {
22 return (NumeroDeElementos != Capacidad);
23 }
24
25 public void MeteElemento() {
26 NumeroDeElementos++;
27 }
28
29 public void SacaElemento() {
30 NumeroDeElementos--;
31 }
106 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

32
33 public void RellenaAlmacen() {
34 NumeroDeElementos = Capacidad;
35 }
36
37 } // clase

Puesto que cada aparcamiento que gestiona la empresa puede tener un


número diferente de accesos (puertas), se define la clase Puerta . La clase Puerta,
además del constructor, tiene únicamente dos métodos: EntraVehiculo y
SaleVehiculo , donde se evalúan las peticiones de entrada y salida de los usuarios.

La clase Puerta, para sernos útil, tiene que poder actuar sobre cada uno de
los diferentes aparcamientos (para nosotros “almacenes” de vehículos). No nos
interesa tener que implementar una clase Puerta por cada aparcamiento que gestiona
la empresa.

Para conseguir que las acciones de los métodos implementados en la clase


Puerta se puedan aplicar a los aparcamientos (almacenes) deseados, le pasaremos
(como parámetro) a la clase Puerta la clase Almacen sobre la que tiene que actuar.
El constructor situado en la línea 5 de la clase Puerta admite la clase Almacen como
parámetro. La referencia del almacén suministrado como argumento se copia en la
propiedad Parking de la clase Puerta (this.Parking).

Los métodos EntraVehiculo y SaleVehiculo (líneas 9 y 19) utilizan el


almacén pasado como parámetro (líneas 10, 13 y 23). Estos métodos se apoyan en
las facilidades que provee la clase Almacen para implementar la lógica de
tratamiento de las peticiones de entrada y salida de l aparcamiento realizadas por los
usuarios.

1 public class Puerta {


2
3 Almacen Parking = null;
4
5 Puerta (Almacen Parking) {
6 this.Parking = Parking;
7 }
8
9 public void EntraVehiculo() {
10 if (Parking.HayHueco()) {
11 System.out.println ("Puede entrar");
12 // Abrir la barrera
13 Parking.MeteElemento();
14 }
15 else
 BOBADILLA CAPÍTULO 4: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO CLASES 107

16 System.out.println ("Aparcamiento completo");


17 }
18
19 public void SaleVehiculo() {
20 // Comprobar el pago
21 System.out.println ("Puede salir");
22 // Abrir la barrera
23 Parking.SacaElemento();
24 }
25 }

Finalmente, la clase Aparcamiento recoge las peticiones de entrada/salida de


los usuarios por cada una de las puertas. Esto se simula por medio del teclado:

1 class Aparcamiento {
2 public static void main(String[] args){
3 char CPuerta, COperacion;
4 Puerta PuertaRequerida = null;
5
6 Almacen Aparcamiento = new Almacen( (short) 5 );
7 Puerta Puerta1 = new Puerta(Aparcamiento);
8 Puerta Puerta2 = new Puerta(Aparcamiento);
9
10 do {
11 CPuerta = IntroduceCaracter ("Puerta de acceso:
(1, 2): ");
12 switch (CPuerta) {
13 case '1':
14 PuertaRequerida = Puerta1;
15 break;
16 case '2':
17 PuertaRequerida = Puerta2;
18 break;
19 default:
20 System.out.println ("Puerta seleccionada no
valida");
21 break;
22 }
23
24 COperacion = IntroduceCaracter ("Entrar/Salir
vehiculo (e, s): ");
25 switch (COperacion) {
26 case 'e':
27 PuertaRequerida.EntraVehiculo();
28 break;
29 case 's':
30 PuertaRequerida.SaleVehiculo();
108 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

31 break;
32 default:
33 System.out.println ("Operacion seleccionada
no valida");
34 break;
35 }
36
37 } while (true);
38
39 } // main
40
41
42 static public char IntroduceCaracter (String Mensaje) {
43 String Entrada;
44
45 System.out.print (Mensaje);
46 Entrada = Teclado.Lee_String();
47 System.out.println();
48 Entrada = Entrada.toLowerCase();
49 return Entrada.charAt(0);
50 }
51
52 }

En la línea 6 se declara el aparcamiento (Almacen) sobre el que se realiza la


prueba de funcionamiento. En las líneas 7 y 8 se establece que este aparcamiento
dispondrá de dos accesos: Puerta1 y Puerta2, de tipo Puerta. Obsérvese como a
estas dos puertas se les pasa como argumento el mismo Aparcamiento, de tipo
Almacen. Con esto conseguiremos que las entradas y salidas de los vehículos se
contabilicen en la propiedad NumeroDeElementos de una sola clase Almacen.

En la línea 10 nos introducimos en un bucle sin fin, donde pedimos a la


persona que prueba esta simulación que introduzca el número de puerta del
aparcamiento por donde desea entrar o salir un usuario. Para ello nos ayudamos del
método IntroduceCaracter implementado a partir de la línea 42.

Si el va lor introducido es un 1 ó un 2, se actualiza la propiedad


PuertaRequerida, de tipo Puerta , con la referencia a la puerta correspondiente
(Puerta1 o Puerta2). Si el valor introducido es diferente a 1 y a 2, se visualiza un
mensaje de error (línea 20).

A continuación se vuelve a emplear el método IntroduceCaracter para saber


si se desea simular una entrada o bien una salida. Si es una entrada se invoca al
método EntraVehiculo de la clase Puerta , a través de la referencia PuertaRequerida
 BOBADILLA CAPÍTULO 4: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO CLASES 109

(que sabemos que apunta o bien a la instancia Puerta1, o bien a la instancia


Puerta2). Si el usuario desea salir, se invoca al método SaleVehiculo .

4.4.2 Resultados

4.5 PROPIEDADES Y MÉTODOS DE CLASE Y DE


INSTANCIA
En una clase, las propiedades y los métodos pueden definirse como:
• De instancia
• De clase

4.5.1 Propiedades de instancia

Las propiedades de instancia se caracterizan porque cada vez que se define


una instancia de la clase, se crean físicamente una nuevas variables que contendrán
los valores de dichas propiedades en la instancia creada. Es decir, cada objeto (cada
instancia de una clase) contiene sus propios valores en las propiedades de instancia.
Todos los ejemplos de clases que hemos realizado hasta ahora han utilizado
variables de instancia.

En este punto es importante resaltar el hecho de que hasta que no se crea una
primera instancia de una clase, no existirá ninguna propiedad visible de la clase.

Gráficamente lo expuesto se puede representar como:


110 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

1 class Sencilla {
2 public int PropiedadDeInstancia;
3 }

La clase Sencilla está definida, pero no instanciada, por lo que todavía no


existe ninguna variable PropiedadDeInstancia. El gráfico nos muestra únicamente la
estructura que tendrá una instancia de la clase Sencilla (cuando la definamos).

Si ahora intentásemos hacer uso de la propiedad PropiedadDeInstancia a


través del nombre de la clase (Sencilla), el compilador nos daría un error:

1 class PruebaSencilla {
2 public static void main (String[] args) {
3 Sencilla.PropiedadDeInstancia = 8;
4 }
5 }

El compilador nos indica que la variable PropiedadDeInstancia es “no


estática” y que existe un error. Para poder hacer uso de la variable
PropiedadDeInstancia, obligatoriamente deberemos crear alguna instancia de la
clase, tal y como hemos venido hacie ndo en las últimas lecciones:

1 class PruebaSencilla2 {
2 public static void main (String[] args) {
3 Sencilla Instancia1 = new Sencilla();
4 Sencilla Instancia2 = new Sencilla();
5 Instancia1.PropiedadDeInstancia = 8;
6 Instancia2.PropiedadDeInstancia = 5;
7 }
8 }
 BOBADILLA CAPÍTULO 4: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO CLASES 111

En este caso disponemos de dos propiedades de instancia, a las que podemos


acceder como:
Instancia1.PropiedadDeInstancia e Instancia2.PropiedadDeInstancia

Todo intento de utilizar directamente la definición de la clase nos dará error:


Sencilla.PropiedadDeInstancia

4.5.2 Propiedades de clase

Una propiedad de clase (propiedad estática) se declara con el atributo static:

1 class SencillaEstatica {
2 static public int PropiedadDeClase;
3 }

A diferencia de las propiedades de instancia, las propiedades de clase


existen incluso si no se ha creado ninguna instancia de la clase. Pueden ser
referenciadas directamente a través del nombre de la clase, sin tener que utilizar el
identificador de ninguna instancia.
112 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

1 class PruebaSencillaEstatica {
2 public static void main (String[] args) {
3 SencillaEstatica.PropiedadDeClase = 8;
4 }
5 }

Las propiedades de clase son compartidas por todas las instancias de la


clase. Al crearse una instancia de la clase, no se crean las variable s estáticas de esa
clase. Las variables estáticas (de clase) existen antes de la creación de las instancias
de la clase.

1 class PruebaSencillaEstatica {
2 public static void main (String[] args) {
3 SencillaEstatica Instancia1 = new SencillaEstatica();
4 SencillaEstatica Instancia2 = new SencillaEstatica();
5 SencillaEstatica.PropiedadDeClase = 4;
6 Instancia1.PropiedadDeClase = 8;
7 Instancia2.PropiedadDeClase = 5;
8 }
9 }

En el ejemplo anterior, SencillaEstatica.PropiedadDeClase,


Instancia1.PropiedadDeClase e Instancia2.PropiedadDeClase hacen referencia a la
misma variable (la propiedad estática PropiedadDeClase de la clase
SencillaEstatica)
 BOBADILLA CAPÍTULO 4: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO CLASES 113

4.5.3 Métodos de instancia

Los métodos de instancia, al igual que las propiedades de instancia, sólo


pueden ser utilizados a través de una instancia de la clase. Hasta ahora siempre
hemos definido métodos de instancia (salvo el método main, que es estático).

La siguiente porción de código (obtenida de un ejemplo ya realizado)


muestra el funcionamiento de los métodos de instancia (al que estamos habituados):
en primer lugar se declaran y definen las instancias de las clases (líneas 2, 3 y 4) y
posteriormente se hace uso de los métodos a través de las instancias (líneas 11 y 12).

Cualquier intento de acceder a un método de instancia a través del nombre


de la clase (y no de una instancia de la clase) nos dará error de compilación.

En la línea 9 de la porción de código mostrada, podemos observar como


hacemos una llamada a un método estático: utilizamos el método Lee_String de la
clase Teclado. Esta llamada funciona porque el método Lee_String es estático, si no
lo fuera obtendríamos un error de compilación en la línea 9.

1 .....................
2 LogisticaAlmacen Almacen1 = new
3 LogisticaAlmacen((byte)2);
4 LogisticaAlmacen Almacen2 = new
5 LogisticaAlmacen((byte)4);
6 LogisticaAlmacen Almacen3 = new
7 LogisticaAlmacen((byte)8);
8
9 String Accion;
10
11 do {
12 Accion = Teclado.Lee_String();
13 if (Accion.equals("m")) // meter contenedor
14 if (Almacen1.HayHueco())
15 Almacen1.MeteContenedor();
16 ............................

4.5.4 Métodos de clase

Un método estático puede ser utilizado sin necesidad de definir previamente


instancias de la clase que contiene el método. Los métodos estáticos pueden
114 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

referenciarse a través del nombre de la clase (al igual que las propiedades estáticas).
Esta posibilidad es útil en diversas circunstancias:
• Cuando el método proporciona una utilidad general
Esta situación la hemos comprobado con los métodos de la clase
Math . Si queremos, por ejemplo, realizar una raíz cuadrada, no nos es
necesario crear ninguna instancia del método Math; directamente
escribimos Math.sqrt(Valor_double). Esto es posible porque el
método sqrt de la clase Math es estático.
• Cuando el método hace uso de propiedades estáticas u otros métodos
estáticos
Los métodos estáticos referencian propiedades y métodos estáticos.

No es posible hacer referencia a una propiedad de instancia o un método de


instancia desde un método estático. Esto es así debido a que en el momento que se
ejecuta un método estático puede que no exista ninguna instancia de la clase donde
se encuentra la propiedad o el método de instancia al que referencia el método
estático.

Los compiladores de Java comprueban estas situaciones y producen errores


cuando detectan una referencia a un objeto no estático dentro de un método estático.

Gráficamente, lo explicado se puede mostrar de la siguiente manera:


 BOBADILLA CAPÍTULO 4: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO CLASES 115

Una situación muy común cuando se empieza a programar es realizar una


referencia a una propiedad no estática desde el método estático main. El siguiente
ejemplo caracteriza este tipo de error tan extendido:

1 class Error {
2 int Valor = 8;
3
4 public static void main(String[] args){
5 Valor = 6;
6 } // main
7
8 } // clase

Si se quiere referenciar a la propiedad Valor de la clase, es necesario que


esté declarada como estática:

1 class NoError {
2 static int Valor = 8;
3
4 public static void main(String[] args){
5 Valor = 6;
6 } // main
7
8 } // clase

4.5.5 Ejemplo que utiliza propiedades de clase

Vamos a realizar el control de una votación en la que se puede presentar un


número cualquiera de candidatos. En cada momento se puede votar a cualquier
candidato y se pueden pedir los siguientes datos:
116 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

• Nombre de un candidato concreto y el número de votos que lleva hasta el


momento
• Nombre del candidato más votado hasta el momento y número de votos que
lleva conseguidos

La solución desarrollada parte de una clase Votacion, que permite almacenar


el nombre de un candidato y el numero de votos que lleva, además de los métodos
necesarios para actualizar el estado del objeto. Si instanciamos la clase 14 veces, por
ejemplo, podremos llevar el control de votos de 14 candidatos.

La cuestión que ahora se nos plantea es: ¿Cómo contabilizar el número de


votos y almacenar el nombre del candidato más votado hasta el momento? Una
solución posible es crear una nueva clase “MasVotado” que se instancie una sola vez
y contenga propiedades para almacenar estos valores, junto a métodos para consultar
y actualizar los mismos.

La solución expuesta en el párrafo anterior funcionaría correctamente y no


requiere del uso de propiedades estáticas, aunque existe una cuestión de diseño que
hay que tener clara: la clase “MasVotado” tiene sentido si se instancia una sola vez.

La solución que se aporta en este apartado no requiere del uso de una nueva
clase “MasVotado” o similar, ni necesita un número fijo de instanciaciones para
funcionar. La solución propuesta contiene las propiedades y métodos de acceso a la
persona más votada dentro de la propia clase Votacion, en la que se vota a cada
persona.

Como el nombre y número de votos de la persona mas votada hasta el


momento es una información general, que no depende únicamente de los votos de un
candidato, sino de los votos recibidos por todos los candidatos, estas propiedades
deben ser accesibles, comunes y compartidas por todos. Estas variables deben ser
estáticas (de clase).

4.5.6 Código del ejemplo

1 class Votacion {
2 // Persona a la que se vota en esta instancia y el numero
3 // de votos que lleva
4 private String Persona = null;
5 private int Votos = 0;
6
7 // Persona mas votada de todas las instancias y el numero
 BOBADILLA CAPÍTULO 4: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO CLASES 117

8 // de votos que lleva


9 static private int VotosMasVotado = 0;
10 static private String PersonaMasVotada = null;
11
12 // Constructor
13 Votacion (String Persona) {
14 this.Persona = Persona;
15 }
16
17 // Se invoca cada vez que alguien vota a Persona
18 public void Voto() {
19 Votos++;
20 if (Votos > VotosMasVotado) {
21 PersonaMasVotada = Persona;
22 VotosMasVotado = Votos;
23 }
24 }
25
26 // Devuelve el nombre de Persona
27 public String NombrePersona() {
28 return Persona;
29 }
30
31 // Devuelve el numero de votos de Persona
32 public int Votos() {
33 return Votos;
34 }
35
36 // Devuelve el nombre de la persona mas votada
37 static public String NombreDelMasVotado() {
38 return PersonaMasVotada;
39 }
40
41 // Devuelve el numero de votos de la persona mas votada
42 static public int VotosDelMasVotado() {
43 return VotosMasVotado;
44 }
45
46 }
118 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

En las líneas de código 4 y 5 se definen las propiedades de instancia


Persona y Votos, que contendrán el nombre completo de un candidato y el número
de votos que lleva contabilizados. El nombre del candidato se asigna en la
instanciación de la clase, a través del constructor situado en la línea 13. Una vez
creada la instancia, se puede conocer el nombre del candidato utilizando el método
NombrePersona (línea 27) y el número de votos contabilizados, utilizando el
método Votos (línea 32).

Las líneas 9 y 10 declaran las propiedades de clase VotosMasVotado y


PersonaMasVotada, que contendrán el número de votos de la persona más votada
hasta el momento y su nombre completo. Al ser propiedades estáticas, no pertenecen
a ninguna instancia, sino que tienen existencia desde que se empieza a ejecutar la
aplicación. Sólo existe un espacio de almacenamiento de estas variables, a diferencia
de las propiedades Persona y Votos que se van creando (replicando) a medida que se
crean instancias de la clase.

El valor de VotosMasVotado se puede obtener en cualquier momento (y


conviene resaltar las palabras “en cualquier momento”) a través del método estático
VotosDelMasVotado (línea 42). Tanto la propiedad como el método, al ser estáticos,
son accesibles desde el comienzo de la aplicación. Análogamente, el valor de
PersonaMasVotada puede consultarse invocando el método estático
NombreDelMasVotado (línea 37).

Cada vez que se contabiliza un voto del candidato con el que se ha


instanciado la clase, se debe llamar al método Voto (línea 18), que aumenta el
número de votos del candidato (línea 19). Además se comprueba si este voto
convierte al candidato en el nuevo ganador temporal (línea 20) de la votación; si es
así, se actualiza el nombre y número de votos del candidato más votado hasta el
momento (líneas 21 y 22).
 BOBADILLA CAPÍTULO 4: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO CLASES 119

Con la clase Votacion implementada, podemos pasar a comprobar su


funcionamiento en una votación simulada de tres candidatos: Juan, Ana y Adela. La
clase PruebaVotacion realiza esta función:

1 class PruebaVotacion {
2 public static void main (String[] args) {
3
4 System.out.println (Votacion.NombreDelMasVotado() +
5 ": "+ Votacion.VotosDelMasVotado());
6
7 // Tenemos tres candidatos en esta votacion
8 Votacion Juan = new Votacion ("Juan Peire");
9 Votacion Ana = new Votacion ("Ana Garcia");
10 Votacion Adela = new Votacion ("Adela Sancho");
11
12 // empieza la votacion
13 Juan.Voto(); Ana.Voto(); Ana.Voto(); Ana.Voto();
Adela.Voto();
14 System.out.println (Votacion.NombreDelMasVotado() +
15 ": "+ Votacion.VotosDelMasVotado());
16
17 Juan.Voto(); Juan.Voto(); Juan.Voto(); Adela.Voto();
18 System.out.println (Votacion.NombreDelMasVotado() +
19 ": "+ Votacion.VotosDelMasVotado());
20
21 Adela.Voto(); Adela.Voto(); Ana.Voto(); Ana.Voto();
22 System.out.println (Votacion.NombreDelMasVotado() +
23 ": "+ Votacion.VotosDelMasVotado());
24
25 System.out.println (Juan.NombrePersona() + ": " +
Juan.Votos() );
26 System.out.println (Ana.NombrePersona() + ": " +
Ana.Votos() );
27 System.out.println (Adela.NombrePersona() + ": " +
Adela.Votos() );
28
29 }
30 }

En la línea 4 se accede al nombre y número de votos del candidato más


votado (y todavía no hemos creado ningún candidato). Esto es posible porque tanto
los métodos invocados como las variables accedidas son estáticos, y tienen
existencia antes de la creación de instancias de la clase. Observese como se accede a
los métodos a través del nombre de la clase, no a través del nombre de ninguna
instancia de la misma (que ni siquiera existe en este momento). El valor esperado
120 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

impreso por la instrucción System.out es el de inicialización de las variables estáticas


en la clase Votacion (null y 0).

En las líneas 8, 9 y 10 damos “vida” a nuestros candidatos: Juan, Ana y


Adela, creando instancias de la clase Votacion. En este momento, además de las
propiedades ya existentes: VotosMasVotado y PersonaMasVotada, se dispone de
tres copias de las propiedades de instancia: Persona y Votos.

En la línea 13 se contabilizan los siguie ntes votos: uno para Juan, tres para
Ana y uno para Adela. En la línea 14 se comprueba que llevamos un seguimiento
correcto de la persona más votada (Ana García, con 3 votos). Seguimos
referenciando a los métodos estáticos a través del nombre de la clase (no de una
instancia), que es la forma de proceder más natural y elegante.

En la línea 17 se contabilizan nuevos votos, obteniéndose resultados en la


18. De igual forma se actúa en las líneas 21 y 22.

En las líneas 25, 26 y 27 se imprimen los nombres y votos obtenidos por


cada candidato. Esta información se guarda en variables de instancia, accedidas
(como no podría ser de otra manera) por métodos de instancia, por lo que la
referencia a los métodos se hace a través de los identificadores de instancia (Juan,
Ana y Adela).

4.5.7 Resultados

4.6 PAQUETES Y ATRIBUTOS DE ACCESO


Los paquetes sirven para agrupar clases relacionadas, de esta manera, cada
paquete contiene un conjunto de clases. Las clases que hay dentro de un paquete
deben tener nombres diferentes para que puedan diferenciarse entre sí, pero no hay
ningún problema en que dos clases que pertenecen a paquetes diferentes tengan el
 BOBADILLA CAPÍTULO 4: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO CLASES 121

mismo nombre; los paquetes facilitan tanto el agrupamiento de clases como la


asignación de nombres.

Hasta ahora no hemos hecho un uso explícito de los paquetes en las clases
que hemos creado. Cuando no se especifica el nombre del paquete al que pertenece
una clase, esa clase pasa a pertenecer al “paquete por defecto”.

4.6.1 Definición de paquetes

Definir el paquete al que pertenece una clase es muy sencillo: basta con
incluir la sentencia package Nombre_Paquete; como primera sentencia de la clase
(obligatoriamente la primera). Ejemplo:
package Terminal;
public class Telefono {
……….
}

package Terminal;
public class Ordenador {
……….
}

package Terminal;
public class WebTV {
……….
}

En el ejemplo anterior, el paquete Terminal contiene tres clases: Telefono,


Ordenador y WebTV. El nombre de los ficheros que contienen las clases sigue
siendo Telefono.java, Ordenador.java y WebTV.java. Estos ficheros,
obligatoriamente, deben situarse en un directorio (carpeta) con el nombre del
paquete: Terminal.

Las clases definidas como public son accesibles desde fuera del paquete, las
que no presentan este atributo de acceso sólo son accesibles desde dentro del
paquete (sirven para dar soporte a las clases publicas del paquete).

En resumen, en este ejemplo se definen tres clases Telefono, Ordenador y


WebTV, pertenecientes a un mismo paquete Terminal y accesibles desde fuera del
paquete, por ser publicas. Los ficheros que contienen las clases se encuentran en un
122 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

directorio de nombre Terminal (obligatoriamente el mismo nombre que el del


paquete).

4.6.2 Utilización de las clases de un paquete

Cuando necesitamos hacer uso de todas o algunas de las clases de un


paquete, debemos indicarlo de manera explícita para que el compilador sepa donde
se encuentran las clases y pueda resolver las referencias a las mismas. Para ello
utilizamos la sentencia import:
• import Nombre_Paquete.Nombre_Clase;
• import NombrePaquete.*;

En el primer caso se especifica la clase que se va a utilizar (junto con la


referencia de su paquete). En el segundo caso se indica que queremos hacer uso
(potencialmente) de todas las clases del paquete. Conviene recordar que sólo
podemos hacer uso, desde fuera de un paquete, de las clases definidas como públicas
en dicho paquete.

Siguiendo el ejemplo del paquete Terminal, podríamos utilizar:


import Terminal.Ordenador;
class TerminalOficinaBancaria {
// podemos utilizar las referencias deseadas a la clase Ordenador
}

import Terminal.*;
class TerminalOficinaBancaria {
// podemos utilizar las referencias deseadas a las 3 clases del paquete Terminal
}

import Terminal.Telefono;
import Terminal.Ordenador;
import Terminal.WebTV;
class TerminalOficinaBancaria {
// podemos utilizar las referencias deseadas a las 3 clases del paquete Terminal

Podemos hacer uso de tantas sentencias import combinadas como deseemos,


referidas a un mismo paquete, a diferentes paquetes, con la utilización de asterisco o
sin él. Como se puede observar, el tercer ejemplo produce el mismo efecto que el
segundo: la posibilidad de utilización de las tres clases del paquete Terminal.
 BOBADILLA CAPÍTULO 4: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO CLASES 123

En este momento cabe realizarse una pregunta: ¿Cómo es posible que en


ejercicios anteriores hayamos hecho uso de la clase Math (en el paquete java.lang)
sin haber puesto la correspondiente sentencia import java.lang.Math, y lo mismo
con la clase String? ...es debido a que el uso de este paquete de utilidades es tan
común que los diseñadores del lenguaje decidieron que no fuera necesario
importarlo para poder utilizarlo.

Una vez que hemos indicado, con la sentencia import, que vamos a hacer
uso de una serie de clases, podemos referenciar sus propiedades y métodos de
manera directa. Una alternativa a esta posibilidad es no utilizar la sentencia import y
referenciar los objetos con caminos absolutos:
Terminal.Telefono MiTelefono = new Terminal.Telefono(....);

Aunque resulta más legible el código cuando se utiliza la sentencia import:


import Terminal.Telefono;
.......................
Telefono MiTelefono = new Telefono(....);

4.6.3 Proceso de compilación cuando se utilizan paquetes

El proceso de compilación cuando se importan paquetes puede ser el mismo


que cuando no se importan. Basta con realizar previamente la configuración
necesaria.

Las clases de un paquete se encuentran en el directorio que tiene el mismo


nombre que el paquete, y habitualmente compilaremos las clases en ese directorio,
generando los objetos .class. Siguiendo nuestro ejemplo tendremos Telefono.class,
Ordenador.class y WebTV.class en el directorio Terminal.

Para que el compilador funcione correctamente, debe encontrar los ficheros


.class de nuestros programas (que se encontrarán en el directorio de trabajo que
utilicemos) y el de las clases que importamos, que se encontrarán, en general, en los
directorios con nombres de paquetes.

En nuestro ejemplo, suponiendo que trabajamos en el directorio


C:\CursoJava\Introduccion y que hemos situado las clases Telefono, Ordenador y
WebTV en el directorio C:\Paquetes\Terminal, el compilador deberá buscar los
ficheros .class en estos dos directorios. Debemos actualizar la variable de entorno
del sistema CLASSPATH (ver lección 1) con el siguiente valor:
CLASSPATH = C:\CursoJava\Introducción;C:\Paquetes\Terminal
124 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

Según el sistema operativo que utilicemos deberemos cambiar la variable de


entorno de una manera u otra, por ejemplo, en Windows 98 lo podemos hacer a
través del Panel de Control (Sistema) o bien añadiendo la línea:
set CLASSPATH = C:\CursoJava\Introducción;C:\Paquetes\Terminal al fichero
autoexec.bat

También podemos utilizar el atributo –classpath en el compilador de java:


javac –classpath .;C:\CursoJavaIntroduccion;C:\Paquetes\Terminal
TerminalOficinaBancaria.java

4.6.4 Atributos de acceso a los miembros (propiedades y


métodos) de una clase

Hasta ahora hemos utilizado los atributos de acceso público (public) y


privado (private ) para indicar las posibilidades de acceso a las propiedades y
métodos de una clase desde el exterior a la misma. Hemos empleado estos atributos
según los principios básicos generales de la programación orientada a objetos: los
atributos son de acceso privado y sólo se puede acceder a ellos (en consulta o
modificación) a través de métodos públicos.

Si bien la manera con la que hemos actuado es, sin duda, la más habitual,
adecuada y aconsejada, existen diferentes posibilidades que tienen que ver con la
existencia de los paquetes, motivo por el cual hemos retrasado a este momento la
explicación detallada de los atributos de acceso.

Existen 4 posibles atributos de acceso, que listamos a continuación según el


nivel de restricción que imponen:

Tipo de Palabra Ejemplo Acceso desde Acceso desde una


acceso reservada una clase del clase de otro
mismo paquete paquete
Privado private private int PPrivada; No No
Sin int PSinEspecificar; Sí No
especificar
Protegido protected protected int Sí No
PProtegida;
Publico public public int PPublica; Sí Sí

El acceso desde una clase perteneciente al mismo paquete sólo está


prohibido si el miembro es privado. El acceso desde una clase perteneciente a otro
 BOBADILLA CAPÍTULO 4: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO CLASES 125

paquete sólo está permitido si el miembro es público. Los demás atributos de acceso
tienen un mayor sentido cuando utilizamos el mecanismo de herencia, que se
explicará un poco más adelante.

Ejemplo:
package ConversionDeMedidas;
public class ConversionDeDistancias {
final public double LibrasAKilos = ...;
.................
}

package VentaDeProductos;
import ConversionDeMedidas.ConversionDeDistancias;
class VentaDeNaranjas {
........................
double Kilos = Libras * LibrasAKilos;
.......................
}

La propiedad constante LibrasAKilos ha sido declarada como pública en la


clase ConversionDeDistancias, dentro del paquete ConversionDeMedidas. Al
declararse como pública, puede ser referenciada desde una clase (VentaDeNaranjas)
situada en un paquete diferente (VentaDeProductos) al anterior. Esto no sería
posible si LibrasAKilos tuviera un atributo de acceso diferente a public.

Cualquiera de los ejemplos que hemos realizado en las últimas lecciones nos
sirve para ilustrar el uso de propiedades privadas situadas en clases pertenecientes a
un mismo paquete. Puesto que no incluíamos ninguna sentencia package, todas
nuestras clases pertenecían al “paquete por defecto”.

4.7 EJEMPLO: MÁQUINA EXPENDEDORA


En esta lección se desarrolla el software necesario para controlar el
funcionamiento de una máquina expendedora sencilla. Esta máquina suministrará
botellas de agua, naranja y coca-cola, permitiendo establecer los precios de cada
producto. Así mismo admitirá monedas de un euro y de 10 céntimos de euro (0.1
euros). El diseño se realizará de tal manera que podamos definir con facilidad una
máquina con cualquier número de productos.
126 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

Si analizamos el ejercicio con detalle, descubriremos que existe la necesidad


de mantener la cuenta de:
• Cuantas botellas de agua nos quedan (en el depósito de botellas de agua)
• Cuantas botellas de naranja nos quedan (en el depósito de botellas de naranja)
• Cuantas botellas de coca-cola nos quedan (en el depósito de botellas de coca-cola)
• Cuantas monedas de un euro nos quedan (en el depósito de monedas de un euro)
• Cuantas monedas de un décimo de euro nos quedan (en el depósito de monedas de
10 céntimos de euro)

En definitiva, surge la necesidad de utilizar una clase que nos gestione un


almacén de elementos. Esta clase la hemos implementado en ejercicios anteriores y
podría ser reutilizada. A continuación mostramos el código de la misma, sin
comentar sus propiedades y métodos, ya explicados en ejercicios anteriores.

1 public class MaquinaAlmacen {


2 private short Capacidad;
3 private short NumeroDeElementos = 0;
4
5 MaquinaAlmacen(short Capacidad) {
6 this.Capacidad = Capacidad;
7 }
8
9 public short DimeNumeroDeElementos() {
10 return (NumeroDeElementos);
11 }
12
13 public short DimeCapacidad() {
14 return (Capacidad);
15 }
16
17 public boolean HayElemento() {
18 return (NumeroDeElementos != 0);
19 }
20
21 public boolean HayHueco() {
22 return (NumeroDeElementos != Capacidad);
23 }
24
25 public void MeteElemento() {
26 NumeroDeElementos++;
27 }
28
29 public void SacaElemento() {
30 NumeroDeElementos--;
31 }
32
 BOBADILLA CAPÍTULO 4: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO CLASES 127

33 public void RellenaAlmacen() {


34 NumeroDeElementos = Capacidad;
35 }
36
37 } // MaquinaAlmacen

Una vez que disponemos de la clase MaquinaAlmacen, estamos en


condiciones de codificar la clase MaquinaModeloSencillo, que definirá una máquina
expendedora con tres almacenes de bebidas y dos almacenes de monedas. Además
es necesario que en cada máquina instanciada se puedan poner precios
personalizados, puesto que el precio al que se vende un producto varía dependiendo
de donde se ubica la máquina.

La clase MaquinaModeloSencillo puede implementarse de la siguiente


manera:

1 public class MaquinaModeloSencillo {


2
3 public MaquinaAlmacen Deposito1Euro = new
MaquinaAlmacen((short)8);
4 public MaquinaAlmacen Deposito01Euro = new
MaquinaAlmacen((short)15);
5
6 public MaquinaAlmacen DepositoCocaCola = new
MaquinaAlmacen((short)10);
7 public MaquinaAlmacen DepositoNaranja = new
MaquinaAlmacen((short)5);
8 public MaquinaAlmacen DepositoAgua = new
MaquinaAlmacen((short)8);
9
10 private float PrecioCocaCola = 1.0f;
11 private float PrecioNaranja = 1.3f;
12 private float PrecioAgua = 0.6f; //precio recomendado
13
14 public void PonPrecios (float CocaCola, float Naranja,
float Agua) {
15 PrecioCocaCola = CocaCola;
16 PrecioNaranja = Naranja;
17 PrecioAgua = Agua;
18 }
19
20 public float DimePrecioCocaCola() {
21 return PrecioCocaCola;
22 }
23
24 public float DimePrecioNaranja() {
25 return PrecioNaranja;
128 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

26 }
27
28 public float DimePrecioAgua() {
29 return PrecioAgua;
30 }
31
32 public void MostrarEstadoMaquina() {
33 System.out.print("CocaColas: "+
34 DepositoCocaCola.DimeNumeroDeElementos()+ " ");
35 System.out.print("Naranjas: "+
36 DepositoNaranja.DimeNumeroDeElementos() + " ");
37 System.out.println("Agua: "+
38 DepositoAgua.DimeNumeroDeElementos() + " ");
39
40 System.out.print("1 Euro: "+
41 Deposito1Euro.DimeNumeroDeElementos() + " ");
42 System.out.println("0.1 Euro: "+
43 Deposito01Euro.DimeNumeroDeElementos() + " ");
44 System.out.println();
45 }
46
47 }

En la línea 3 se define el depósito de monedas de 1 euro (Deposito1Euro),


inicializado con una capacidad de 8 elementos. En la línea 4 se hace una definición
similar a la anterior para las monedas de 10 céntimos de euro (Deposito01Euro), con
capacidad inicial para 15 monedas.

En la línea 6 se define el primer depósito de bebidas (DepositoCocaCola ),


con una capacidad para 10 recipientes. En las líneas 7 y 8 se definen
DepositoNaranja y DepositoAgua, con capacidades de 5 y 8 recipientes.

Los 5 depósitos han sido declarados con el atributo de acceso public, de esta
manera pueden ser referenciados directamente por las clases que instancien a
MaquinaModeloSencillo . Una alternativa menos directa, pero más adaptada a la
programación orientada a objetos sería declararlos como privados e incorporar los
métodos necesarios para obtener sus referencias, por ejemplo:
public MaquinaModeloSencillo DameAlmacenAgua();
public MaquinaModeloSencillo DameAlmacenNaranja();
.......................

o bien
public MaquinaModeloSencillo DameAlmacen(String TipoAlmacen);
 BOBADILLA CAPÍTULO 4: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO CLASES 129

Las líneas 10, 11 y 12 declaran las variables PrecioCocaCola ,


PrecioNaranja y PrecioAgua. Los valores por defecto de estos precios se establecen
como 1, 1.3 y 0.6 euros. En la línea 14 se define el método PonPrecios, que permite
modificar en cualquier momento el precio de los tres productos que admite una
instancia de la clase MaquinaSencilla. Los métodos DimePrecioCocaCola,
DimePrecioNaranja y DimePrecioAgua (líneas 20, 24 y 28) completan la
funcionalidad necesaria para gestionar los precios de los productos.

Finalmente, en la línea 32, se implementa el método


MostrarEstadoMaquina, que muestra el número de elementos que contiene cada uno
de los depósitos.

De forma análoga a como hemos implementado la clase


MaquinaModeloSencillo , podríamos crear nuevas clases que definieran más
productos, o mejor todavía, podríamos ampliar esta clase con nuevos productos; esta
última posibilidad se explica en el siguiente tema.

Las máquinas definidas, o las que podamos crear siguiendo el patrón de


MaquinaModeloSencillo , necesitan un elemento adicional: el control de las
monedas. Cuando un usuario trata de comprar una botella de un producto, no sólo es
necesario verificar que se cuenta con existencias de ese producto, sino que también
hay que realizar un control de las monedas que el usuario introduce y del cambio
que hay que devolverle, pudiéndose dar la circunstancia de que no se le pueda
suministrar un producto existente porque no se dispone del cambio necesario.

Para realizar el control de las monedas introducidas y del cambio que hay
que devolver, se ha implementado la clase MaquinaAutomataEuros: el concepto más
importante que hay que entender en esta clase es el significado de su primer método
IntroducciónMonedas, que admite como parámetros una máquina de tipo
MaquinaModeloSencillo y un precio. Este método, además de realizar todo el
control monetario, devuelve true si el pago se ha realizado con éxito y false si no ha
sido así.

Los detalles de esta clase pueden obviarse en el contexto general del


ejercicio. En cualquier caso, los lectores interesados en su funcionamiento pueden
ayudarse de las siguientes indicaciones:

El método IntroduccionMonedas admite las entradas de usuario (u, d, a) que


simulan la introducción de una moneda de un euro, de un décimo de euro o de la
pulsación de un botón ´”anular operación”. Existe una propiedad muy importante,
Acumulado, donde se guarda la cantidad de dinero que el usuario lleva introducido;
el método implementa un bucle de introducción de monedas (línea 15) hasta que
Acumulado deje de ser menor que el precio del producto (línea 47) o el usuario anule
la operación (líneas 38 a 41).
130 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

Cuando el usuario introduce un euro (líneas 17 y 20), se comprueba si existe


hueco en el depósito de monedas de euro de Maquina (línea 21). Si es así se
introduce el euro en el depósito (línea 22) y se incrementa la propiedad Acumulado
(línea 23), en caso contrario se muestra un mensaje informando de la situación (línea
25) y el euro no introducido en el depósito lo podrá recoger el usuario.

La introducción de monedas de décimo de euro se trata de manera


equivalente a la introducción de euros explicada en el párrafo anterior.

Al salirse del bucle (línea 48) se comprueba si la operación ha sido anulada,


en cuyo caso se devuelve la cantidad de moneda introducida (Acumulado). Si la
operación no fue anulada (línea 50) es necesario comprobar si tenemos cambio
disponible (línea 51); si es así se devuelve la cantidad sobrante Acumulado-Precio
en la línea 52. Si no hay cambio disponible (línea 53) se visualiza un mensaje
indicándolo, se devuelve todo el dinero introducido y se almacena el valor true en la
propiedad Anulado.

Finalmente, en la línea 58, devolvemos la indicación de operación con éxito


(es decir, no anulada).

El análisis de los métodos CambioDisponible y Devolver se deja al lector


interesado en los detalles de funcionamiento de esta clase.

1 public class MaquinaAutomataEuros {


2
3 // ***************************************************
4 // * Recoge monedas en 'Maquina' para cobrar 'Precio'.
// * Devuelve 'true'
5 // * si el pago se ha realizado con exito y 'false' en
// * caso contrario
6 // ****************************************************
7 public static boolean IntroduccionMonedas
8 (MaquinaModeloSencillo Maquina, float Precio) {
9
10 String Accion;
11 char Car;
12 boolean Pagado=false, Anulado = false, CambioOK ;
13 float Acumulado = 0;
14
15 do {
16 System.out.println("-- u,d,a --");
17 Accion = Teclado.Lee_String();
18 Car = Accion.charAt(0);
19 switch (Car) {
20 case 'u':
21 if (Maquina.Deposito1Euro.HayHueco()) {
 BOBADILLA CAPÍTULO 4: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO CLASES 131

22 Maquina.Deposito1Euro.MeteElemento();
23 Acumulado = Acumulado + 1f;
24 } else
25 System.out.println("Temporalmente esta
26 maquina no cepta monedas de un euro");
27 break;
28
29 case 'd':
30 if (Maquina.Deposito01Euro.HayHueco()) {
31 Maquina.Deposito01Euro.MeteElemento();
32 Acumulado = Acumulado + 0.1f;
33 } else
34 System.out.println("Temporalmente esta
35 maquina no acepta monedas de 0.1 euros");
36 break;
37
38 case 'a':
39 System.out.println("Operación anulada");
40 Anulado = true;
41 break;
42 }
43
44 Maquina.MostrarEstadoMaquina();
45
46 } while (Acumulado<Precio || Anulado);
47
48 if (Anulado)
49 Devolver(Maquina,Acumulado);
50 else
51 if (CambioDisponible(Maquina,Acumulado-Precio)) {
52 Devolver (Maquina,Acumulado-Precio);
53 } else {
54 System.out.println("La maquina no dispone del
cambio necesario");
55 Devolver(Maquina,Acumulado);
56 Anulado = true;
57 }
58 return (!Anulado);
59 }
60
61
62
63 // ******************************************************
64 // * Indica si es posible devolver 'Cantidad' euros en
// * 'Maquina'
65 // ******************************************************
66 private static boolean CambioDisponible
67 (MaquinaModeloSencillo Maquina, float Cantidad) {
132 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

68
69 int Monedas1,Monedas01;
70
71 Cantidad = Cantidad + 0.01f; //Evita problemas de
//falta de precision
72 Monedas1 = (int) Math.floor((double) Cantidad);
73 Cantidad = Cantidad - (float) Monedas1;
74 Monedas01 = (int) Math.floor((double) Cantidad*10f);
75 return {
(Maquina.Deposito1Euro.DimeNumeroDeElementos()>=Monedas1)&&
(Maquina.Deposito01Euro.DimeNumeroDeElementos()>=Monedas01));
76 }
77
78
79
80 // **************************************************
81 // * Devuelve la cantidad de dinero indicada,
82 // * actualizando los almacenes de monedas
83 // **************************************************
84 private static void Devolver (MaquinaModeloSencillo
85 Maquina, float Cantidad) {
86
87 int Monedas1,Monedas01;
88 Cantidad = Cantidad + 0.01f; //Evita problemas de
//falta de precision
89 Monedas1 = (int) Math.floor((double)Cantidad);
90 Cantidad = Cantidad - (float) Monedas1;
91 Monedas01 = (int) Math.floor((double)Cantidad*10f);
92
93 for (int i=1;i<=Monedas1;i++){
94 Maquina.Deposito1Euro.SacaElemento();
95 // Sacar 1 moneda de un euro
96 }
97
98 for (int i=1;i<=Monedas01;i++){
99 Maquina.Deposito01Euro.SacaElemento();
100 // Sacar 1 moneda de 0.1 euro
101 }
102 System.out.println("Recoja el importe: "+Monedas1+"
103 monedas de un euro y "+Monedas01+
104 " monedas de 0.1 euros");
105
106 }
107
108 } // clase
 BOBADILLA CAPÍTULO 4: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO CLASES 133

Las clases anteriores completan la funcionalidad de la máquina expendedora


que se pretendía implementar en este ejercicio. Para finalizar nos basta con crear una
clase que haga uso de las anteriores e interaccione con el usuario. A esta clase la
llamaremos MaquinaControlador. La jerarquía de clases del ejercicio nos queda de
la siguiente manera:

La clase MaquinaControlador define la propiedad MiMaquina, instanciando


MaquinaModeloSencillo (línea 7); posteriormente se establecen los precios de los
productos (línea 8), se introduce alguna moneda en los depósitos (una de un euro y
dos de 0.1 euro) en las líneas 9 a 11 y se rellenan los almacenes de coca-cola y
naranja (líneas 12 y 13). Suponemos que no disponemos de botellas de agua en este
momento.

Nos metemos en un bucle infinito de funcionamiento de la máquina (líneas


17 a 70), aunque como se puede observar en la línea 70 hemos permitido salir del
bucle escribiendo cualquier palabra que comience por ‘s’. En el bucle, el usuario
puede pulsar entre las opciones ‘c’, ‘n’ y ‘a’, correspondientes a coca-cola, naranja y
agua.

Si el usuario selecciona un producto concreto, por ejemplo naranja (línea


36), se muestra un mensaje con su elección (línea 37) y se comprueba si hay
elementos en el almacén del producto (línea 38). Si no hay elementos de ese
producto se muestra la situación en un mensaje (líneas 46 y 47), si hay existencias
invocamos al método estático IntroduccionMonedas de la clase
MaquinaAutomataEuros (línea 39); si todo va bien se extrae el elemento (la botella
de naranja) y se le indica al usuario que la recoja (líneas 41 y 42), en caso contrario
el método IntroduccionMonedas se encarga de realizar las acciones oportunas con
las monedas.
134 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

Cuando el usuario escoge cualquier otro producto (coca-cola o agua) el


funcionamiento es equivalente al explicado en el párrafo anterior.

1 public class MaquinaControlador {


2 public static void main(String[] args){
3
4 String Accion;
5 char Car;
6
7 MaquinaModeloSencillo MiMaquina = new
MaquinaModeloSencillo();
8 MiMaquina.PonPrecios(1.1f, 1.3f, 0.8f);
9 MiMaquina.Deposito1Euro.MeteElemento();
10 MiMaquina.Deposito01Euro.MeteElemento();
11 MiMaquina.Deposito01Euro.MeteElemento();
12 MiMaquina.DepositoCocaCola.RellenaAlmacen();
13 MiMaquina.DepositoNaranja.RellenaAlmacen();
14 //MiMaquina.Deposito1Euro.RellenaAlmacen(); No nos
15 // ha llegado el suministro de agua
16
17 do {
18 System.out.println("-- c,n,a,s --");
19 Accion = Teclado.Lee_String();
20 Car = Accion.charAt(0);
21 switch (Car) {
22 case 'c':
23 System.out.println("Ha seleccionado
Coca cola");
24 if(MiMaquina.DepositoCocaCola.HayElemento()) {
25 if (MaquinaAutomataEuros.IntroduccionMonedas
26 (MiMaquina,MiMaquina.DimePrecioCocaCola())) {
27 MiMaquina.DepositoCocaCola.SacaElemento();
28 System.out.println("No olvide coger su
cocacola");
29 // Sacar fisicamente la CocaCola
30 }
31 }
32 else
33 System.out.println("Producto agotado");
34 break;
35
36 case 'n':
37 System.out.println("Ha seleccionado Naranja");
38 if (MiMaquina.DepositoNaranja.HayElemento()) {
39 if (MaquinaAutomataEuros.IntroduccionMonedas
40 (MiMaquina,MiMaquina.DimePrecioNaranja())) {
41 MiMaquina.DepositoNaranja.SacaElemento();
42 System.out.println("No olvide coger su
 BOBADILLA CAPÍTULO 4: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO CLASES 135

naranja");
43 // Sacar fisicamente la Naranja
44 }
45 }
46 else
47 System.out.println("Producto agotado");
48 break;
49
50 case 'a':
51 System.out.println("Ha seleccionado Agua");
52 if (MiMaquina.DepositoAgua.HayElemento()) {
53 if MaquinaAutomataEuros.IntroduccionMonedas
54 (MiMaquina, MiMaquina.DimePrecioAgua())) {
55 MiMaquina.DepositoAgua.SacaElemento();
56 System.out.println("No olvide coger su
agua");
57 // Sacar fisicamente el agua
58 }
59 }
60 else
61 System.out.println("Producto agotado");
62 break;
63
64 default:
65 System.out.println("Error de seleccion,
intentelo de nuevo");
66 break;
67 }
68 MiMaquina.MostrarEstadoMaquina();
69
70 } while (!Accion.equals("s"));
71 }
72 }

A continuación se muestra una posible ejecución del software desarrollado:

En primer lugar se adquiere una botella de naranja (1.3 euros) suministrando


el precio exacto. Obsérvese como se incrementan los depósitos de monedas y se
decrementa el de botellas de naranja.

Posteriormente se trata de adquirir una coca-cola (1.1 euros) con dos


monedas de euro. El almacén de euros va incrementando el número de elementos,
pero posteriormente, cuando se descubre que la máquina no dispone del cambio
necesario, se devuelven las monedas:
136 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)
CAPÍTULO 5

PROGRAMACIÓN ORIENTADA A
OBJETOS USANDO HERENCIA

5.1 HERENCIA
5.1.1 Funcionamiento básico

La herencia es un mecanismo muy importante en la programación orientada


a objetos. Gracias a la herencia podemos crear clases que especializan a otras
previamente definidas.

Pongamos como ejemplo una clase Publicacion en la que se determinan


propiedades básicas comunes a todas las publicaciones (libros, revistas, periódicos,
etc.). Entre estas propiedades pueden definirse el número de páginas y el precio de la
publicación:

1 class Publicacion {
2 public int NumeroDePaginas;
3 public float Precio;
4 }

Partiendo de la clase base Publicacion, podemos especializar un libro de la


siguiente manera:
1 class Libro extends Publicacion {
2 public String Titulo;
3 public String TipoPortada;
4 public String ISBN;
5 public String NombreAutor;
6 public String Editorial;
7 }
138 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

La clase Libro extiende (y se emplea la palabra reservada extends) a la clase


Publicación. La clase Libro contiene ahora todas sus propiedades más las
propiedades de la clase Publicacion. De esta manera, un libro se define por
NumeroDePaginas, Precio , Titulo , TipoPortada, etc.

Se dice (en terminología orientada a objetos) que Libro es una clase


derivada o subclase de Publicación, mientras que Publicación es la superclase de
Libro.

Para comprobar que un libro contiene tanto las propiedades de la superclase


como las de la subclase realizamos el siguiente programa:

1 class PruebaLibro {
2 public static void main(String[] args) {
3 Libro MiLibro = new Libro();
4 MiLibro.NombreAutor = "Frederick Forsyth";
5 MiLibro.Titulo = "El manifiesto negro";
6 MiLibro.Editorial = "Circulo de lectores";
7 MiLibro.TipoPortada = "Dura";
8 MiLibro.NumeroDePaginas = 575;
9 }
10 }

Podemos comprobar como la clase Libro ha heredado las propiedades de la


clase Publicacion.

Una superclase puede tener cualquier número de subclases:

1 class Periodico extends Publicacion {


2 public String Nombre;
3 public String Fecha;
4 }

Publicacion

Libro Periodico

El mecanismo de herencia no sólo actúa sobre las propiedades, lo hace sobre


todos los miembros (métodos y propiedades) de las clases, de esta manera, las clases
anteriores se pueden completar con métodos:
 BOBADILLA CAPÍTULO 5: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO HERENCIA 139

1 class Publicacion2 {
2 private int NumeroDePaginas;
3 private float Precio;
4
5 public int DimeNumeroDePaginas(){
6 return NumeroDePaginas;
7 }
8
9 public void PonNumeroDePaginas(int NumeroDePaginas){
10 this.NumeroDePaginas = NumeroDePaginas;
11 }
12
13 public float DimePrecio(){
14 return Precio;
15 }
16
17 public void PonPrecio(float Precio){
18 this.Precio = Precio;
19 }
20 }

1 class Periodico2 extends Publicacion2 {


2 private String Nombre;
3 private String Fecha;
4
5 public String DimeNombre() {
6 return Nombre;
7 }
8
9 public void PonNombre(String Nombre) {
10 this.Nombre = Nombre;
11 }
12
13 public String DimeFecha() {
14 return Fecha;
15 }
16
17 public void PonFecha(String Fecha) {
18 this.Fecha = Fecha;
19 }
20 }

En este caso la clase derivada (o subclase) Periodico2 contiene tanto las


propiedades como los métodos de la superclase Publicacion2, aunque como veremos
en el siguiente apartado no todos los miembros son directamente accesibles. La clase
PruebaPeriodico2 nos muestra como podemos acceder a los valores de las
propiedades de Publicacion2.
140 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

1 class PruebaPeriodico2 {
2 public static void main(String[] args) {
3 Periodico2 MiPeriodico = new Periodico2();
4 MiPeriodico.PonNumeroDePaginas(65);
5 MiPeriodico.PonPrecio(0.9f);
6 MiPeriodico.PonFecha("22/02/2003");
7 System.out.println(MiPeriodico.DimeNumeroDePaginas());
8 }
9 }

5.1.2 Accesibilidad a los miembros heredados en una subclase

Las subclases heredan todos los miembros de su superclase, aunque no


todos los miembros tienen porque ser accesibles. En particular, los miembros
privados de una superclase no son directamente accesibles en sus subclases. La
siguiente tabla muestra las posibilidades existentes:

Tipo de Palabra Ejemplo Acceso desde una Acceso desde una


acceso reservada subclase del subclase de otro
mismo paquete paquete
Privado private private int PPrivada; No No
Sin int PSinEspecificar; Sí No
especificar
Protegido protected protected int Sí Sí
PProtegida;
Publico public public int PPublica; Sí Sí

La única diferencia entre las posibilidades de acceso de una clase a los


miembros de otra y de una subclase a los miembros de su superclase está en que los
miembros protegidos de la superclase sí son accesibles a subclases situadas en
paquetes diferentes a la superclase.

En nuestro ejemplo, en el que sólo se emplean propiedades privadas y


métodos públicos, las propiedades privadas de la superclase Publicacion2
(NumeroDePaginas y Precio) no son directamente accesibles desde la subclase
Periodico2.

Existe la posibilidad de “ocultar” propiedades de la superclase, definiendo


propiedades con el mismo nombre en la subclase. En este caso, en la subclase, al
referenciar directamente el nombre nos referimos a la propiedad de la subclase; si
 BOBADILLA CAPÍTULO 5: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO HERENCIA 141

escribimos super.Nombre nos referimos a la propiedad de la superclase. Resulta


conveniente no utilizar este mecanismo de ocultación de propiedades en el diseño de
las clases.

Los métodos tienen un mecanismo similar al de ocultación de propiedades;


se denomina “redefinición” y nos permite volver a definir el comportamiento de los
métodos de la superclase. Para poder redefinir un método debe tener la misma firma
que el equivalente en la superclase, además su atributo de acceso debe ser el mismo
o menos restrictivo que el original.

Al igual que ocurre con las propiedades, nos podemos referir al método de la
superclase utilizando la palabra reservada super.

5.1.3 Constructores de las subclases

Normalmente, entre otras posibles acciones, los constructores inicializan las


propiedades de las clases, es decir establecen su estado inicial. En las clases
derivadas de una superclase, los constructores, además de establecer el estado inicial
de su propia subclase deben establecer el estado inicial de la superclase.

Para realizar la acción descrita en el párrafo anterior, los constructores de las


subclases tienen la posibilidad de invocar a los constructores de sus superclases. La
sintaxis es la siguiente:

super (parámetros) // donde puede haber cero o mas parámetros

La llamada super, en caso de utilizarse, debe ser (obligatoriamente) la


primera sentencia del constructor.

Cuando en un constructor de una subclase no se utiliza la sentencia super, el


compilador inserta automáticamente super() como primera instrucción. Esto puede
dar lugar a errores de compilación, puesto que si en la superclase hemos definido
algún constructor y no hemos definido super(), el compilador no encontrará este
constructor. Si en la superclase no hemos definido ningún constructor, no existirán
problemas, puesto que super() es el constructor por defecto que crea el compilador
cuando nosotros no definimos otro. En cualquier caso, cuando se necesite, se
recomienda poner explícitamente la llamada super().

Repasemos estos conceptos introduciendo constructores en el ejemplo de las


publicaciones que estamos siguiendo:
142 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

1 class Publicacion3 {
2 private int NumeroDePaginas;
3 private float Precio;
4
5 Publicacion3() {
6 NumeroDePaginas = 0;
7 Precio = 0f;
8 }
9
10 Publicacion3(int NumeroDePaginas) {
11 PonNumeroDePaginas(NumeroDePaginas);
12 }
13
14 Publicacion3(float Precio) {
15 PonPrecio(Precio);
16 }
17
18 Publicacion3(int NumeroDePaginas, float Precio) {
19 this(NumeroDePaginas);
20 PonPrecio(Precio);
21 }
22
23 Publicacion3(float Precio, int NumeroDePaginas) {
24 this(NumeroDePaginas,Precio);
25 }
26
27 public int DimeNumeroDePaginas(){
28 return NumeroDePaginas;
29 }
30
31 public void PonNumeroDePaginas(int NumeroDePaginas){
32 this.NumeroDePaginas = NumeroDePaginas;
33 }
34
35 public float DimePrecio(){
36 return Precio;
37 }
38
39 public void PonPrecio(float Precio){
40 this.Precio = Precio;
41 }
42 }

La clase Publicacion3 contiene todos los constructores posibles para


inicializar el valor de dos propiedades. Tenemos el constructor sin parámetros (al
que se llamará automáticamente si no ponemos la sentencia super en algún
 BOBADILLA CAPÍTULO 5: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO HERENCIA 143

constructor de alguna subclase), constructores con un solo parámetro y constructores


con dos parámetros.

Cada sentencia this(parámetros) invoca al constructor de la misma clase que


coincide en firma con la llamada; por ejemplo, en la línea 19, se invoca al
constructor de la línea 10 y en la línea 24 al constructor de la 18.

Ahora modificamos la subclase Periodico2 para que contenga constructores:

1 class Periodico3 extends Publicacion3 {


2 private String Nombre;
3 private String Fecha;
4
5 Periodico3() {
6 super();
7 Nombre = null;
8 Fecha = null;
9 }
10
11 Periodico3(String Nombre, String Fecha) {
12 super();
13 this.Nombre = Nombre;
14 this.Fecha = Fecha;
15 }
16
17 Periodico3(int NumeroDePaginas, float Precio) {
18 super(NumeroDePaginas, Precio);
19 this.Nombre = null;
20 this.Fecha = null;
21 }
22
23 Periodico3(String Nombre, String Fecha,
24 int NumeroDePaginas, float Precio) {
25 super(NumeroDePaginas, Precio);
26 this.Nombre = Nombre;
27 this.Fecha = Fecha;
28 }
29
30 public String DimeNombre() {
31 return Nombre;
32 }
33
34 public void PonNombre(String Nombre) {
35 this.Nombre = Nombre;
36 }
37
38 public String DimeFecha() {
144 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

39 return Fecha;
40 }
41
42 public void PonFecha(String Fecha) {
43 this.Fecha = Fecha;
44 }
45 }

La clase Periodico3 contiene una serie de constructores representativos


(poner todos los posibles constructores resulta innecesario e inviable dado el gran
número de combinaciones posibles). Estos constructores nos permiten inicializar
explícitamente todas las propiedades de la subclase y la superclase (constructor
definido en la línea 23) , sólo las propiedades de la subclase (línea 11), sólo las
propiedades de la superclase (línea 17) o no definir explícitamente ninguna
propiedad (constructor vacío). En este último caso, aunque no se implementase, el
compilador realizaría implícitamente las mismas acciones.

Para comprobar el funcionamiento de la clase Periodico3 creamos la clase


PruebaPeriodico3, que realiza diversas instancias que invocan a cada uno de los
constructores definidos:

1 class PruebaPeriodico3 {
2 public static void main(String[] args) {
3 Periodico3 PeriodicoA = new Periodico3();
4 Periodico3 PeriodicoB = new Periodico3("El Mundo",
"12/08/2003");
5 Periodico3 PeriodicoC = new Periodico3(64, 0.9f);
6 Periodico3 PeriodicoD = new
Periodico3("El Pais","7/6/2003",45,0.9f);
7 Imprimir(PeriodicoA);
8 Imprimir(PeriodicoB);
9 Imprimir(PeriodicoC);
10 Imprimir(PeriodicoD);
11 }
12
13 private static void Imprimir(Periodico3 Periodico) {
14 String Nombre, Fecha;
15 int NumeroDePaginas;
16 float Precio;
17 Nombre = Periodico.DimeNombre();
18 Fecha = Periodico.DimeFecha();
19 NumeroDePaginas = Periodico.DimeNumeroDePaginas();
20 Precio = Periodico.DimePrecio();
21 System.out.println(Nombre+", "+Fecha+",
"+NumeroDePaginas+", "+Precio);
22 }
23 }
 BOBADILLA CAPÍTULO 5: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO HERENCIA 145

En la clase PruebaPeriodico3 se crea una instancia de Periodico3 sin


proporcionar ningún parámetro (línea 3), otra instancia proporcionando valores de
las propiedades de la subclase (línea 4), otra con valores de la superclase (línea 5) y
una última instancia que incluye valores iniciales de las propiedades de la subclase y
la superclase (línea 6).

A partir de la línea 13 codificamos un método Imprimir que visualiza los


contenidos de las 4 propiedades accesibles en la clase Periodico3, para ello
utilizamos los métodos de la clase derivada DimeNombre y DimeFecha y los
métodos de la superclase DimeNumeroDePaginas y DimePrecio.

Tras realizar las llamadas pertinentes al método Imprimir, obtenemos los


resultados esperados, comprobándose el funcionamiento de los constructores de las
clases derivadas:

5.1.4 El modificador final

Si utilizamos el modificador final al definir una clase evitamos que se


puedan construir clases derivadas de la misma:

public final class NombreClase {


// contenido de la clase
}

public class ClaseDerivada extends NombreClase {


}

La utilización del modificador final asociado a las clases no es muy habitual,


puesto que restringe la posibilidad de reutilizar las clases usando el mecanismo de
herencia.
146 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

5.2 EJEMPLOS
En este apartado se muestran dos ejemplos sencillos de herencia. Los
ejemplos son muy parecidos entre sí, con la intención de que el lector pueda realizar
el segundo tomando como referencia el primero de ellos.

5.2.1 Figuras geométricas

En este primer ejemplo se pretende crear clases que representen diferentes


figuras geométricas bidimensionales. Nosotros implementaremos el círculo y el
rectángulo. Las figuras geométricas se caracterizarán por su color y su posición. En
los círculos, además, se establece un radio; en los rectángulos es necesario definir la
longitud de sus lados.

La estructura de las clases presenta el siguiente diagrama:

Figura

Circulo Rectángulo

La clase Figura, que se muestra a continuación, contiene las propiedades


privadas ColorFigura y Posicion (líneas 4 y 5). ColorFigura es del tipo Color (línea
1) y Posicion es un vector (matriz lineal) de dos componentes: Posicion[0]
representando la coordenada X del centro de la figura y Posición[1] representando la
coordenada Y del centro de la figura.

1 import java.awt.Color;
2
3 public class Figura {
4 private Color ColorFigura;
5 private int[] Posicion = new int[2];
6
7 Figura() {
8 EstableceColor(Color.black);
9 Posicion[0] = 0;
10 Posicion[1] = 0;
11 }
12
 BOBADILLA CAPÍTULO 5: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO HERENCIA 147

13 Figura(Color color) {
14 EstableceColor(color);
15 }
16
17 Figura(Color color, int[] Posicion) {
18 EstableceColor(color);
19 EstableceCentro(Posicion);
20 }
21
22 public void EstableceColor(Color color) {
23 ColorFigura = color;
24 }
25
26 public Color DimeColor() {
27 return ColorFigura;
28 }
29
30 public void EstableceCentro(int[] Posicion) {
31 this.Posicion[0] = Posicion[0];
32 this.Posicion[1] = Posicion[1];
33 }
34
35 public int[] DimeCentro() {
36 return Posicion;
37 }
38
39 }

En la clase Figura se incluyen los métodos públicos básicos de consulta y


actualización de las propiedades privadas: EstableceColor y DimeColor (líneas 22 y
26 respectivamente) y EstableceCentro y DimeCentro (líneas 30 y 35
respectivamente).

Los constructores de la clase hacen llamadas a los métodos EstableceColor


y EstableceCentro para inicializar el contenido de las propiedades.

Una vez creada la clase Figura, podemos definir las diferentes subclases que
especializan y amplían a esta superclase, representando diferentes figuras
geométricas. A modo de ejemplo proporcionamos la clase derivada Circulo :

1 import java.awt.Color;
2
3 public class Circulo extends Figura {
4 private double Radio;
5
6 Circulo(double Radio) {
7 EstableceRadio(Radio);
148 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

8 }
9
10 Circulo(double Radio,Color color) {
11 super(color);
12 EstableceRadio(Radio);
13 }
14
15 Circulo(double Radio, Color color, int[] Posicion) {
16 super(color, Posicion);
17 EstableceRadio(Radio);
18 }
19
20 public void EstableceRadio(double Radio) {
21 this.Radio = Radio;
22 }
23
24 public double DimeRadio() {
25 return Radio;
26 }
27 }

Circulo es una clase derivada de Figura (línea 3) que incorpora una nueva
propiedad Radio (línea 4) a las heredadas de su superclase (ColorFigura y
Posicion). Los métodos públicos EstableceRadio y DimeRadio permiten la
actualización y acceso a la propiedad privada Radio .

Se ha definido un constructor que permite instanciar un círculo con un radio


determinado, dejando la posición central y color a sus valores por defecto (línea 6).
El siguiente constructor (línea 10) permite establecer el radio y el color, mientras
que el último constructor (línea 15) nos ofrece la posibilidad de asignar valores a
todas las propiedades accesibles en la clase Circulo .

La última clase de este ejemplo especializa a Figura, definiendo objetos de


tipo Rectangulo. Esta clase es similar a Circulo , aunque aquí se establece la longitud
de los lados de un rectángulo en lugar del radio de un círculo. El código de la
subclase es:

1 import java.awt.Color;
2
3 public class Rectangulo extends Figura {
4 private double[] Lados = new double[2];
5
6 Rectangulo(double[] Lados) {
7 EstableceLados(Lados);
8 }
9
 BOBADILLA CAPÍTULO 5: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO HERENCIA 149

10 Rectangulo(double[]Lados, Color color) {


11 super(color);
12 EstableceLados(Lados);
13 }
14
15 Rectangulo(double[] Lados, Color color, int[] Posicion) {
16 super(color, Posicion);
17 EstableceLados(Lados);
18 }
19
20 public void EstableceLados(double[] Lados) {
21 this.Lados[0] = Lados[0];
22 this.Lados[1] = Lados[1];
23 }
24
25 public double[] DimeLados() {
26 return Lados;
27 }
28 }

La propiedad privada Lados (línea 4) es un vector de dos componentes: la


primera para definir un lado del rectángulo (por ejemplo el horizontal) y la segunda
para definir el otro lado del rectángulo. El método EstableceLados permite
modificar el contenido de la propiedad Lados y el método DimeLados sirve para
consultar su valor.

Los constructores de la clase derivada permiten asignar valores iniciales a


las propiedades de la superclase Figura y a la propiedad Lados de esta subclase.

Para mostrar el funcionamiento de las clases heredadas de Figura, se


muestra el siguiente código (PruebaFiguras):

1 import java.awt.Color;
2
3 public class PruebaFiguras {
4 public static void main(String[] args) {
5 int[] Posicion = {10,20};
6 double[] Lados = {50d,100d};
7 Circulo MiCirculo = new Circulo(3d,Color.red,Posicion);
8 Rectangulo MiRectangulo = new
Rectangulo(Lados,Color.blue,Posicion);
9
10 System.out.println(MiCirculo.DimeRadio());
11 int[] Centro = MiCirculo.DimeCentro();
12 System.out.println(Centro[0]);
13 System.out.println(Centro[1]);
14
150 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

15 System.out.println(MiRectangulo.DimeColor());
16
17 }
18 }

En la línea 7 de la clase PruebaFiguras se instancia un círculo de radio 3,


color rojo y posición central 10, 20. En la línea 8 se instancia un rectángulo con
lados de longitud 50,100, color azul y posición 10, 20. En la línea 10 se invoca al
método DimeRadio de la subclase Circulo, en la línea 11 al método DimeCentro de
la superclase Figura, y en la línea 15 al método DimeColor de la superclase Figura.

El resultado es:

5.2.2 Vehículos

En este ejemplo se pretende definir clases que implementen algunas


características de diferentes tipos de vehículos. Crearemos una clase Vehiculo que
contendrá propiedades básicas comunes a todos los vehículos y actuará de
superclase de las distintas clases derivadas (Camion, Motocicleta , etc.).

La superclase Vehiculo contendrá propiedades que establezcan el color del


vehículo, el número de ruedas que utiliza, la cilindrada que tiene y la potencia que
ofrece. Además contará con los métodos y constructores que se considere
necesarios.

La clase derivada Camion establecerá una nueva propiedad que indique el


número de ejes que posee el camión, junto a los constructores y métodos que se
consideren adecuados.

La clase derivada Motocicleta contendrá una propiedad que albergue el


número de ocupantes permitido que puede transportar cada motocicleta.
 BOBADILLA CAPÍTULO 5: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO HERENCIA 151

Vehiculo

Camion Motocicleta

Finalmente se implementará una clase PruebaVehiculos que creará diversas


instancias de camiones y motocicletas e invocará a diferentes métodos accesibles a
las subclases.

Código de la superclase Vehiculo:

1 import java.awt.Color;
2
3 public class Vehiculo {
4 private Color ColorVehiculo;
5 private byte NumRuedas;
6 private short Cilindrada;
7 private short Potencia;
8
9 Vehiculo(Color color) {
10 EstableceColor(color);
11 }
12
13 Vehiculo (byte NumRuedas) {
14 this.NumRuedas = NumRuedas;
15 }
16
17 Vehiculo (short Cilindrada) {
18 this.Cilindrada = Cilindrada;
19 }
20
21 Vehiculo(Color color, byte NumRuedas) {
22 this(color);
23 this.NumRuedas = NumRuedas;
24 }
25
26 Vehiculo(Color color, byte NumRuedas, short Cilindrada) {
27 this(color,NumRuedas);
28 this.Cilindrada = Cilindrada;
29 }
30
31 Vehiculo(Color color, byte NumRuedas, short Cilindrada,
32 short Potencia) {
33 this(color,NumRuedas,Cilindrada);
34 this.Potencia = Potencia;
152 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

35 }
36
37 public void EstableceColor(Color color) {
38 ColorVehiculo = color;
39 }
40
41 public Color DimeColor() {
42 return ColorVehiculo;
43 }
44
45 public byte DimeNumRuedas() {
46 return NumRuedas;
47 }
48
49 public short DimeCilindrada() {
50 return Cilindrada;
51 }
52
53 public short DimePotencia() {
54 return Potencia;
55 }
56
57 }

Código de la subclase Camion:

1 import java.awt.Color;
2
3 public class Camion extends Vehiculo {
4
5 private byte NumeroDeEjes;
6
7 Camion(byte NumeroDeRuedas) {
8 super(NumeroDeRuedas);
9 }
10
11 Camion(Color color, byte NumeroDeRuedas) {
12 super(color,NumeroDeRuedas);
13 }
14
15 Camion(Color color, byte NumeroDeRuedas,
16 short Cilindrada) {
17 super(color,NumeroDeRuedas,Cilindrada);
18 }
19
20 Camion(Color color, byte NumeroDeRuedas,
21 short Cilindrada, short Potencia) {
22 super(color,NumeroDeRuedas,Cilindrada,Potencia);
 BOBADILLA CAPÍTULO 5: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO HERENCIA 153

23 }
24
25 Camion(Color color, byte NumeroDeRuedas,
26 byte NumeroDeEjes, short Cilindrada,
27 short Potencia) {
28 super(color,NumeroDeRuedas,Cilindrada,Potencia);
29 EstableceNumeroDeEjes(NumeroDeEjes);
30 }
31
32 public byte DimeNumeroDeEjes() {
33 return NumeroDeEjes;
34 }
35
36 public void EstableceNumeroDeEjes(byte NumeroDeEjes) {
37 this.NumeroDeEjes = NumeroDeEjes;
38 }
39
40 }

Código de la subclase Motocicleta :

1 import java.awt.Color;
2
3 public class Motocicleta extends Vehiculo {
4
5 private byte NumeroDePlazas;
6
7 Motocicleta() {
8 super((byte)2);
9 }
10
11 Motocicleta(byte NumeroDePlazas) {
12 super((byte)2);
13 EstableceNumeroDePlazas(NumeroDePlazas);
14 }
15
16 Motocicleta(Color color) {
17 super(color,(byte)2);
18 }
19
20 Motocicleta(Color color, short Cilindrada) {
21 super(color,(byte)2,Cilindrada);
22 }
23
24 Motocicleta(Color color, short Cilindrada,
25 short Potencia) {
26 super(color,(byte)2,Cilindrada,Potencia);
27 }
154 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

28
29 public byte DimeNumeroDePlazas() {
30 return NumeroDePlazas;
31 }
32
33 public void EstableceNumeroDePlazas(byte NumeroDePlazas) {
34 this.NumeroDePlazas = NumeroDePlazas;
35 }
36
37 }

Código de la clase PruebaVehiculos:

1 import java.awt.Color;
2
3 public class PruebaVehiculos {
4 public static void main(String[] args) {
5 Motocicleta MotoBarata = new
6 Motocicleta(Color.red,(short)125,(short)25);
7 Motocicleta MotoBarata2 = new
8 Motocicleta(Color.red,(short)125,(short)25);
9 Motocicleta MotoCara = new
10 Motocicleta(Color.yellow,(short)1000,(short)90);
11
12 Camion CamionNormal = new Camion(Color.red,(byte)4,
13 (byte)2,(short)4000,(short)300);
14 Camion CamionEnorme = new Camion(Color.red,(byte)24,
15 (byte)6,(short)15000,(short)800);
16
17 MotoBarata.EstableceNumeroDePlazas((byte)1);
18 System.out.println(MotoBarata.DimeNumeroDePlazas());
19 System.out.println(MotoBarata2.DimeCilindrada());
20 System.out.println(CamionEnorme.DimeNumeroDeEjes());
21
22 }
23 }

Resultado:
 BOBADILLA CAPÍTULO 5: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO HERENCIA 155

5.3 POLIMORFISMO

5.3.1 Conceptos

El polimorfismo, desde un punto de vista de orientación a objetos, permite


que clases de diferentes tipos puedan ser referenciadas por una misma variable. En
el siguiente ejemplo, una instancia de la clase Figura (MiFigura) podrá referenciar a
propiedades y métodos implementados en las clases Circulo y Rectángulo.

Pongamos como ejemplo que las clases Circulo y Rectangulo implementan


un método llamado Perimetro que calcula el perímetro de la figura geométrica:

La referencia MiFigura (de tipo Figura) podrá invocar al método Perimetro


de la clase Circulo o al método Perimetro de la clase Rectangulo , dependiendo de, si
156 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

en tiempo de ejecución, está referenciando a una instancia de la clase Circulo o está


referenciando a una instancia de la clase Rectangulo.

Es importante resaltar que la determinación del método Perimetro que se va


a invocar (el de la clase Circulo o el de la clase Rectangulo ) no se realiza en la
compilación, sino en tiempo de ejecución, lo que hace del polimorfismo un
mecanismo de programación muy potente.

Para entender mejor la utilidad del polimorfismo, pongamos un ejemplo en


el que su utilización es adecuada: supongamos que estamos realizando un editor
gráfico en el que el usuario puede dibujar figuras geométricas bidimensionales tales
como círculos, rectángulos, pentágonos, triángulos, etc. Además, dentro de las
opciones de la herramienta, existe un botón que al ser pulsado nos muestra en una
ventana el valor del perímetro de la figura seleccionada.

Nosotros no conocemos a priori las figuras que el usuario dibujará, eso es


algo que ocurre en tiempo de ejecución, al igual que no podemos saber por
adelantado de qué figura o figuras solicitará conocer el perímetro. En un momento
dado, mientras un usuario utiliza el editor gráfico, existirá una estructura de datos
que contendrá las figuras dibujadas:

El vector MiFigura contendrá referencias a objetos de tipo Figura (Figura[]


MiFigura = new Figura[100]). Según el usuario, en tiempo de ejecución, seleccione
una figura geométrica u otra se invocará a un método Perimetro u otro. Esto es
posible gracias al polimorfismo.

El polimorfismo, en Java, debe implementarse respetando a las siguientes


reglas:
 BOBADILLA CAPÍTULO 5: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO HERENCIA 157

• La invocación a un método de un subclase debe realizarse a través de


una referencia a la superclase; es decir, es adecuado poner p =
LaFigura.Perimetro(), pero no p = ElCirculo.Perimetro().
• El método llamado debe ser también un miembro de la superclase; en
nuestro ejemplo, el método Perimetro debe existir también en la clase
Figura.
• La firma del método debe ser igual en la superclase que en las clases
derivadas; es decir, el nombre del método y el número y tipos de sus
parámetros debe ser igual en las clases Figura, Circulo , Rectangulo ,
etc.
• El tipo de retorno del método (que se utilizará de forma polimórfica)
debe ser igual en la superclase que en las subclases.
• El atributo de acceso del método no debe ser más restrictivo en las
clases derivadas que en la superclase.

5.3.2 Ejemplo

Para acabar de ilustrar la forma de utilización del polimorfismo, vamos a


ampliar las clases Figura, Circulo y Rectangulo implementadas en la lección
anterior, de manera que contengan un método Perimetro que calcule el perímetro de
cada tipo de figura (círculos y rectángulos):

La clase Figura debe contener un método Perimetro para satisfacer las


reglas de utilización del polimorfismo en Java. Nótese que nunca se va a ejecutar el
código de este método, se ejecutará el código de los métodos Perimetro de las
subclases. Cuando expliquemos clases abstractas mejoraremos este aspecto.

1 import java.awt.Color;
2
3 public class PolimorfismoFigura {
4
5 // Los mismos miembros que en la clase Figura,
6 // modificando el nombre de los constructores
7
8 public double Perimetro() {
9 return 0d;
10 }
11
12 }
158 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

El método Perimetro de la clase PolimorfismoCirculo calcula el perímetro


(2πr) del círculo:

1 import java.awt.Color;
2
3 public class PolimorfismoCirculo extends PolimorfismoFigura
{
4
5 // Los mismos miembros que en la clase Circulo,
6 // modificando el nombre de los constructores
7
8 public double Perimetro() {
9 return 2.0d*Math.PI*Radio;
10 }
11
12 }

El método Perimetro de la clase PolimorfismoRectangulo calcula el


perímetro (2*lado1+2*lado2) del rectángulo:

1 import java.awt.Color;
2
3 public class PolimorfismoRectangulo extends
PolimorfismoFigura {
4
5 // Los mismos miembros que en la clase Rectangulo,
6 // modificando el nombre de los constructores
7
8 public double Perimetro() {
9 return 2d*Lados[0] + 2d*Lados[1];
10 }
11
12 }

En la clase PolimorfismoPruebaFiguras se utiliza el recurso del


polimorfismo. En la línea 7 se declara una propiedad MiCirculo de tipo
PolimorfismoFigura, pero instanciando la clase PolimorfismoCirculo . En la línea 9
se hace lo mismo con la referencia MiRectangulo , que siendo una instancia de la
clase PolimorfismoRectangulo, es de tipo PolimorfismoFigura.

En la línea 12, cuando se invoca al método Perimetro con la referencia


MiCirculo , en tiempo de ejecución se determina que la propiedad MiCirculo (de tipo
PolimorfismoFigura) apunta a una instancia de la clase PolimorfismoCirculo , por lo
que se invoca al método Perimetro de dicha clase (y no de PolimorfismoRectangulo
o PolimorfismoFigura).
 BOBADILLA CAPÍTULO 5: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO HERENCIA 159

En la línea 13 ocurre lo mismo que en la 12, salvo que ahora se invoca al


método Perimetro de la clase PolimorfismoRectangulo .

Ya fuera de lo que es el concepto de polimorfismo, cuando intentamos hacer


una llamada a un método de una clase derivada utilizando una referencia de la
superclase, nos da error, puesto que desde una superclase no se tiene visibilidad de
los miembros de las subclases (aunque al contrario no haya problemas). Es decir no
podemos poner MiCirculo.DimeRadio(), porque el método DimeRadio pertenece a
la clase PolimorfismoCirculo y MiCirculo es una propiedad del tipo
PolimorfismoFigura.

Si queremos conocer el radio de MiCirculo , tenemos dos posibilidades:


• Crear una instancia de la clase PolimorfismoCirculo y asignarle la
referencia de MiCirculo (utilizando el mecanismo de casting). Esta
solución se implementa en la línea 15 del código mostrado.
• Utilizar el mecanismo de casting sin crear explícitamente una
instancia de la clase PolimorfismoCirculo . Esta solución se
implementa en la línea 17.

1 import java.awt.Color;
2
3 public class PolimorfismoPruebaFiguras {
4 public static void main(String[] args) {
5 int[] Posicion = {10,20};
6 double[] Lados = {50d,100d};
7 PolimorfismoFigura MiCirculo = new
8 PolimorfismoCirculo(3d,Color.red,Posicion);
9 PolimorfismoFigura MiRectangulo = new
10 PolimorfismoRectangulo(Lados,Color.blue,Posicion);
11
12 System.out.println(MiCirculo.Perimetro());
13 System.out.println(MiRectangulo.Perimetro());
14
15 PolimorfismoCirculo InstanciaCirculo =
(PolimorfismoCirculo) MiCirculo;
16 System.out.println(InstanciaCirculo.DimeRadio());
17 System.out.println(((PolimorfismoCirculo)MiCirculo).
18 DimeRadio());
19 }
20 }
160 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

5.4 CLASES ABSTRACTAS E INTERFACES

5.4.1 Métodos abstractos

Un método abstracto es un método que se declara, pero no se define. La


sintaxis es la misma que la de la declaración de un método no abstracto, pero
terminando con punto y coma y sin poner las llaves de comienzo y final del método;
además se utiliza la palabra reservada abstract.

Por ejemplo, uno de los métodos Perimetro, vistos anteriormente se


declaraba y definía como:

public double Perimetro() {


return 2.0d*Math.PI*Radio;
}

Este mismo método, declarado como abstracto adopta la forma:

public abstract double Perimetro();

Cuando hacemos uso del polimorfismo nos vemos obligados a definir un


método en la superclase, sabiendo que nunca será llamado. En la lección anterior, en
la clase PolimorfismoFigura escribíamos el método:

public double Perimetro() {


return 0d;
}

Resultaría mucho más elegante utilizar la versión abstracta del método en


casos como este.

Una puntualización interesante respecto a los métodos abstractos es que no


deben ser definidos como final, puesto que los métodos final no pueden ser
redefinidos en las clases derivadas, por lo tanto, si escribimos un método abstracto
como final, las subclases siempre tendrán ese método como abstracto y nunca
podrán ser instanciadas.
 BOBADILLA CAPÍTULO 5: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO HERENCIA 161

5.4.2 Clases abstractas

Cuando una clase contiene al menos un método abstracto, la clase es


abstracta y debe declararse como tal:

public abstract class ClaseAbstracta {


………………..
}

Se pueden declarar variables de clases abstractas, pero no instanciarlas:

ClaseAbstracta VariableClase;
VariableClase = new ClaseAbstracta();

Pueden utilizarse clases abstractas como superclases:

public abstract class ClaseAbstractaDerivada extends ClaseAbstracta {


// podemos definir parte de los metodos abstractos
}

public class ClaseNoAbstracta extends ClaseAbstractaDerivada {


// definimos todos los metodos abstractos que tenga la superclase
}

ClaseNoAbstracta MiInstancia= new ClaseNoAbstracta();

Las clases abstractas proporcionan un mecanismo muy potente para facilitar


el diseño y programación orientado a objetos; podemos diseñar aplicaciones que
contengan una serie de clases abstractas y codificar las mismas sin entrar en la
definición de los detalles del código de los métodos. La aplicación queda de esta
manera bien estructurada, definida y consistente (podemos compilarla), a partir de
este punto de partida resulta mucho más sencilla la fase de implementación, que
puede ser llevada a cabo en paralelo por diversos programadores, conociendo cada
uno los objetos que tiene que codificar y las clases relacionadas que puede emplear.

Veamos el ejemplo de los vehículos (motocicletas y camiones) empleando


una clase abstracta. Crearemos la clase abstracta AbstractoVehiculo y dos clases
derivadas: AMotocicleta y ACamion. La clase AbstractoVehiculo contendrá las
propiedades necesarias para albergar el color, número de ruedas, cilindrada y
potencia. Podremos inicializar estas propiedades a través de diferentes constructores
162 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

y consultarlas a través de métodos. También existirá un método abstracto Impuesto


que será definido en cada subclase derivada.

El código de ejemplo realizado para la clase Vehiculo es:

1 import java.awt.Color;
2
3 abstract public class AbstractoVehiculo {
4
5 // Los mismos constructores y metodos que en la clase
// Vehiculo
6 // Tambien cambiamos el nombre de los constructores
7
8 abstract public float Impuesto();
9
10 }

El método Impuesto se deja abstracto en la superclase y se define en cada


una de las subclases según sea necesario. El impuesto que pagan los camiones es
diferente al que pagan las motocicletas, al igual que el perímetro de un círculo se
calcula de manera diferente al de un rectángulo.

La clase derivada ACamion implementa el método Impuesto ; esta clase no es


abstracta, puesto que no contiene ningún método abstracto. Supondremos que el
impuesto que pagan los camiones se calcula realizando el sumatorio de los términos:
cilindrada/30, potencia*20, Número de ruedas *20 y número de ejes * 50. El código
de la clase nos queda de la siguiente manera:

1 import java.awt.Color;
2
3 public class ACamion extends AbstractoVehiculo {
4
5 // Los mismos constructores y metodos que en la clase
// Camion
6 // Tambien cambiamos el nombre de los constructores
7
8 public float Impuesto(){
9 return (super.DimeCilindrada()/30 +
super.DimePotencia()*20 +
10 super.DimeNumRuedas()*20 +
11 DimeNumeroDeEjes()*50);
12 }
13
14 }
 BOBADILLA CAPÍTULO 5: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO HERENCIA 163

Obsérvese el uso de la palabra reservada super para identificar la superclase


de la clase derivada donde se emplea; de esta manera, this indica la propia clase y
super indica la superclase.

La clase derivada AMotocicleta es similar a ACamion. Supondremos que el


impuesto se calcula aplicando la fórmula: cilindrada/30 + potencia*30. El código
nos queda de la siguiente manera:

1 import java.awt.Color;
2
3 public class AMotocicleta extends AbstractoVehiculo {
4
5 // Los mismos constructores y metodos que en la clase
// Motocicleta
6 // Tambien cambiamos el nombre de los constructores
7
8 public float Impuesto(){
9 return (super.DimeCilindrada()/30 +
super.DimePotencia()*30);
10 }
11
12 }

Para probar las clases anteriores empleamos el fichero


APruebaVehiculos.java que contiene declaraciones e instanciaciones de diversos
objetos camión y motocicleta. Obsérvese como en la línea 5 se declara una variable
de tipo AbstractoVehiculo (clase abstracta) y se instancia como tipo Amotocicleta
(clase no abstracta), con el fin de poder utilizar posteriormente el método Impuesto
de manera polimórfica. En las líneas 7, 9, 11 y 14 se realizan acciones similares a la
descrita.

1 import java.awt.Color;
2
3 public class APruebaVehiculos {
4 public static void main(String[] args) {
5 AbstractoVehiculo MotoBarata =
6 new AMotocicleta(Color.red,(short)125,(short)25);
7 AMotocicleta MotoBarata2 =
8 new AMotocicleta(Color.red,(short)125,(short)25);
9 AbstractoVehiculo MotoCara = new
AMotocicleta(Color.yellow,(short)1000,(short)90);
10
11 AbstractoVehiculo CamionNormal = new
12 ACamion(Color.red,(byte)4,(byte)2,
13 (short)4000,(short)300);
14 AbstractoVehiculo CamionEnorme = new
164 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

15 ACamion(Color.red,(byte)24,(byte)6,
16 (short)15000,(short)800);
17
18 AMotocicleta InstanciaMotoBarata =
19 (AMotocicleta) MotoBarata;
20 InstanciaMotoBarata.EstableceNumeroDePlazas((byte)1);
21 System.out.println(InstanciaMotoBarata.
22 DimeNumeroDePlazas());
23 MotoBarata2.EstableceNumeroDePlazas((byte)1);
24 System.out.println(MotoBarata2.DimeNumeroDePlazas());
25 System.out.println(((ACamion)CamionEnorme).
26 DimeNumeroDeEjes());
27
28 System.out.println(MotoBarata.Impuesto());
29 System.out.println(MotoCara.Impuesto());
30 System.out.println(CamionNormal.Impuesto());
31 System.out.println(CamionEnorme.Impuesto());
32 System.out.println(InstanciaMotoBarata.Impuesto());
33 }
34 }

5.4.3 Interfaces

Los interfaces son colecciones de constantes y métodos abstractos. Los


métodos son siempre públicos y abstractos (no es necesario definirlos como tal) y las
constantes son siempre públicas, estáticas y, por supuesto, final (tampoco es
necesario especificar sus atributos de acceso y modificadores).

Los interfaces proporcionan un mecanismo de herencia múltiple que no


puede ser utilizado empleando únicamente clases. Es necesario saber que este
mecanismo proporcionado por Java es muy limitado respecto a las posibilidades que
ofrece la herencia múltiple de la programación orientada o objetos; sin embargo,
permite afrontar de manera elegante diferentes situaciones de diseño y
programación.

La sintaxis de definición de un interfaz es:

public interface Nombre {


// constantes y metodos abstractos
}
 BOBADILLA CAPÍTULO 5: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO HERENCIA 165

Ejemplo de interfaz que contiene solo constantes:

1 public interface DatosCentroDeEstudios {


2 byte NumeroDePisos = 5;
3 byte NumeroDeAulas = 25;
4 byte NumeroDeDespachos = 10;
5 // resto de datos constantes
6 }

Ejemplo de interfaz que contiene solo métodos:

1 public interface CalculosCentroDeEstudios {


2 short NumeroDeAprobados(float[] Notas);
3 short NumeroDeSuspensos(float[] Notas);
4 float NotaMedia(float[] Notas);
5 float Varianza(float[] Notas);
6 // resto de metodos
7 }

Ejemplo de interfaz con constantes y métodos:

public interface CentroDeEstudios {


byte NumeroDePisos = 5;
byte NumeroDeAulas = 25;
byte NumeroDeDespachos = 10;
// resto de datos constantes

short NumeroDeAprobados(float[] Notas);


short NumeroDeSuspensos(float[] Notas);
float NotaMedia(float[] Notas);
float Varianza(float[] Notas);
// resto de metodos
}

También podemos definir un interfaz basado en otro. Utilizamos la palabra


reservada extends, tal y como hacemos para derivar subclases:

public interface CentroDeEstudios extends DatosCentroDeEstudios {


short NumeroDeAprobados(float[] Notas);
short NumeroDeSuspensos(float[] Notas);
float NotaMedia(float[] Notas);
float Varianza(float[] Notas);
// resto de metodos
166 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

Al igual que hablamos de superclases y subclases, podemos emplear la


terminología superinterfaces y subinterfaces. A diferencia de las clases, un interfaz
puede extender simultáneamente a varios superinterfaces, lo que supone una
aproximación a la posibilidad de realizar herencia múltiple:

1 public interface CentroDeEstudios extends


2 DatosCentroDeEstudios, CalculosCentroDeEstudios {
3 // otros posibles metodos y constantes
4 }

Para poder utilizar los miembros de un interfaz es necesario implementarlo


en una clase; se emplea la palabra reservada implements para indicarlo:

1 public class CCentroDeEstudios implements


2 CentroDeEstudios {
3
4 public short NumeroDeAprobados(float[] Notas) {
5 short NumAprobados = 0;
6 for (int i=0; i<Notas.length; i++)
7 if (Notas[i]>=5.0f)
8 NumAprobados++;
9 return NumAprobados;
10 }
11
12 public short NumeroDeSuspensos(float[] Notas) {
13 short NumSuspensos = 0;
14 for (int i=0; i<Notas.length; i++)
15 if (Notas[i]<5.0f)
16 NumSuspensos++;
17 return NumSuspensos;
18 }
19
20 public float NotaMedia(float[] Notas) {
21 float Suma = 0;
22 for (int i=0; i<Notas.length; i++)
23 Suma = Suma + Notas[i];
24 return Suma/(float)Notas.length;
25 }
26
27 public float Varianza(float[] Notas) {
28 float Media = NotaMedia(Notas);
29 float Suma = 0;
30 for (int i=0; i<Notas.length; i++)
 BOBADILLA CAPÍTULO 5: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO HERENCIA 167

31 Suma = Suma + Math.abs(Media-Notas[i]);


32 return Suma/(float)Notas.length;
33 }
34
35 }

5.4.4 Ejercicio

Vamos a definir dos interfaces (ItfImpresion e ItfImpresion2)


extremadamente sencillos. Cada uno de ellos consta de un solo método, diseñado
para imprimir un texto: (Imprimir e ImprimirBonito). Después crearemos una serie
de clases para practicar con el mecanismo de implementación de interfaces. El
diagrama de clases e interfaces presenta el siguiente aspecto:

En el diagrama superior, los dos bloques con entramado de ondas


representan a los interfaces, mientras que los tres bloques sin entramado representan
a las distintas clases que implementan a los interfaces. Obsérvese como la clase
ItfNeutro utiliza el mecanismo de herencia múltiple (implementando
simultáneamente dos interfaces).
168 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

El interfaz ItfImpresion se utiliza como superinterfaz de diversas clases, al


igual que una clase puede ser superclase de varias clases derivadas.

El código de los interfaces es:

1 public interface ItfImpresion {


2 void Imprimir();
3 }

1 public interface ItfImpresion2 {


2 void ImprimirBonito();
3 }

El código de las clases ItfPositivoImpresion e ItfNegativoImpresion se ha


mantenido lo más sencillo posible:

1 public class ItfPositivoImpresion implements ItfImpresion {


2 public void Imprimir() {
3 System.out.println ("!Que buen tiempo hace!");
4 }
5 }

1 public class ItfNegativoImpresion implements ItfImpresion {


2 public void Imprimir() {
3 System.out.println ("!Odio los lunes!");
4 }
5 }

Para probar el funcionamiento de las clases, se codifica ItfPrueba:

1 public class ItfPrueba {


2 public static void main(String[] args){
3 ItfImpresion Positivo = new ItfPositivoImpresion();
4 ItfImpresion Negativo = new ItfNegativoImpresion();
5
6 Positivo.Imprimir();
7 Negativo.Imprimir();
8 }
9 }

En la línea 3 se declara una variable Positivo de tipo ItfImpresion (interfaz)


y se instancia como ItfPositivoImpresion (clase); en la línea 4 se hace los mismo con
la variable Negativo. Estamos utilizando el mismo mecanismo que cuando
 BOBADILLA CAPÍTULO 5: PROGRAMACIÓN ORIENTADA A OBJETOS USANDO HERENCIA 169

declaramos una variable de tipo superclase abstracta y la instanciamos como clase


derivada.

En las líneas 6 y 7 se explota el polimorfismo que proporciona Java,


haciendo llamadas al mismo método Imprimir definido de manera diferente en cada
una de las clases que implementan el interfaz ItfImpresion. En el resultado, como
cabe esperar, primero aparece !Que buen tiempo hace!, y posteriormente !Odio los
lunes!

La clase ItfNeutro implementa los dos interfaces, consiguiendo herencia


múltiple (de métodos abstractos):

1 public class ItfNeutro implements ItfImpresion,


ItfImpresion2 {
2
3 public void Imprimir(){
4 System.out.print ("Las olas del mar");
5 }
6
7 public void ImprimirBonito(){
8 System.out.print("---- ");
9 Imprimir();
10 System.out.println(" ----");
11 }
12
13 }

La clase ItfPrueba2 crea una instancia de la clase ItfNeutro (línea 3) y otra


de la clase ItfNegativoImpresion (línea 4). Posteriormente, en las líneas 6, 7 y 8, se
hace uso del polimorfismo (llamando al método Imprimir definido en clases
diferentes) y de la posibilidad de acceso a los métodos Imprimir e ImprimirBonito
diseñados en los interfaces ItfImpresion e ItfImpresion2.

1 public class ItfPrueba2 {


2 public static void main(String[] args){
3 ItfNeutro Instancia = new ItfNeutro();
4 ItfImpresion Negativo = new ItfNegativoImpresion();
5
6 Instancia.Imprimir();
7 Instancia.ImprimirBonito();
8 Negativo.Imprimir();
9 }
10 }
CAPÍTULO 6

EXCEPCIONES

6.1 EXCEPCIONES PREDEFINIDAS

6.1.1 Introducción

Las excepciones señalan errores o situaciones poco habituales en la


ejecución de un programa, por ejemplo una división de un valor entre cero, un
intento de acceso a un String declarado, pero no instanciado, etc.

Habitualmente, en programación, se incluyen tantas instrucciones


condicionales como sea necesario para conseguir que una aplicación sea robusta, de
esta manera, por ejemplo, en cada división de un valor entre una variable, antes se
comprueba que el denominador no sea cero:
...............
Denominador = ..............
if (Denominador != 0) {
Numerador = ................
Resultado = Numerador / Denominador;
}
else
System.out.println (“No se puede realizar la división”);
.................

Utilizando el mecanismo de excepciones que proporciona Java, en nuestro


ejemplo, en lugar de incluir una serie de instrucciones condicionales para evitar las
distintas divisiones entre cero que se puedan dar, se escribe el programa sin tener en
cuenta esta circunstancia y, posteriormente, se escribe el código que habría que
ejecutar si la situación “excepcional” se produce:
172 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

....................
Numerador = .............
Denominador = ............
Resultado = Numerador / Denominador;
Si existe excepción
....................

System.out.println (“No se puede realizar la división”);

Al hacer uso de excepciones, el bloque que codifica la porción de aplicación


resulta más sencillo de entender, puesto que no es necesario incluir las instrucciones
condicionales que verifican si puede darse la situación de excepción. En definitiva,
si utilizamos el mecanismo de excepciones, podemos separar la lógica del programa
de las instrucciones de control de errores, haciendo la aplicación más legible y
robusta.

Las excepciones son objetos (clases) que se crean cuando se produce una
situación extraordinaria en la ejecución del programa. Estos objetos almacenan
información acerca del tipo de situación anormal que se ha producido y el lugar
donde ha ocurrido. Los objetos excepción se pasan automáticamente al bloque de
tratamiento de excepciones (el inferior de nuestro gráfico) para que puedan ser
referenciados.

La superclase de todas las excepciones es la clase Throwable. Sólo las


instancias de esta clase o alguna de sus subclases pueden ser utilizadas como
excepciones. La clase Trowable tiene dos clases derivadas: Error y Exception.

La clase Exception sirve como superclase para crear excepciones de


propósito específico (adaptadas a nuestras necesidades), por ejemplo, si estamos
diseñando una clase que lee secuencialmente bytes en una cinta digital de datos,
podemos crear la excepción: FinDeCinta que se produce cuando el dispositivo físico
ha alcanzado el final de la cinta. Otro ejemplo podría ser la implementación de una
clase de envío de datos a un satélite no geoestacionario, donde convendría incluir
una excepción FueraDeCobertura que se produzca cuando el satélite se encuentre
fuera del alcance de nuestra antena parabólica. Un último ejemplo: si escribimos un
driver de impresora podemos crear una clase derivada de Exception para que nos
avise de la situación excepcional FinDePapel.

La clase Error sirve de superclase para una serie de clases derivadas ya


definidas que nos informan de situaciones anormales relacionadas con errores de
muy difícil recuperación producidos en el sistema.
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 6: EXCEPCIONES 173

No es obligatorio tratar las excepciones derivadas de la clase Error, ya que


en la ejecución de una aplicación probablemente nunca se produzcan estas
situaciones anormales, sin embargo, sí que es obligatorio hacer un tratamiento
explícito de las excepciones derivadas de la clase Exception (no podemos ignorar la
falta de cobertura de un satélite, el fin del papel en una impresora, el fin de datos en
una lectura secuencial, etc.). En una aplicación bien diseñada e implementada es
muchísimo más probable que se produzcan excepciones de tipo Exception que
excepciones de tipo Error.

La clase Exception tiene un amplio número de clases derivadas


proporcionadas por el SDK, por ejemplo existen excepciones predefinidas para el
uso de ficheros, de SQL, etc. De todas estas subclases, RuntimeExeption tiene una
característica propia: no es necesario realizar un tratamiento explícito de estas
excepciones (de todas las demás clases derivadas de Exception si es necesario). Esto
es debido a que, al igual que con las excepciones derivadas de Error, existen pocas
posibilidades de recuperar situaciones anómalas de este tipo.

Entre las clases derivadas de RuntimeException se encuentran:

Clase Situación de excepción


ArithmeticException Cuando ocurre una operación aritmética errónea, por
ejemplo división entre cero; con los valores reales no se
produce esta excepción.
ArrayStoreException Intento de almacenar un valor de tipo erróneo en una
matriz de objetos
IllegalArgumentException Se le ha pasado un argumento ilegal o inapropiado a un
método
IndexOutOfBoundsException Cuando algún índice (por ejemplo de array o String)
está fuera de rango
NegativeArraySizeException Cuando se intenta crear un array con un índice negativo
NullPointerException Cuando se utiliza como apuntador una variable con
valor null

El siguiente gráfico muestra las clases más importantes en el uso de


excepciones y su jerarquía . En las clases no sombreadas es obligatorio realizar un
tratamiento explícito de las excepciones, en las clases sombreadas no es necesario
este tratamiento
174 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

Throwable

Exception Error

AWTError
LinkageError
IOException
ThreadDeat
SQLException
VirtualMachineError
AWTExceptio
RuntimeException

6.1.2 Sintaxis y funcionamiento con una sola excepción

En el apartado anterior adelantábamos el diseño básico del tratamiento de


excepciones: separar el código de los programas del código de control de situaciones
excepcionales en los programas, y los ilustrábamos gráficamente con el siguiente
ejemplo:

....................
Numerador = .............
Denominador = ............
Resultado = Numerador / Denominador;
Si existe excepción
....................

System.out.println (“No se puede realizar la división”);

El bloque superior representa el código del programa y se le denomina


bloque de “intento” (try). Aquí se introduce el código del programa susceptible de
causar cierto tipo de excepciones. Las instrucciones se incluyen en un bloque con la
siguiente sintaxis:
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 6: EXCEPCIONES 175

try {
// instrucciones susceptibles de causar cierto tipo de excepciones
}

El bloque inferior contiene las instrucciones de control de situaciones


excepcionales. La sintaxis es:

catch (TipoDeExcepcion Identificador) {


// instrucciones de control de situaciones excepcionales
}

Entre el bloque try y el bloque catch no puede haber ninguna


instrucción. Ambos bloques se encuentran ligados en ejecución.

El funcionamiento es el siguiente: se ejecutan las instrucciones del bloque


try hasta que se produzca la situación de excepción de tipo TipoDeExcepcion; nótese
que, habitualmente, no se producirá la excepción y se ejecutarán todas las
instrucciones del bloque try. Si se da la excepción, se pasa a ejecutar las
instrucciones del bloque catch y, posteriormente, las instrucciones siguientes al
bloque catch. Si no se produce la excepción no se ejecutan las instrucciones del
bloque catch. Gráficamente:
176 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

6.1.3 Ejemplo con una sola excepción

En este ejemplo capturaremos la excepción ArithmeticException, que es una


subclase de RuntimeException; aunque no es obligatorio tratar las excepciones que
derivan de RuntimeException, en ciertos casos resulta conveniente.

La clase Excepcion1 implementa un bucle sin fin (líneas 3 y 18) que recoge
un valor entero suministrado por el usuario (línea 7) y lo utiliza como denominador
en una división (línea 8). Todo ello dentro de un bloque try (líneas 5 y 10).

El bloque catch atiende únicamente a excepciones de tipo


ArithmeticException (línea 12), imprimiendo el mensaje “Division por cero” en la
consola (línea 13). La instrucción situada en la línea 16 nos muestra como se ejecuta
el programa tras los bloques ligados try-catch.

1 public class Excepcion1 {


2 public static void main(String[] args) {
3 do {
4
5 try {
6 System.out.print("Valor: ");
7 int Valor = Teclado.Lee_int();
8 float Auxiliar = 8/Valor;
9 System.out.println(Auxiliar);
10 }
11
12 catch (ArithmeticException e) {
13 System.out.println("Division por cero");
14 }
15
16 System.out.println("Despues del catch");
17
18 } while (true);
19 }
20 }

Al ejecutar el programa e introducir diversos valores, obtenemos la siguiente


salida:
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 6: EXCEPCIONES 177

Si introducimos un valor distinto de cero se ejecutan todas las instrucciones


del bloque try, ninguna del catch (no se produce la excepción) y la instrucción
siguiente al catch. Si introducimos el valor cero se ejecutan las instrucciones del try
hasta que se produce la excepción (línea 8), por lo que la línea 9 no se ejecuta;
posteriormente se ejecutan las instrucciones del catch y finalmente las posteriores al
bloque catch.

6.1.4 Sintaxis y funcionamiento con más de una excepción

Una porción de código contenida en un bloque try, potencialmente puede


provocar más de una excepción, por lo que Java permite asociar varios bloques
catch a un mismo bloque try. La sintaxis es la siguiente:

try {
// instrucciones susceptibles de causar cierto tipo de excepciones
}
catch (TipoDeExcepcion1 Identificador) {
// instrucciones que se deben ejecutar si ocurre la excepcion de tipo
TipoDeExcepcion1
}
catch (TipoDeExcepcion2 Identificador) {
// instrucciones que se deben ejecutar si ocurre la excepcion de tipo
TipoDeExcepcion2
}
................
catch (TipoDeExcepcionN Identificador) {
// instrucciones que se deben ejecutar si ocurre la excepcion de tipo
TipoDeExcepcionN
}

No se pueden poner instrucciones entre los bloques catch.

Cuando “se levanta” (ocurre) una excepción en las instrucciones del bloque
try, se recorren en orden los bloques catch hasta que se encuentra uno con el mismo
tipo o un tipo que es superclase de la excepción que ha llegado. Se ejecutan
únicamente las instrucciones del bloque catch que cumple los requisitos (si es que
alguno los cumple).

De esta manera sería correcto imple mentar la secuencia:


178 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

try {
// instrucciones susceptibles de causar cierto tipo de excepciones
}
catch (NullPointerException e) {
// instrucciones que se deben ejecutar si ocurre la excepcion de tipo
NullPointerException
}
catch (RuntimeException e) {
// instrucciones que se deben ejecutar si ocurre la excepcion de tipo
RunTimeException
}
catch (Exception e) {
// instrucciones que se deben ejecutar si ocurre la excepcion de tipo Exception
}

Si ocurre una excepción del tipo NullPointerException se ejecutan las


instrucciones asociadas al primer bloque catch. Si ocurre una excepción del tipo
RunTimeException o de una clase derivada del mismo (distinta a
NullPointerException) se ejecutan las instrucciones asociadas al segundo bloque
catch. Si ocurre una excepción del tipo Exception o de una clase derivada del mismo
(distinta a RuntimeException) se ejecutan las instrucciones asociadas al tercer bloque
catch

Si cambiásemos el orden (en cualquiera de sus posibles combinaciones) de


los bloques catch del ejemplo nos encontraríamos con un error de compilación,
puesto que al menos un bloque catch no podría ejecutarse nunca.

Resulta habitual utilizar varios bloques catch correspondientes a


excepciones con un mismo nivel de herencia, por ejemplo con subclases de
RuntimeException:

1 public class Excepcion2 {


2 public static void main(String[] args) {
3 int Posicion=0;
4 do{
5 try {
6 float[] Valores = new float[5];
7 System.out.print("Posicion: ");
8 Posicion = Teclado.Lee_int();
9 System.out.print("Valor: ");
10 int Valor = Teclado.Lee_int();
11
12 float Auxiliar = 8/Valor;
13 Valores[Posicion] = Auxiliar;
14 }
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 6: EXCEPCIONES 179

15
16 catch (ArithmeticException e) {
17 System.out.println("Division por cero");
18 }
19
20 catch(IndexOutOfBoundsException e) {
21 System.out.println("Indice fuera del array");
22 }
23
24 System.out.println("Hola");
25
26 } while (Posicion!=10);
27 }
28 }

El código de la clase Excepcion2 contiene un bloque try (líneas 5 a 14) y dos


bloques catch (líneas 16 a 18 y 20 a 22). En el bloque try se define un vector de 5
posiciones float (línea 6) y se piden dos valores enteros; el primero (línea 8) servirá
de índice para almacenar un dato en la posición indicada del vector Valores (línea
13), el segundo valor (obtenido en la línea 10) actuará de denominador en una
división (línea 12).

Como se puede comprobar, las instrucciones del bloque try pueden generar,
al menos, dos excepciones: ArithmeticException e IndexOutOfBoundsException. Si
introducimos un índice (Posición) mayor que 4 (el vector abarca de 0 a 4) nos
encontramos con una excepción del segundo tipo, mientras que si introducimos
como denominador (valor) un cero, nos encontramos con una excepción aritmética.

Como se puede observar analizando la ejecución mostrada, una vez que se


produce una excepción se termina la ejecución en el bloque try y se pasa al bloque
catch correspondiente (en caso de que exista).
180 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

6.1.5 El bloque finally

Como hemos visto, una vez que llega una excepción dentro de un bloque try,
las instrucciones siguientes (en el bloque) a la que causa la excepción no se ejecutan.
Esto puede dar lugar a que dejemos el sistema en un estado no deseado (ficheros
abiertos, comunicaciones sin terminar, recursos bloqueados, etc.). Para evitar este
tipo de situaciones, Java proporciona la posibilidad de incluir un bloque (finally )
cuyas instrucciones siempre se ejecutan después de las del catch seleccionado.

La sintaxis es:

try {
.............
}

catch (TipoExcepcion1 Identificador) {


.............
}
.............
catch (TipoExcepcionN Identificador) {
..............
}

finally {
// instrucciones que siempre se ejecutan
}

Las instrucciones del bloque finally siempre se ejecutan,


independientemente de que se trate o no una excepción. Si en el bloque try existe
una sentencia return, las instrucciones del bloque finally también se ejecutan antes
que el return.

6.1.6 Propagación de excepciones

Las instrucciones que tenemos dentro de un bloque try a menudo contienen


llamadas a métodos que a su vez pueden realizar llamadas a otros métodos y así
sucesivamente. Cualquiera de los métodos llamados puede provocar una excepción y
cualquiera de los métodos puede, o no, tratarla (con bloques catch).
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 6: EXCEPCIONES 181

Una excepción no tratada en un bloque se propaga hacia el bloque llamante.


Este mecanismo de propagación continúa mientras no se trate la excepción o se
llegue al método de nivel superior. Si la excepción no se trata en el método de nivel
superior, se imprime un mensaje de error por consola.

En el gráfico anterior se presenta el caso de un bloque try que contiene una


llamada a un método Metodo_A que a su vez hace una llamada a Metodo_B, donde
se produce una excepción. Como la excepción no es tratada en Metodo_B se propaga
al bloque llamante (Metodo_A); a su vez la excepción no es tratada en Metodo_A,
por lo que se propaga al bloque try, donde sí que es capturada por un catch.

En el caso de que un método capture y trate la excepción, el mecanismo de


propagación se termina y la ejecución de los métodos llamados y el bloque try del
método llamante continúan como si la excepción no se hubiera producido:
182 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

En muchas ocasiones, después de tratar la excepción a un nivel, resulta


conveniente que también sea tratada a niveles superiores; por ejemplo, si estando
leyendo de un fichero origen y escribiendo su contenido en un fichero destino nos
encontramos con un error de lectura, además de tratar la excepción a ese nivel
(cerrar los ficheros, etc.) sería deseable propagar la excepción al programa llamante
para que informe al usuario y le permita hacer un nuevo intento si lo desea.

Para propagar de forma explícita una excepción se emplea la palabra


reservada throw seguida del objeto excepción. Ejemplo:

try {
// codigo
Origen.CopiaFichero(Destino);
// codigo
}
catch (IOException e) {
System.out.println (“Error de lectura ¿Desea intentarlo de Nuevo?”);
..........
}

public void CopiaFichero (TipoFichero Destino) {


try {
// codigo
}
catch (IOException e) {
// cerrar ficheros, etc.
throw e;
}

En el ejemplo anterior, el método CopiaFichero, después de tratar la


excepción, la propaga (throw) al método llamante, que a su vez hace otro
tratamiento de la excepción.

6.1.7 Estado de una excepción

Como hemos visto, todas las excepciones derivan de la clase Throwable y


tienen acceso a sus dos constructores y sus 7 métodos. El estado de las instancias de
esta clase se compone de un String que sirve de mensaje indicando las características
de la excepción y una pila de ejecución que contiene la relación de métodos que se
encuentran en ejecución en el momento en el que se produce la excepción.
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 6: EXCEPCIONES 183

Los métodos más significativos de la clase Throwable son:

Método Descripción
getMessage() Devuelve (como String) el mensaje de error almacenado en este
objeto. Este mensaje se compone del nombre de la excepción más
una breve referencia
printStackTrace() Imprime por el dispositivo de salida de errores (normalmente la
consola) el mensaje y la pila de ejecución almacenados en el objeto
Throwable. Este mismo método está sobrecargado para poder sacar
el resultado por diferentes salidas: printStackTrace(PrintStream s) y
printStackTrace(PrintWriter s)
toString() Devuelve (como String) una descripción corta del objeto

Para ilustrar la forma de uso de estos métodos, en la clase Excepcion2


modificamos el contenido de los bloques catch para imprimir la pila de ejecución:

16 catch (ArithmeticException e) {
17 System.out.println("Division por cero");
18 e.printStackTrace();
19 }
20
21 catch(IndexOutOfBoundsException e) {
22 System.out.println("Indice fuera del array");
23 e.printStackTrace();
24 }

A la nueva clase la hemos llamado Excepcion3. La instrucción donde se


realiza la división se encuentra en la línea 12 del método main, mientras que la
instrucción donde se asigna el valor a la posición concreta del array se encuentra en
la línea 13 del método main. A continuación se muestra el resultado de una posible
ejecución del programa:

Como se puede observar, cada instrucción e.printStackTrace() provoca la


impresión de un breve mensaje acerca de la excepción, seguido de la pila de
ejecución de los métodos del programa (en este caso sólo el método main) con
indicación de la línea de cada método donde se ha producido la excepción.
184 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

6.2 EXCEPCIONES DEFINIDAS POR EL


PROGRAMADOR

6.2.1 Introducción

Las excepciones predefinidas cubren las situaciones de error más habituales


con las que nos podemos encontrar, relacionadas con el propio lenguaje y el
hardware. Cuando se desarrollan aplicaciones existen otras situaciones de error de
más ‘alto nivel’ relacionadas con la funcionalidad de nuestros programas.

Imaginemos una aplicación informática que controla la utilización de los


remontes de una estación de esquí: los pases de acceso a los remontes son personales
e intransferibles y dispondrán de un código de barras que los identifica. Cada vez
que un usuario va a hacer uso de un remonte debe introducir su pase de acceso en
una máquina de validación, que acciona un torno y devuelve el pase. El sistema
puede constar de un ordenador central al que le llegan telemáticamente los datos
correspondientes a los códigos de barras de los pases que en cada momento se están
introduciendo en cada máquina de validación de cada remonte; si un código de
barras está en regla, el ordenador envía una orden de liberar el torno para permitir al
usuario acceder al remonte. El ordenador central habitualmente recibirá códigos
correctos utilizados en momentos adecuados, sin embargo, en ciertas ocasiones nos
encontraremos con situaciones anómalas:
1 Código de barras ilegible
2 Código de barras no válido (por ejemplo correspondiente a un pase
caducado)
3 Código de barras utilizado en otro remonte en un periodo de tiempo
demasiado breve
4 etc.

Ninguna de las situaciones anteriores se puede detectar utilizando


excepciones predefinidas, puesto que su naturaleza está estrechamente relacionada
con los detalles de la aplicación (no del lenguaje o el sistema informático), por lo
que tendremos que recurrir al método tradicional de incluir condiciones (if, case, ...)
de comprobación o bien hacer uso de excepciones definidas por nosotros mismos
(excepciones no predefinidas).

Antes de nada tenemos que tener en cuenta que el mecanismo de


excepciones es muy lento en ejecución comparado con la utilización de
instrucciones condicionales. Aunque el mecanismo de excepciones es elegante,
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 6: EXCEPCIONES 185

debemos utilizarlo con prudencia: únicamente en situaciones que realmente son


excepcionales.

La primera situación anómala de nuestro ejemplo (código de barras ilegible)


podría ser tratado como excepción o bien a través de alguna instrucción condicional,
depende de la frecuencia con la que se nos presente esta situación. Si ocurre, por
ejemplo, en aproximadamente un 0.5% de los casos, tendremos un claro candidato a
ser tratado como excepción. Si ocurre, digamos en un 14% de los casos, deberíamos
pensar en tratarlo con rapidez por medio de instrucciones condicionales. Para tratar
la segunda situación anómala del ejemplo (código de barras no valido) deberíamos
aplicar un razonamiento equivalente al que acabamos de realizar.

Nuestro tercer caso nos presenta la situación de que un mismo pase de pistas
ha podido ser duplicado (intencionadamente o por error), puesto que resulta
físicamente imposible hacer uso de un remonte en un instante y volver a utilizarlo en
otro remonte lejano medio minuto después. Probablemente esta situación se presenta
muy rara vez y resultaría muy adecuado tratarla como excepción propia.

Una vez razonada la utilidad de este tipo de excepciones, veremos la manera


de definirlas en Java

6.2.2 Definición de una excepción definida por el programador

En programación orientada a objetos lo más adecuado es que las


excepciones sean objetos, por lo que en Java definiremos las excepciones como
clases. Nuestras clases de excepción, en general, heredarán de la clase Exception.

En las clases que nos creemos a partir de Exception, incluiremos al menos el


constructor vacío y otro que contenga un String como argumento. Este String se
inicializa automáticamente con el nombre de la clase; la inicialización se realiza en
la superclase Throwable. El texto que pongamos al hacer uso del segundo
constructor se añadirá al nombre de la clase insertado por la superclase.

A continuación se presenta una clase (ExPropia ) muy simple que define una
excepción propia:

1 public class ExPropia extends Exception {


2
3 ExPropia () {
4 super("Esta es mi propia excepcion");
5 }
186 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

6
7 ExPropia (String s) {
8 super(s);
9 }
10
11 }

En la línea 1 se define nuestra clase Expropia que hereda de Exception. La


línea 3 define el constructor vacío, que en este caso añade el texto “Esta es mi propia
excepcion” al nombre de la clase, quedando “ExPropia: Esta es mi propia
excepcion”. La línea 7 define el constructor con un String como argumento; nos
servirá para añadir el texto que deseemos al instanciar la clase.

6.2.3 Utilización de una excepción definida por el programador

Una vez que disponemos de una excepción propia, podremos programar la


funcionalidad de nuestras aplicaciones provocando (lanzando) la excepción cuando
detectemos alguna de las situaciones anómalas asociadas.

En el siguiente ejemplo (ExPropiaClase) se presenta un método (línea 2)


que levanta (throw) la excepción ExPropia cuando se lee un cero (línea 4). La
excepción ExPropia podría levantarse en diferentes secciones de código de esta u
otras clases. No debemos olvidar indicar que nuestro método es susceptible de lanzar
la excepción (línea 2).

1 public class ExPropiaClase {


2 public void Metodo() throws ExPropia {
3 if (Teclado.Lee_int() == 0)
4 throw new ExPropia();
5 }
6 // otros metodos
7 }

Finalmente, en clases de más alto nivel podemos programar secciones de


código que recojan la excepción que hemos creado:

1 public class ExPropiaPrueba {


2 public static void main (String[] args) {
3 System.out.println("Hola");
4 do
5 try {
6 ExPropiaClase Instancia = new ExPropiaClase();
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 6: EXCEPCIONES 187

7 Instancia.Metodo();
8 }
9 catch(ExPropia e) {
10 System.out.println(e);
11 }
12 while(true);
13 }
14 }

Como se puede observar en la clase ExPropiaPrueba, podemos insertar


código en un bloque try asociándole un bloque catch que recoge la excepción que
hemos definido (ExPropia). A continuación se muestra un posible resultado de la
ejecución de ExPropiaPrueba.

6.2.4 Ejemplo

En los apartados anteriores hemos definido y utilizado una excepción con


sus constructores básicos, pero sin propiedades ni métodos adicionales. En ocasiones
con el esquema seguido es suficiente, sin embargo, a menudo es adecuado dotar de
un estado (propiedades) a la clase que define la excepción. Veamos este concepto
con un ejemplo sencillo: deseamos programar la introducción por teclado de
matrículas de vehículos en un país donde las matrículas se componen de 8
caracteres, siendo obligatoriamente el primero de ellos una letra. Si alguna matrícula
introducida por el usuario no sigue el formato esperado se recogerá la interrupción
oportuna y se escribirá un aviso en la consola.

En el código siguiente se presenta la clase ExMatricula que hereda la


funcionalidad de Exception (línea 1). Los constructores situados en las líneas 7 y 10
permiten iniciar el estado de la clase a través de los constructores de su superclase.

En la línea 3 se define la propiedad MalFormada, que contiene información


sobre la razón por la que el formato de la matrícula es incorrecto: tamaño
inadecuado (línea 4) o inexistencia de la letra inicial (línea 5).
188 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

El constructor de la línea 14 permite crear una excepción ExMatricula


indicando la naturaleza del problema que obligará a levantar (trhow) esta excepción
(ExMatricula.MAL_TAMANIO o ExMatricula.MAL_LETRA).

En la línea 18 se suministra un método que permitirá, a los programa que


reciben la excepción, saber la razón que ha provocado la misma.

1 public class ExMatricula extends Exception {


2
3 private int MalFormada = 0;
4 static final int MAL_TAMANIO = -1;
5 static final int MAL_LETRA = -2;
6
7 ExMatricula()
8 {}
9
10 ExMatricula(String s) {
11 super(s);
12 }
13
14 ExMatricula(int MalFormada) {
15 this.MalFormada = MalFormada;
16 }
17
18 public int DimeProblema() {
19 return MalFormada;
20 }
21
22 }

Una vez creada la excepción ExMatricula podemos escribir el código que


valida las matrículas. La siguiente clase (ExMatriculaValidar) realiza este cometido:
el método Validar situado en la línea 7 comprueba el formato del parámetro
suministrado y si es necesario tendrá capacidad de levantar (throws) la excepción
ExMatricula .

Si la longitud de la matrícula es distinta de 8 caracteres (línea 8) se crea y


levanta la excepción ExMatricula con estado inicial ExMatricula.MAL_TAMANIO
(línea 9). Si el carácter inicial no es una letra (línea 11) se crea y levanta la
excepción ExMatricula con estado inicial ExMatricula.MAL_LETRA (línea 12).

El método privado (sólo es visible en esta clase) UnaLetra aísla el primer


carácter y comprueba que es una letra empleando el método matches y la expresión
regular “[A-Za-z]”. La utilización de expresiones regulares se incluye a partir de la
versión 1.4 del JDK.
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 6: EXCEPCIONES 189

1 public class ExMatriculaValidar {


2
3 private boolean UnaLetra(String Matricula) {
4 return Matricula.substring(0,1).matches("[A-Za-z]");
5 }
6
7 public void Validar(String Matricula) throws ExMatricula {
8 if (Matricula.length()!=8)
9 throw new ExMatricula(ExMatricula.MAL_TAMANIO);
10 else
11 if (!UnaLetra(Matricula))
12 throw new ExMatricula(ExMatricula.MAL_LETRA);
13 else
14 {} // matricula bien formada
15 }
16
17 }

Con el método de validación de matrículas implementado, capaz de levantar


la excepción ExMatricula , ya podemos escribir una aplicación que recoja las
matrículas que introducen los usuarios. La recuperación de los errores de formato se
escribirá en el bloque catch correspondiente:

1 public class ExMatriculaPrincipal {


2
3 public static void main (String[] args) {
4 ExMatriculaValidar LaMatricula = new
ExMatriculaValidar();
5
6 do
7 try {
8 System.out.println("Introduce la matricula: ");
9 String Matricula = Teclado.Lee_String();
10 LaMatricula.Validar(Matricula);
11 }
12 catch(ExMatricula e) {
13 switch (e.DimeProblema()) {
14 case ExMatricula.MAL_TAMANIO:
15 System.out.println("Tamanio incorrecto");
16 break;
17 case ExMatricula.MAL_LETRA:
18 System.out.println("Letra inicial no
incluida");
19 break;
20 default:
21 System.out.println("Matricula correcta");
22 break;
190 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

23 }
24 }
25 while(true);
26 }
27 }

En el código anterior existe un bucle infinito (líneas 6 y 25) que permite la


validación de matrículas (líneas 8, 9 y 10). Habitualmente estas tres líneas se
ejecutan repetitivamente; esporádicamente el usuario introducirá una matrícula con
el formato erróneo y el método Validar levantará la excepción ExMatricula , que será
recogida y tratada en el bloque catch (línea 12).

En la línea 13 sabemos que se ha producido una excepción ExMatricula ,


pero no conocemos su causa exacta (tamaño incorrecto o primer carácter distinto de
letra). El método DimeProblema que incluimos en la clase ExMatricula nos indica el
estado de la excepción. Si el estado de la excepción es MAL_TAMANIO le
indicamos esta situación al usuario que introdujo la matrícula con un tamaño
incorrecto (líneas 14 y 15). En las líneas 17 y 18 hacemos lo propio con
MAL_LETRA.

La ventana siguiente muestra un ejemplo de ejecución del programa:

Los objetos utilizados en el ejemplo se presentan en la siguiente tabla:

Objeto Cometido Nombre de la clase


Excepción Define la situación de error ExMatricula
Clase que levanta la Reconocimiento y comunicación de ExMatriculaValidar
excepción la situación de error, junto con la
creación del objeto excepción
Clase que recoge la Recibe la situación de error y trata ExMatriculaPrincipal
excepción de recuperar la situación
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 6: EXCEPCIONES 191

La estructura de clases se representa en el siguiente diagrama que relaciona


los objetos:
CAPÍTULO 7

INTERFAZ GRÁFICO DE USUARIO

7.1 CREACIÓN DE VENTANAS

7.1.1 Introducción

Hasta ahora, todos los ejemplos que se han desarrollado se han ejecutado en
modo consola, es decir, las entradas de datos y la visualización de resultados se
realiza a través de una ventana de MS-DOS. Las aplicaciones normalmente
requieren de un interfaz de usuario más sofisticado, basado en ventanas gráficas y
distintos componentes (botones, listas, cajas de texto, etc.) en el interior de estas
ventanas.

Java proporciona una serie de paquetes estándar que nos facilitan la labor de
crear los Interfaces Gráficos de Usuario (GUI) que necesitemos. Estos paquetes se
encuentran englobados dentro de Java Foundation Classes (JFC), que proporciona:
• Soporte de componentes de entrada/salida a través de AWT (Abstract Window
Toolkit) y Swing
• Imágenes 2D
• Servicio de impresión
• Soporte para funciones Drag-and-Drop (arrastrar y pegar)
• Funciones de accesibilidad, etc.

De los paquetes anteriores, los más utilizados, con diferencia, son AWT y
Swing. AWT fue el primero en aparecer y proporciona los componentes básicos de
entrada/salida; Swing recoge la mayor parte de la funcionalidad de AWT, aportando
clases más complejas y especializadas que las ya existentes, así como otras de nueva
creación.
194 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

El elemento más básico que se suele emplear en un interfaz gráfico de


usuario es la ventana. AWT proporciona la clase Window, aunque para simplificar
los desarrollos acudiremos a la clase Frame (marco) derivada de Window y por lo
tanto más especializada.

La jerarquía existente en torno a las ventanas de Java es:

Object java.lang

Component

Container
java.awt
Window

Frame

JFrame javax.swing

La clase Object es la raíz de toda la jerarquía de clases, por lo que todos los
objetos tienen a Object como superclase. La clase abstracta Component proporciona
una representación gráfica susceptible de ser representada en un dispositivo de
salida; como se puede observar es una clase muy genérica que no utilizaremos
directamente, sino a través de sus clases derivadas.

La clase abstracta Container deriva de Component y añade la funcionalidad


de poder contener otros componentes AWT (por ejemplo varios botones, una caja de
texto, etc.). La clase Window proporciona una ventana gráfica sin bordes ni
posibilidad de incluir barras de menú, por ello haremos uso de su clase derivada
Frame, que no presenta las restricciones del objeto Window.

JFrame es la versión de Frame proporcionada por Swing. Al igual que


muchos otros componentes de Swing, ofrece posibilidades más amplias que las
implementadas por sus superclases de AWT, aunque también presenta una mayor
complejidad de uso.

Nosotros haremos uso de las clases más útiles que nos ofrece AWT,
empezando por la definición de ventanas mediante el objeto Frame.
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 7: INTERFAZ GRÁFICO DE USUARIO 195

7.1.2 Utilización de la clase Frame

Una manera muy sencilla de crear y visualizar una ventana en Java se


muestra en el siguiente ejemplo (Marco1): en primer lugar se importa la clase Frame
de AWT (línea 1); más adelante, en otros ejemplos, utilizaremos conjuntamente
diferentes clases de AWT, por lo que nos resultará más sencillo utilizar la
instrucción import java.awt.*.

La línea 6 nos crea la instancia MiMarco de tipo Frame; en la línea 7 se


asigna un tamaño de ventana de 400 (horizontal) por 200 (vertical) pixels; en la línea
8 se define el título de la ventana y, por último, la línea 9 hace visible (representa) el
marco creado y definido en las líneas anteriores.

La clase Frame contiene 4 constructores; uno de ellos permite definir el


título, por lo que las líneas 6 y 8 pueden sustituirse por la sentencia Frame MiMarco
= new Frame(“Mi primera ventana”);.

1 import java.awt.Frame;
2
3 public class Marco1 {
4
5 public static void main(String[] args) {
6 Frame MiMarco = new Frame();
7 MiMarco.setSize(400,200);
8 MiMarco.setTitle("Mi primera ventana");
9 MiMarco.setVisible(true);
10 }
11 }

No existe ningún constructor de la clase Frame que admita como parámetros


el tamaño de la ventana, lo que nos ofrece la posibilidad de crearnos nuestra propia
clase (Marco2) que herede de Frame y ofrezca esta posibilidad:

1 import java.awt.Frame;
2
3 public class Marco2 extends Frame {
4
5 Marco2(String Titulo) {
196 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

6 this.setTitle(Titulo);
7 this.setVisible(true);
8 }
9
10 Marco2(String Titulo, int Ancho, int Alto) {
11 this(Titulo);
12 this.setSize(Ancho, Alto);
13 }
14
15 Marco2() {
16 this("Mi primera ventana",400,200);
17 }
18
19 }

La clase Marco2 que hemos diseñado permite utilizar el constructor vacío


(línea 15), obteniendo una ventana de tamaño 400 x 200 con el título “Mi primera
ventana”. También se puede incorporar el título (línea 5), consiguiéndose una
ventana visible (línea 7) con el título indicado. Finalmente se proporciona un
constructor que permite incluir título y dimensiones (línea 10); en este caso nos
apoyamos en el constructor que admite título (palabra reservada this en la línea 11) y
el método setSize del objeto Frame (línea 12).

Una vez definida la clase Marco2, podemos utilizarla en diferentes


programas como el mostrado a continuación:

1 public class Marco2main {


2
3 public static void main(String[] args) {
4 Marco2 MiVentana1 = new Marco2();
5 Marco2 MiVentana2 = new Marco2("Ventana2",300,50);
6 Marco2 MiVentana3 = new Marco2("Ventana3");
7 }
8 }

Marco2main crea tres instancias de la clase Marco2, que al ser subclase de


Frame representa marcos de ventanas; las líneas 4 a 6 muestran diferentes maneras
de instanciar Marco2. El resultado obtenido son tres ventanas superpuestas de
diferentes tamaños y títulos:
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 7: INTERFAZ GRÁFICO DE USUARIO 197

7.1.3 Creación y posicionamiento de múltiples ventanas

Los interfaces gráficos de usuario (GUI) no tienen por qué restringirse al uso
de una única ventana para entrada de datos, mostrar información, etc. Siguiendo el
mecanismo explicado en los apartados anteriores podemos crear y hacer uso de un
número ilimitado de ventanas.

Cuando se crean varias ventanas a menudo resulta útil situar cada una de
ellas en diferentes posiciones del dispositivo de salida, de forma que adopten una
configuración práctica y estética, evitándose que se oculten entre sí, etc.

Para situar una ventana en una posición concreta utilizamos el método


setLocation, heredado de la clase Component. Este método está sobrecargado y
admite dos tipos de parámetros de entrada:
• setLocation (int x, int y)
• setLocation (Point p)

En ambos casos es necesario suministrar las coordenadas bidimensionales


que sitúen la esquina superior izquierda de la ventana respecto a su componente
padre (la pantalla, el objeto contenedor del Frame, etc.). El siguiente ejemplo
(Marco3) ilustra la manera de utilizar el método que usa la clase Point de AWT
como argumento:

1 import java.awt.Frame;
2 import java.awt.Point;
3
4 public class Marco3 {
5
6 public static void main(String[] args) {
7 Frame MiMarco = new Frame();
8 MiMarco.setSize(400,200);
9 MiMarco.setTitle("Mi primera ventana");
10 MiMarco.setLocation(new Point(100,220));
11 MiMarco.setVisible(true);
12 }
13 }

El resultado de la clase Marco3 es una ventana situada en la posición


(100,220) respecto al origen del GraphicsDevice, que habitualmente es la pantalla
gráfica del ordenador.

A continuación se presenta la clase Marco4, que surge de Marco2,


añadiendo la posibilidad de indicar la posición de la ventana instanciada:
198 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

1 import java.awt.Frame;
2 import java.awt.Point;
3
4 public class Marco4 extends Frame {
5
6 Marco4(String Titulo) {
7 this.setTitle(Titulo);
8 this.setVisible(true);
9 }
10
11 Marco4(String Titulo, int Ancho, int Alto) {
12 this(Titulo);
13 this.setSize(Ancho, Alto);
14 }
15
16 Marco4(String Titulo, int Ancho, int Alto,
17 int PosX, int PosY) {
18 this(Titulo,Ancho, Alto);
19 this.setLocation(new Point(PosX,PosY));
20 }
21
22 Marco4() {
23 this("Mi primera ventana",400,200,100,100);
24 }
25
26 }

El constructor definido en la línea 16 permite situar el marco creado en la


posición deseada (línea 19). La clase Marco4main instancia varias ventanas con
diferentes tamaños y posicio nes iniciales:

1 public class Marco4main {


2
3 public static void main(String[] args) {
4 Marco4 MiVentana1 = new Marco4();
5 Marco4 MiVentana2 = new
Marco4("Ventana2",300,50,100,300);
6 Marco4 MiVentana3 = new Marco4("Ventana3",400,100);
7 }
8 }

La posición relativa de las 3 ventanas involucradas en el ejemplo es la


siguiente:
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 7: INTERFAZ GRÁFICO DE USUARIO 199

7.2 PANELES Y OBJETOS DE DISPOSICIÓN (LAYOUTS)

7.2.1 Introducción

Las ventanas tienen utilidad si podemos añadirles diversos componentes de


interacción con los usuarios. El elemento que en principio se adapta mejor a esta
necesidad es la clase Container; como ya se explicó: “La clase abstracta Container
deriva de Component y añade la funcionalidad de poder contener otros componentes
AWT (por ejemplo varios botones, una caja de texto, etc.)”.

En lugar de utilizar directamente la clase Container, resulta mucho más


sencillo hacer uso de su clase heredada Panel. Un panel proporciona espacio donde
una aplicación puede incluir cualquier componente, incluyendo otros paneles.

Para ilustrar el funcionamiento y necesidad de los paneles emplearemos un


componente muy útil, conocido y fácil de utilizar: los botones (Button). Una forma
de crear un botón es a través de su constructor Button(String Texto), que instancia un
botón con el “Texto” interior que se le pasa como parámetro.
Object java.lang

Component

Button Container
java.awt
Panel Window

Frame
200 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

Nuestro primer ejemplo (Boton1) no hace uso de ningún panel, situando un


botón directamente sobre una ventana. En la línea 8 se crea la instancia BotonHola
del objeto Button, con el texto “Hola” como etiqueta. La línea 10 es muy importante,
añade el componente BotonHola al contenedor MiMarco; además de crear los
componentes (en nuestro caso el botón) es necesario situar los mismos en los
contenedores donde se vayan a visualizar.

A continuación del código se representa el resultado del programa: un botón


que ocupa toda la ventana. Si hubiéramos creado varios botones y los hubiésemos
añadido a MiMarco, el resultado visible sería la ventana ocupada por entero por el
último botón añadido.

1 import java.awt.Frame;
2 import java.awt.Button;
3
4 public class Boton1 {
5
6 public static void main(String[] args) {
7 Frame MiMarco = new Frame();
8 Button BotonHola = new Button("Hola");
9
10 MiMarco.add(BotonHola);
11
12 MiMarco.setSize(400,200);
13 MiMarco.setTitle("Ventana con botón");
14 MiMarco.setVisible(true);
15 }
16 }

Si no utilizamos paneles (y/o layouts), los componentes (tales como


botones) se representarán en las ventanas de una manera inadecuada. Los paneles y
layouts nos permiten añadir componentes visuales con diferentes disposiciones en el
plano.

El ejemplo anterior puede ser ampliado con facilidad para que el marco
incorpore un panel. Usando el panel conseguiremos que los componentes que
añadamos al mismo sigan una disposición establecida (por defecto ordenación
secuencial).
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 7: INTERFAZ GRÁFICO DE USUARIO 201

La clase Boton2 instancia un panel en la línea 9 y lo añade al marco en la


línea 13. Los botones creados en las líneas 10 y 11 se añaden (secuencialmente, por
defecto) al panel en las líneas 14 y 15:

Marco
Panel

Botón

1 import java.awt.Frame;
2 import java.awt.Button;
3 import java.awt.Panel;
4
5 public class Boton2 {
6
7 public static void main(String[] args) {
8 Frame MiMarco = new Frame();
9 Panel MiPanel = new Panel();
10 Button BotonArea = new Button("Calcular área");
11 Button BotonPerimetro = new Button("Calcular
perímetro");
12
13 MiMarco.add(MiPanel);
14 MiPanel.add(BotonArea);
15 MiPanel.add(BotonPerimetro);
16
17 MiMarco.setSize(400,200);
18 MiMarco.setTitle("Ventana con botones");
19 MiMarco.setVisible(true);
20 }
21 }
202 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

7.2.2 Utilización básica de paneles

Los paneles son contenedores que pueden albergar componentes (incluidos


otros paneles). En el siguiente ejemplo (Panel1 ) emplearemos la disposición de
elementos que se muestra a continuación:

Marco
PanelPrincipal

PanelDcha

BotonDescansar
PanelIzq
BotonSaltar BotonCorrer

1 import java.awt.Frame;
2 import java.awt.Button;
3 import java.awt.Panel;
4
5 public class Panel1 {
6
7 public static void main(String[] args) {
8 Frame MiMarco = new Frame();
9 Panel PanelPrincipal = new Panel();
10 Panel PanelIzq = new Panel();
11 Panel PanelDcha = new Panel();
12
13 Button BotonCorrer = new Button("Correr");
14 Button BotonSaltar = new Button("Saltar");
15 Button BotonDescansar = new Button("Descansar");
16
17 MiMarco.add(PanelPrincipal);
18 PanelPrincipal.add(PanelIzq);
19 PanelPrincipal.add(PanelDcha);
20 PanelIzq.add(BotonCorrer);
21 PanelIzq.add(BotonSaltar);
22 PanelDcha.add(BotonDescansar);
23
24 MiMarco.setSize(400,200);
25 MiMarco.setTitle("Ventana con paneles");
26 MiMarco.setVisible(true);
27 }
28 }
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 7: INTERFAZ GRÁFICO DE USUARIO 203

La ventana de la izquierda representa un posible resultado de este programa.


Dependiendo de la definición de pantalla en pixels que tenga el usuario la relación
de tamaños entre los botones y la ventana puede variar. Si los botones no caben a lo
ancho, la disposición secuencial hará “saltar de línea” al panel derecho,
obteniéndose una visualización como la mostrada en la ventana de la derecha.

La idea subyacente en el diseño del paquete AWT es que los interfaces


gráficos de usuario puedan visualizarse independientemente de las características y
configuración de los dispositivos de salida, así como del tamaño de las ventanas de
visualización (en muchos casos los navegadores Web). Para compaginar este
objetivo con la necesidad de dotar al programador de la flexibilidad necesaria en el
diseño de GUI’s se proporcionan las clases “Layouts” (FlowLayout, BorderLayout,
GridLayout, etc.).

Cada panel que creamos tiene asociado, implícita o explícitamente, un tipo


de disposición (“layout”) con la que se visualizan los componentes que se le añaden.
Si no especificamos nada, la disposición por defecto (implícita) es FlowLayout, con
los elementos centrados en el panel, tal y como aparecen los botones del ejemplo
anterior.

7.2.3 Objeto de disposición (Layout): FlowLayout

Para establecer una disposición de manera explícita, podemos recurrir al


método setLayout (heredado de la clase Container) o bien instanciar nuestro panel
indicando directamente el “layout” deseado. Las siguientes instrucciones ilustran las
posibles maneras de asociar un FlowLayout a un panel:
Panel MiPanel = new Panel (new FlowLayout());
OtroPanel.setLayout(new FlowLayout());

El siguiente ejemplo (FlowLayout1) muestra una posible forma de crear una


ventana con 4 botones dispuestos horizontalmente. En la línea 11 creamos el objeto
204 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

PosicionamientoSecuencial, de tipo FlowLayout. En la línea 17 asociamos este


objeto al panel instanciado en la línea 10 (MiPanel).

1 import java.awt.Frame;
2 import java.awt.Button;
3 import java.awt.Panel;
4 import java.awt.FlowLayout;
5
6 public class FlowLayout1 {
7
8 public static void main(String[] args) {
9 Frame MiMarco = new Frame();
10 Panel MiPanel = new Panel();
11 FlowLayout PosicionamientoSecuencial = new
FlowLayout();
12 Button BotonA = new Button("Primer botón");
13 Button BotonB = new Button("Segundo botón");
14 Button BotonC = new Button("Tercer botón");
15 Button BotonD = new Button("Cuarto botón");
16
17 MiPanel.setLayout(PosicionamientoSecuencial);
18
19 MiMarco.add(MiPanel);
20 MiPanel.add(BotonA);
21 MiPanel.add(BotonB);
22 MiPanel.add(BotonC);
23 MiPanel.add(BotonD);
24
25 MiMarco.setSize(300,100);
26 MiMarco.setTitle("Ventana con flow layout");
27 MiMarco.setVisible(true);
28 }
29 }

La ventana que se visualiza contiene cuatro botones posicionados


consecutivamente y centrados respecto al panel. Dependiendo del tamaño con el que
dimensionemos la ventana, los botones cabrán horizontalmente o bien tendrán que
“saltar” a sucesivas líneas para tener cabida, tal y como ocurre con las líneas en un
procesador de texto:
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 7: INTERFAZ GRÁFICO DE USUARIO 205

Los objetos FlowLayout pueden instanciarse con posicionamiento centrado


(FlowLayout.CENTER), izquierdo (FlowLayout.LEFT), derecho
(FlowLayout.RIGHT) y los menos utilizados LEADING y TRAILING. La
disposición por defecto es CENTER.

Si en el ejemplo anterior sustituimos la línea 11 por: FlowLayout


PosicionamientoSecuencial = new FlowLayout(FlowLayout.LEFT);
el resultado obtenido es:

7.2.4 Objeto de disposición (Layout): BorderLayout

Un BorderLayout sitúa y dimensiona los componentes en 5 “regiones


cardinales”: norte, sur, este, oeste y centro. La clase que se presenta a continuación
(BorderLayout1) crea un BorderLayout (línea 11) y lo asocia al panel MiPanel
(línea 18). En las líneas 21 a 25 se añaden cinco botones al panel, cada uno en una
zona “cardinal”.

1 import java.awt.Frame;
2 import java.awt.Button;
3 import java.awt.Panel;
4 import java.awt.BorderLayout;
5
6 public class BorderLayout1 {
7
8 public static void main(String[] args) {
9 Frame MiMarco = new Frame();
10 Panel MiPanel = new Panel();
11 BorderLayout PuntosCardinales = new BorderLayout();
12 Button BotonNorte = new Button("Norte");
13 Button BotonSur = new Button("Sur");
14 Button BotonEste = new Button("Este");
15 Button BotonOeste = new Button("Oeste");
16 Button BotonCentro = new Button("Centro");
17
18 MiPanel.setLayout(PuntosCardinales);
19
206 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

20 MiMarco.add(MiPanel);
21 MiPanel.add(BotonNorte, BorderLayout.NORTH);
22 MiPanel.add(BotonSur, BorderLayout.SOUTH);
23 MiPanel.add(BotonEste, BorderLayout.EAST);
24 MiPanel.add(BotonOeste, BorderLayout.WEST);
25 MiPanel.add(BotonCentro, BorderLayout.CENTER);
26
27 MiMarco.setSize(300,100);
28 MiMarco.setTitle("Ventana con BorderLayout");
29 MiMarco.setVisible(true);
30 }
31 }

Tal y como muestran los dos siguientes resultados (correspondientes a la


ventana con el tamaño original y la misma ventana agrandada) los componentes
(botones) insertados en el panel se sitúan en sus respectivas regiones del
BorderLayout.

Cada región de un BorderLayout admite un único componente, pero nada


impide que ese componente sea un contenedor que admita una nueva serie de
componentes. Vamos a modificar la clase BorderLayout1 para que la región norte
sea ocupada por tres botones dispuestos en posición horizontal. El diseño de la
solución es el siguiente:

Marco
PanelGeneral
PanelMenuNorte
BotonOpcion1
BotonOpcion2
BotonOpcion3

BotonOeste BotonEste
BotonSur
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 7: INTERFAZ GRÁFICO DE USUARIO 207

En la clase BorderLayout2 se crea el panel PanelGeneral (línea 8) al que se


asocia el BorderLayout PuntosCardinales (línea 9) mediante el método setLayout
(línea 19). Análogamente, se crea el panel PanelMenuNorte (línea 7) al que se asocia
el FlowLayout OpcionesMenu (línea 10) mediante el método setLayout (línea 20).

A PanelMenuNorte se le añaden tres botones (líneas 24 a 26), mientras que


el propio panel (con los tres botones incluidos) se añade a la región norte del
PanelGeneral (línea 23). De esta manera, el botón BotonNorte del ejemplo anterior
queda sustituido por la secuencia (en flujo secuencial) de botones BotonOpcion1,
BotonOpcion2 y BotonOpcion3.

1 import java.awt.*;
2
3 public class BorderLayout2 {
4
5 public static void main(String[] args) {
6 Frame MiMarco = new Frame();
7 Panel PanelMenuNorte = new Panel();
8 Panel PanelGeneral = new Panel();
9 BorderLayout PuntosCardinales = new BorderLayout();
10 FlowLayout OpcionesMenu = new FlowLayout();
11 Button BotonOpcion1 = new Button("Opción 1");
12 Button BotonOpcion2 = new Button("Opción 2");
13 Button BotonOpcion3 = new Button("Opción 3");
14 Button BotonSur = new Button("Sur");
15 Button BotonEste = new Button("Este");
16 Button BotonOeste = new Button("Oeste");
17 Button BotonCentro = new Button("Centro");
18
19 PanelGeneral.setLayout(PuntosCardinales);
20 PanelMenuNorte.setLayout(OpcionesMenu);
21
22 MiMarco.add(PanelGeneral);
23 PanelGeneral.add(PanelMenuNorte, BorderLayout.NORTH);
24 PanelMenuNorte.add(BotonOpcion1);
25 PanelMenuNorte.add(BotonOpcion2);
26 PanelMenuNorte.add(BotonOpcion3);
27 PanelGeneral.add(BotonSur, BorderLayout.SOUTH);
28 PanelGeneral.add(BotonEste, BorderLayout.EAST);
29 PanelGeneral.add(BotonOeste, BorderLayout.WEST);
30 PanelGeneral.add(BotonCentro, BorderLayout.CENTER);
31
32 MiMarco.setSize(400,150);
33 MiMarco.setTitle("Ventana con BorderLayout");
34 MiMarco.setVisible(true);
35 }
36 }
208 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

7.2.5 Objeto de disposición (Layout): GridLayout

Un GridLayout coloca y dimensiona los componentes en una rejilla


rectangular. El contenedor se divide en rectángulos de igual tamaño, colocándose un
componente en cada rectángulo.

Los objetos GridLayout se pueden instanciar indicando el número de filas y


columnas de la rejilla:
GridLayout Matriz = new GridLayout(3,2);

También se puede hacer uso del constructor vacío y posteriormente utilizar


los métodos setRows y setColumns:
GridLayout Matriz = new GridLayout();
Matriz.setRows(3);
Matriz.setColumns(2);

La clase GridLayout1 crea una ventana con 6 botones dispuestos


matricialmente en 2 filas y 3 columnas, para ello se instancia un objeto de tipo
GridLayout con dicha disposición (línea 9). En la línea 14 se asocia el GridLayout al
panel, añadiéndose posteriormente 6 botones al mismo (líneas 15 y 16).

1 import java.awt.*;
2
3 public class GridLayout1 {
4
5 public static void main(String[] args) {
6 Frame MiMarco = new Frame();
7 Panel MiPanel = new Panel();
8
9 GridLayout Matriz = new GridLayout(2,3);
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 7: INTERFAZ GRÁFICO DE USUARIO 209

10 Button[] Botones = new Button[6];


11 for (int i=0;i<6;i++)
12 Botones[i] = new Button("Botón "+i);
13
14 MiPanel.setLayout(Matriz);
15 for (int i=0;i<6;i++)
16 MiPanel.add(Botones[i]);
17
18 MiMarco.add(MiPanel);
19 MiMarco.setSize(300,100);
20 MiMarco.setTitle("Ventana con GridLayout");
21 MiMarco.setVisible(true);
22 }
23 }

En GridLayout2 se crea una clase muy sencilla en la que se asocia un


GridLayout de 3 x 2 directamente al marco de la aplicación:

1 import java.awt.*;
2
3 public class GridLayout2 {
4
5 public static void main(String[] args) {
6 Frame MiMarco = new Frame();
7
8 MiMarco.setLayout(new GridLayout(3,2));
9 for (int i=0;i<6;i++)
10 MiMarco.add(new Button("Botón "+i));
11
12 MiMarco.setSize(300,100);
13 MiMarco.setTitle("Ventana con GridLayout");
14 MiMarco.setVisible(true);
15 }
16 }
210 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

Finalmente, en GridLayout3 se utiliza un GridLayout (línea 8) en el que, en


una celda, se añade un componente Panel (Vertical) con disposición BorderLayout
(línea 7). Al panel Vertical se le añaden tres botones en las regiones cardinales:
norte, centro y sur (líneas 10 a 12).

1 import java.awt.*;
2
3 public class GridLayout3 {
4
5 public static void main(String[] args) {
6 Frame MiMarco = new Frame();
7 Panel Vertical = new Panel(new BorderLayout());
8 MiMarco.setLayout(new GridLayout(2,3));
9
10 Vertical.add(new Button("Arriba"), BorderLayout.NORTH);
11 Vertical.add(new Button("Centro"),
BorderLayout.CENTER);
12 Vertical.add(new Button("Abajo"), BorderLayout.SOUTH);
13
14 MiMarco.add(Vertical);
15 for (int i=1;i<6;i++)
16 MiMarco.add(new Button("Botón "+i));
17
18 MiMarco.setSize(300,160);
19 MiMarco.setTitle("Ventana con Layouts Grid y Border");
20 MiMarco.setVisible(true);
21 }
22 }
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 7: INTERFAZ GRÁFICO DE USUARIO 211

7.3 ETIQUETAS, CAMPOS Y ÁREAS DE TEXTO

7.3.1 Introducción

Una vez que somos capaces de crear marcos, añadirles paneles con
diferentes “layouts” e insertar componentes en los mismos, estamos preparados para
realizar diseños de interfaces de usuario, sin embargo, todavía nos falta conocer los
diferentes componentes básicos de entrada/salida que podemos utilizar en Java.

Un componente que hemos empleado en todos los ejemplos anteriores es el


botón (Button). Ahora se explicarán las etiquetas, los campos de texto y las áreas de
texto.
Object java.lang

Component

Button Container

Label java.awt
Panel Window
TextComponent

Frame
TextField TextArea

7.3.2 Etiqueta (Label)

Las etiquetas permiten situar un texto en un contenedor. El usuario no puede


editar el texto, aunque si se puede variar por programa.

Los constructores de la clase Label son:


Label ()
Label (String Texto)
Label (String Texto, int Alineacion)
212 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

Entre los métodos existentes tenemos:


setText (String Texto)
setAlignment (int Alineacion)

De esta manera podemos definir e instanciar etiquetas de diversas maneras:


Label Saludo = new Label (“Hola”, Label.LEFT);

Label OtroSaludo = new Label (“Buenos días”);


OtroSaludo.setAlignment (Label.CENTER);

Label Cabecera = new Label ();


Cabecera.setAlignment (Label.RIGHT);
Cabecera.setText (”Ingresos totales”);

En la clase Etiqueta1 se presenta la manera más simple de visualizar una


etiqueta:

1 import java.awt.Frame;
2 import java.awt.Label;
3
4 public class Etiqueta1 {
5
6 public static void main(String[] args) {
7 Frame MiMarco = new Frame();
8 Label Titulo = new Label("Notas de matemáticas");
9
10 MiMarco.add(Titulo);
11
12 MiMarco.setSize(200,100);
13 MiMarco.setTitle("Ventana con etiqueta");
14 MiMarco.setVisible(true);
15 }
16 }

El siguiente ejemplo que se presenta (Etiqueta2) combina el uso de


diferentes “la youts” con el de etiquetas. Se instancia un PanelGeneral con un
GridLayout 1 x 2 asociado (línea 7). Al PanelGeneral se le añaden los paneles
PanelIzquierdo y PanelDerecho (líneas 11 y 12), el primero con disposición
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 7: INTERFAZ GRÁFICO DE USUARIO 213

GridLayout 3 x 1 (línea 8) y el segundo con disposición FlowLayout (línea 9). En las


líneas 13 a 17 se definen y añaden etiquetas utilizando su constructor más completo.

MiMarco
PanelGeneral

PanelDerecho

PanelIzquierdo

1 import java.awt.*;
2
3 public class Etiqueta2 {
4
5 public static void main(String[] args) {
6 Frame MiMarco = new Frame();
7 Panel PanelGeneral = new Panel(new GridLayout(1,2));
8 Panel PanelIzquierdo = new Panel(new GridLayout(3,1));
9 Panel PanelDerecho = new Panel(new FlowLayout());
10
11 PanelGeneral.add(PanelIzquierdo);
12 PanelGeneral.add(PanelDerecho);
13 PanelIzquierdo.add(new Label("Ford",Label.CENTER));
14 PanelIzquierdo.add(new Label("Opel",Label.CENTER));
15 PanelIzquierdo.add(new Label("Audi",Label.CENTER));
16 PanelDerecho.add(new Label("Coupe"));
17 PanelDerecho.add(new Label("Cabrio"));
18
19 MiMarco.add(PanelGeneral);
20 MiMarco.setSize(250,100);
21 MiMarco.setTitle("Ventana con etiqueta");
22 MiMarco.setVisible(true);
23 }
24 }
214 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

En el último ejemplo de etiquetas (Etiqueta3) se imprime la tabla de


multiplicar con cabeceras, para ello se crea un GridLayout de 11 x 11 (línea 7) que
albergará los resultados de la tabla de multiplicar (10 x 10 resultados) más la
cabecera de filas y la cabecera de columnas.

Los valores de las cabeceras de filas y columnas se crean como sendas


matrices unidimensionales de etiquetas CabeceraFila y CabeceraColumna (líneas 8
y 9).

En las líneas 11 a 14 se asignan valores a los elementos del array


CabeceraFila, y se les asocia el color rojo mediante el método setBackground
perteneciente a la clase Component. En las líneas 18 a 20 se hace lo propio con el
array CabeceraColumna.

Las etiquetas de las celdas centrales de la tabla (los resultados) se calculan y


añaden al panel Tabla en las líneas 22 y 23, dentro del bucle que recorre cada fila
(línea 18).

1 import java.awt.*;
2
3 public class Etiqueta3 {
4
5 public static void main(String[] args) {
6 Frame MiMarco = new Frame();
7 Panel Tabla = new Panel(new GridLayout(11,11));
8 Label[] CabeceraFila = new Label[11];
9 Label[] CabeceraColumna = new Label[11];
10
11 Tabla.add(new Label(""));
12 for (int i=1;i<=10;i++) {
13 CabeceraFila[i] = new Label(""+i);
14 CabeceraFila[i].setBackground(Color.red);
15 Tabla.add(CabeceraFila[i]);
16 }
17
18 for (int i=1;i<=10;i++) {
19 CabeceraColumna[i] = new Label(""+i);
20 CabeceraColumna[i].setBackground(Color.red);
21 Tabla.add(CabeceraColumna[i]);
22 for (int j=1;j<=10;j++)
23 Tabla.add(new Label(""+i*j));
24 }
25
26 MiMarco.add(Tabla);
27 MiMarco.setSize(400,400);
28 MiMarco.setTitle("Tabla de multiplicar");
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 7: INTERFAZ GRÁFICO DE USUARIO 215

29 MiMarco.setVisible(true);
30 }
31 }

7.3.3 Campo de texto (TextField)

Un campo de texto es un componente que permite la edición de una línea de


texto. Sus constructores permiten que su tamaño tenga un número de columnas
determinado y que el campo presente un texto inicial.

En la clase CampoDeTexto1 se instancian tres campos de texto: Nombre,


Apellidos y Nacionalidad (líneas 8 a 10); Nombre se define con un tamaño de
edición de 15 columnas y sin texto inicial, Apellidos con 60 columnas y sin texto
inicial, finalmente Nacionalidad con 15 columnas y texto inicial “Española”.

1 import java.awt.*;
2
3 public class CampoDeTexto1 {
4
5 public static void main(String[] args) {
6 Frame MiMarco = new Frame();
7 Panel EntradaDeDatos = new Panel(new FlowLayout());
8 TextField Nombre = new TextField(15);
9 TextField Apellidos = new TextField(60);
10 TextField Nacionalidad = new TextField("Española",15);
11
12 EntradaDeDatos.add(Nombre);
13 EntradaDeDatos.add(Apellidos);
14 EntradaDeDatos.add(Nacionalidad);
216 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

15
16 MiMarco.add(EntradaDeDatos);
17 MiMarco.setSize(500,130);
18 MiMarco.setTitle("Ventana con campos de texto");
19 MiMarco.setVisible(true);
20 }
21 }

En el resultado de la clase CampoDeTexto1 se puede observar que los


campos de texto no incluyen un literal que indique lo que hay que introducir, por lo
que habitualmente se utilizan etiquetas antes de los campos de texto para realizar
esta función. Por otra parte, la alineación CENTER que por defecto presenta la
disposición FlowLayout no siempre es adecuada. El siguiente ejemplo
(CampoDeTexto2) resuelve ambas situaciones.

En la clase CampoDeTexto2 se crea el panel EntradaDeDatos como


GridLayout de 3 x 1, es decir, con 3 celdas verticales (línea 7). En cada una de estas
celdas se añade un panel con disposición FlowLayout y sus componentes alineados a
la izquierda (líneas 8, 9 y 10).

MiMarco

PanelNombre

PanelApellidos
EntradaDeDatos
PanelNacionalidad

En PanelNombre se añaden (líneas 22 y 23) la EtiquetaNombre y el


CampoNombre, definidos en las líneas 15 y 12; en PanelApellidos se hace lo mismo
con EtiquetaApellidos y CampoApellidos; en PanelNacionalidad se realiza el mismo
proceso. El resultado esperado se puede observar en la ventana situada tras el
código.
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 7: INTERFAZ GRÁFICO DE USUARIO 217

1 import java.awt.*;
2
3 public class CampoDeTexto2 {
4
5 public static void main(String[] args) {
6 Frame MiMarco = new Frame();
7 Panel EntradaDeDatos = new Panel(new GridLayout(3,1));
8 Panel PanelNombre = new Panel(new
FlowLayout(FlowLayout.LEFT));
9 Panel PanelApellidos = new Panel(new
FlowLayout(FlowLayout.LEFT));
10 Panel PanelNacionalidad = new Panel(new
FlowLayout(FlowLayout.LEFT));
11
12 TextField CampoNombre = new TextField(12);
13 TextField CampoApellidos = new TextField(50);
14 TextField CampoNacionalidad = new
TextField("Española",12);
15 Label EtiquetaNombre = new Label("Nombre",Label.LEFT);
16 Label EtiquetaApellidos = new
Label("Apellidos",Label.LEFT);
17 Label EtiquetaNacionalidad = new
Label("Nacionalidad",Label.LEFT);
18
19 EntradaDeDatos.add(PanelNombre);
20 EntradaDeDatos.add(PanelApellidos);
21 EntradaDeDatos.add(PanelNacionalidad);
22 PanelNombre.add(EtiquetaNombre);
23 PanelNombre.add(CampoNombre);
24 PanelApellidos.add(EtiquetaApellidos);
25 PanelApellidos.add(CampoApellidos);
26 PanelNacionalidad.add(EtiquetaNacionalidad);
27 PanelNacionalidad.add(CampoNacionalidad);
28
29 MiMarco.add(EntradaDeDatos);
30 MiMarco.setSize(500,130);
31 MiMarco.setTitle("Ventana con campos de texto
y etiquetas");
32 MiMarco.setVisible(true);
33 }
34 }
218 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

7.3.4 Área de texto (TextArea)

El objeto área de texto es una región que representa texto en una o varias
líneas. Se puede configurar para que su contenido se pueda editar o bien sea de solo
lectura.

Su constructor más complejo permite definir el texto inicial, el número de


filas y columnas (en ese orden) que presentará y la posibilidad de incluir barras de
desplazamiento vertical y horizontal para desplazarse a lo largo y ancho de los
textos:
TextArea (String TextoInicial, int Filas, int Columnas, int
BarrasDeDesplazamiento)

Donde BarrasDeDesplazamiento puede tomar los valores:


SCROLLBARS_BOTH Barras de desplazamiento vertical y
horizontal
SCROLLBARS_NONE Sin barras de desplazamiento
SCROLLBARS_HORIZONTAL_ONLY Barra de desplazamiento horizontal
SCROLLBARS_VERTICAL_ONLY Barra de desplazamiento vertical

Existen constructores más simples (incluido el constructor sin argumentos).


Para complementarlos se dispone de los métodos:
setRows (int Filas) Establece el número de filas del área de texto
setColumns (int Columnas) Establece el número de columnas del área de texto
append (String Texto) Añade un texto

En el ejemplo AreaDeTexto , en la línea 11 se define una instancia


Comentarios de un área de texto con el literal inicial “La tarta”, dimensión 3 filas
por 20 columnas y barra vertical de desplazamiento. Las líneas 12 y 13 actúan sobre
los métodos append e insert (a partir de la posición indicada) de la clase TextArea.

1 import java.awt.*;
2
3 public class AreaDeTexto {
4
5 public static void main(String[] args) {
6 int FILAS = 3;
7 int COLUMNAS = 20 ;
8
9 Frame MiMarco = new Frame();
10 Panel MiPanel = new Panel();
11 TextArea Comentarios = new TextArea("La
tarta",FILAS,COLUMNAS,
TextArea.SCROLLBARS_VERTICAL_ONLY);
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 7: INTERFAZ GRÁFICO DE USUARIO 219

12 Comentarios.append(" de chocolate estaba buena");


13 Comentarios.insert(" muy", 28);
14
15 MiPanel.add(Comentarios);
16 MiMarco.add(MiPanel);
17 MiMarco.setSize(200,100);
18 MiMarco.setTitle("Ventana con área de texto");
19 MiMarco.setVisible(true);
20 }
21 }

7.3.5 Fuentes (Font)

Las fuentes no son componentes; son clases que heredan de forma directa de
Object (la clase inicial de la jerarquía). El conocimiento de la clase Font es
importante debido a que nos permite variar el aspecto de los textos involucrados en
los componentes que estamos estudiando. Podemos cambiar el tamaño y el tipo de
letra de los caracteres contenidos en una etiqueta, un campo de texto, un área de
texto, etc.

Su constructor más utilizado es:


Font (String Nombre, int Estilo, int Tamaño)

El nombre, que puede ser lógico o físico, indica el tipo de letra con el que se
visualizará el texto (Serif , SansSerif, Monospaced...). El estilo se refiere a letra
itálica, negrilla, normal o alguna combinación de ellas: Font.ITALIC, Font.BOLD,
Font.PLAIN.

La clase de ejemplo Fuente ilustra el modo con el que se puede utilizar el


objeto Font. En las líneas 7 y 8 se instancian dos tipos de letras (de fuentes) a los
que denominamos UnaFuente y OtraFuente , teniendo la segunda un tamaño
superior a la primera (40 frente a 20).
220 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

Las líneas 14 y 17 establecen el tipo de fuente de los objetos (las etiquetas


HolaAmigo1 y HolaAmigo2) a los que se aplica el método setFont, que pertenece a
la clase Component. Obsérvese que la mayor parte de las clases de AWT que
estamos estudiando son subclases de Component y por lo tanto heredan su método
setFont. Esta es la clave que nos permite aplicar fuentes a componentes. Lo mismo
ocurre con el objeto Color y los métodos setForeground (líneas 15 y 18) y
setBackground.

1 import java.awt.*;
2
3 public class Fuente {
4
5 public static void main(String[] args) {
6 Frame MiMarco = new Frame();
7 Font UnaFuente = new Font("SansSerif",Font.BOLD,20);
8 Font OtraFuente = new Font("Serif",Font.ITALIC,40);
9
10 Panel Sencillo = new Panel();
11 Label HolaAmigo1 = new Label("Hola amigo");
12 Label HolaAmigo2 = new Label("Hola amigo");
13
14 HolaAmigo1.setFont(UnaFuente);
15 HolaAmigo1.setForeground(Color.red);
16
17 HolaAmigo2.setFont(OtraFuente);
18 HolaAmigo2.setForeground(Color.orange);
19
20 Sencillo.add(HolaAmigo1);
21 Sencillo.add(HolaAmigo2);
22
23 MiMarco.add(Sencillo);
24 MiMarco.setSize(500,130);
25 MiMarco.setTitle("Ventana con etiquetas y fuentes");
26 MiMarco.setVisible(true);
27 }
28 }
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 7: INTERFAZ GRÁFICO DE USUARIO 221

7.4 CAJAS DE VERIFICACIÓN, BOTONES DE RADIO Y


LISTAS

7.4.1 Introducción

Entre los componentes habituales en el diseño de interfaces gráficos de


usuario (GUI) se encuentran los botones, etiquetas, campos de texto y áreas de texto,
todos ellos previamente estudiados. Ahora nos centraremos en las cajas de
verificación (Checkbox), los botones de radio (CheckboxGroup), las listas (List) y
las listas desplegables (Choice).

El siguiente diagrama sitúa todas estas clases en la jerarquía que proporciona


Java:
Object

Font Component CheckboxGroup

Button Label TextComponent Checkbox List Choice

TextField TextArea

7.4.2 Cajas de verificación (Checkbox)

Una caja de verificación es un componente básico que se puede encontrar en


dos posibles estados: activado (true) y desactivado (false). Cuando se pulsa en una
caja de verificación se conmuta de estado.

Los constructores más simples de la clase Checkbox son:


Checkbox ()
Checkbox (String Etiqueta)
Checkbox (String Etiqueta, boolean Estado)

Entre los métodos existentes tenemos:


222 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

setLabel (String Etiqueta)


setState (boolean Estado)

De esta manera podemos definir e instanciar cajas de verificación de


diversas formas:
Checkbox MayorDeEdad = new Checkbox (“Mayor de edad”, true);

Checkbox MayorDeEdad = new Checkbox ();


MayorDeEdad.setLabel (“Mayor de edad”);
MayorDeEdad.setState (“true”);

En la clase CajaDeVerificación se instancian varios componentes de tipo


Checkbox, añadiéndolos a un panel Sencillo con disposición GridLayout 6 x 1. En la
ventana obtenida se puede observar como los componentes instanciados con estado
true aparecen inicialmente marcados.

1 import java.awt.*;
2
3 public class CajaDeVerificacion {
4
5 public static void main(String[] args) {
6 Frame MiMarco = new Frame();
7 Panel Sencillo = new Panel(new GridLayout(6,1));
8
9 Checkbox AireAcondicionado = new Checkbox("Aire
acondicionado",true);
10 Checkbox FarosXenon = new Checkbox("Faros de
xenon",false);
11 Checkbox PinturaMetalizada = new Checkbox("Pintura
metalizada",true);
12 Checkbox LlantasAleacion = new Checkbox("Llantas de
aleación",false);
13 Checkbox TapiceriaCuero = new Checkbox("Tapicería de
cuero",false);
14 Checkbox FarosAntiniebla = new Checkbox("Faros
antiniebla",false);
15
16 Sencillo.add(AireAcondicionado);
17 Sencillo.add(FarosXenon);
18 Sencillo.add(PinturaMetalizada);
19 Sencillo.add(LlantasAleacion);
20 Sencillo.add(TapiceriaCuero);
21 Sencillo.add(FarosAntiniebla);
22
23 MiMarco.add(Sencillo);
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 7: INTERFAZ GRÁFICO DE USUARIO 223

24 MiMarco.setSize(200,200);
25 MiMarco.setTitle("Ventana con cajas de verificación");
26 MiMarco.setVisible(true);
27 }
28 }

7.4.3 Botones de radio (CheckboxGroup)

La clase CheckboxGroup se utiliza para agrupar un conjunto de cajas de


verificación. CheckboxGroup no es un componente (no deriva de la clase
Component), es una subclase de Object. Cuando definimos un grupo de botones de
radio utilizamos varias instancias del componente Checkbox asociadas a un objeto
CheckboxGroup.

Los botones de radio presentan la característica de que la activación de un


elemento implica directamente la desactivación automática de los demás botones del
grupo, es decir no puede haber dos o más botones activos al mismo tiempo.

Sólo existe el constructor sin argumentos del objeto CheckboxGroup:


CheckboxGroup().

Cada caja de verificación se puede asociar a un CheckboxGroup de dos


posibles maneras:
• Instanciando la caja de verificación con uno de los siguientes constructores:
Checkbox (String Etiqueta, boolean Estado, CheckboxGroup
GrupoDeBotonesDeRadio)
Checkbox (String Etiqueta, CheckboxGroup GrupoDeBotonesDeRadio,
boolean Estado)
224 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

• Utilizando el método setCheckboxGroup (CheckboxGroup


GrupoDeBotonesDeRadio) sobre la caja de verificación.

El siguiente ejemplo (BotonesDeRadio1) instancia un grupo de botones de


radio Colores en la línea 8. En las líneas 10 a 15 se crean seis cajas de verificación
asociadas al grupo Colores. Estas 6 cajas de verificación se comportarán como un
grupo de botones de radio (con selección excluyente).

1 import java.awt.*;
2
3 public class BotonesDeRadio1 {
4
5 public static void main(String[] args) {
6 Frame MiMarco = new Frame();
7 Panel Sencillo = new Panel(new GridLayout(6,1));
8 CheckboxGroup Colores = new CheckboxGroup();
9
10 Sencillo.add(new Checkbox("Rojo",false,Colores));
11 Sencillo.add(new Checkbox("Azul",false,Colores));
12 Sencillo.add(new Checkbox("Verde",false,Colores));
13 Sencillo.add(new Checkbox("Amarillo",false,Colores));
14 Sencillo.add(new Checkbox("Negro",false,Colores));
15 Sencillo.add(new Checkbox("Gris",false,Colores));
16
17 MiMarco.add(Sencillo);
18 MiMarco.setSize(200,200);
19 MiMarco.setTitle("Ventana con botones de radio");
20 MiMarco.setVisible(true);
21 }
22 }

Vamos a combinar el ejemplo anterior con el uso de fuentes (Font), de


manera que las etiquetas de las cajas de verificación presenten un tamaño mayor y el
color que representan. Los cambios más significativos respecto a nuestro ejemplo
base son:
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 7: INTERFAZ GRÁFICO DE USUARIO 225

• En la línea 25 se crea una fuente MiFuente con tipo de letra SansSerif ,


plana (ni itálica ni negrilla) y de tamaño 25.
• En la línea 5 se define un método EstableceVisualizacion con
argumentos: un objeto Checkbox, un objeto Color y un objeto Font.
En la línea 8 se establece como fuente del objeto Checkbox el objeto
Font que se pasa como parámetro. Análogamente, en la línea 9, se
establece como color del objeto Checkbox el objeto Color que se pasa
como parámetro.
• Las líneas 27 a 32 invocan al método EstableceVisualizacion con
cada una de las cajas de verificación instanciadas en las líneas 18 a
23. En todos los casos se utiliza la misma fuente (MiFuente ).

1 import java.awt.*;
2
3 public class BotonesDeRadio2 {
4
5 private static void EstableceVisualizacion(Checkbox
6 Elemento, Color ColorSeleccionado,
7 Font FuenteSeleccionada){
8 Elemento.setFont(FuenteSeleccionada);
9 Elemento.setForeground(ColorSeleccionado);
10 }
11
12
13 public static void main(String[] args) {
14 Frame MiMarco = new Frame();
15 Panel Sencillo = new Panel(new GridLayout(6,1));
16 CheckboxGroup Colores = new CheckboxGroup();
17
18 Checkbox Rojo = new Checkbox("Rojo",false,Colores);
19 Checkbox Azul = new Checkbox("Azul",false,Colores);
20 Checkbox Verde = new
Checkbox("Verde",false,Colores);
21 Checkbox Amarillo = new
Checkbox("Amarillo",false,Colores);
22 Checkbox Negro = new
Checkbox("Negro",false,Colores);
23 Checkbox Gris = new Checkbox("Gris",false,Colores);
24
25 Font MiFuente = new Font("SansSerif",Font.PLAIN,25);
26
27 EstableceVisualizacion(Rojo,Color.red,MiFuente);
28 EstableceVisualizacion(Azul,Color.blue,MiFuente);
29 EstableceVisualizacion(Verde,Color.green,MiFuente);
30 EstableceVisualizacion(Amarillo,Color.yellow,MiFuente);
31 EstableceVisualizacion(Negro,Color.black,MiFuente);
32 EstableceVisualizacion(Gris,Color.gray,MiFuente);
226 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

33
34 Sencillo.add(Rojo);
35 Sencillo.add(Azul);
36 Sencillo.add(Verde);
37 Sencillo.add(Amarillo);
38 Sencillo.add(Negro);
39 Sencillo.add(Gris);
40
41 MiMarco.add(Sencillo);
42 MiMarco.setSize(200,200);
43 MiMarco.setTitle("Ventana con botones de radio");
44 MiMarco.setVisible(true);
45 }
46 }

7.4.4 Lista (List)

El componente lista le ofrece al usuario la posibilidad de desplazarse por


una secuencia de elementos de texto. Este componente se puede configurar de
manera que se puedan realizar selecciones múltiples o bien que sólo se pueda
seleccionar un único elemento de la lista.

Los constructores que admite esta clase son:


List ()
List (int NumeroDeFilas)
List (int NumeroDeFilas, boolean SeleccionMultiple)

Haciendo uso del segundo constructor podemos configurar el tamaño de la


lista para que se visualicen NumeroDeFilas elementos de texto. Si hay más
elementos en la lista se puede acceder a ellos por medio de una barra de
desplazamiento vertical. Con el tercer constructor indicamos si deseamos permitir la
selección múltiple de elementos.
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 7: INTERFAZ GRÁFICO DE USUARIO 227

Podemos variar en tiempo de ejecución el modo de selección de elementos,


utilizando el método setMultipleMode(boolean SeleccionMultiple) de la clase List.

La manera de incorporar elementos de texto en una lista es mediante uno de


los siguie ntes métodos:
add (String Elemento)
add (String Elemento, int Posicion)

Con el primer método se añade el elemento especificado al final de la lista;


si se desea insertarlo en una posición determinada se utiliza el segundo método.

En el siguiente ejemplo (Lista1) se instancia la lista Islas con 5 filas y sin


posibilidad de selección múltiple (línea 8). Las líneas 10 a 16 añaden elementos a la
lista.
1 import java.awt.*;
2
3 public class Lista1 {
4
5 public static void main(String[] args) {
6 Frame MiMarco = new Frame();
7 Panel MiPanel = new Panel();
8 List Islas = new List(5,false);
9
10 Islas.add("Tenerife");
11 Islas.add("Lanzarote");
12 Islas.add("Gran Canaria");
13 Islas.add("Hierro");
14 Islas.add("La Gomera");
15 Islas.add("Fuerteventura");
16 Islas.add("La Palma");
17
18 MiPanel.add(Islas);
19 MiMarco.add(MiPanel);
20 MiMarco.setSize(200,200);
21 MiMarco.setTitle("Ventana con lista");
22 MiMarco.setVisible(true);
23 }
24 }
228 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

La clase Lista2 parte del ejemplo anterior. En este caso se utiliza un


constructor más sencillo (línea 8) y se complementa haciendo uso del método
setMultipleMode (línea 9), con el que se indica que se permite selección múltiple
(necesario, por ejemplo si existe la posibilidad de paquetes turísticos combinados
entre varias islas).

Las líneas 19 y 20 utilizan el método select, que permite seleccionar por


programa el elemento indicado por la posición que se le pasa como parámetro. En
este caso se han seleccionado “Gran Canaria” y “Lanzarote”. Obsérvese que la
posición de los elementos se numera a partir de cero.

1 import java.awt.*;
2
3 public class Lista2 {
4
5 public static void main(String[] args) {
6 Frame MiMarco = new Frame();
7 Panel MiPanel = new Panel();
8 List Islas = new List(3);
9 Islas.setMultipleMode(true);
10
11 Islas.add("Fuerteventura");
12 Islas.add("La Gomera");
13 Islas.add("Gran Canaria");
14 Islas.add("Hierro");
15 Islas.add("Lanzarote");
16 Islas.add("Tenerife");
17 Islas.add("La Palma",5);
18
19 Islas.select(2);
20 Islas.select(4);
21
22 MiPanel.add(Islas);
23 MiMarco.add(MiPanel);
24 MiMarco.setSize(200,200);
25 MiMarco.setTitle("Ventana con lista de selección
múltiple");
26 MiMarco.setVisible(true);
27 }
28 }
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 7: INTERFAZ GRÁFICO DE USUARIO 229

7.4.5 Lista desplegable (Choice)

La clase Choice presenta un menú desplegable de posibilidades. La


posibilidad seleccionada aparece como título del menú. No existe la posibilidad de
realizar selecciones múltiples.

Esta clase sólo admite el constructor sin parámetros: Choice() y sus


elementos se aña den mediante el método add(String Elemento).

El ejemplo ListaDesplegable1 ilustra la manera de hacer un uso sencillo del


componente Choice: en la línea 8 se instancia la lista desplegable Ciudades y en las
líneas 10 a 15 se añaden los elementos deseados.

1 import java.awt.*;
2
3 public class ListaDesplegable1 {
4
5 public static void main(String[] args) {
6 Frame MiMarco = new Frame();
7 Panel MiPanel = new Panel();
8 Choice Ciudades = new Choice();
9
10 Ciudades.add("Alicante");
11 Ciudades.add("Avila");
12 Ciudades.add("Granada");
13 Ciudades.add("Segovia");
14 Ciudades.add("Sevilla");
15 Ciudades.add("Toledo");
16
17 MiPanel.add(Ciudades);
18 MiMarco.add(MiPanel);
19 MiMarco.setSize(200,200);
20 MiMarco.setTitle("Ventana con lista desplegable");
21 MiMarco.setVisible(true);
22 }
23 }
230 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

Si en el ejemplo anterior introducimos entre las líneas 15 y 17 las siguientes


instrucciones:
Ciudades.insert("Madrid",3); // inserta “Madrid” en la cuarta posición de la lista
// desplegable “Ciudades”
Ciudades.select("Segovia"); // selecciona el elemento de texto “Segovia”

Obtendremos el resultado:

7.5 DISEÑO DE FORMULARIOS

7.5.1 Diseño del interfaz gráfico deseado

Supongamos que deseamos crear una pantalla de entrada de datos que nos
permita recibir información personal de usuarios, por ejemplo contribuyentes
fiscales residentes en España.

En la parte superior de la ventana deseamos preguntar el nombre y los


apellidos, así como la nacionalidad, que por defecto se rellenará como española,
puesto que sabemos que la gran mayoría de los contribuyentes poseen dicha
nacionalidad. En la zona izquierda de la ventana se consulta el estado civil y en la
derecha la ciudad de residencia; la zona central se deja libre para incluir el logotipo
del ministerio de hacienda, de lo que se encargará un equipo de diseño gráfico, y la
inferior contendrá el botón de enviar/grabar datos. El resultado esperado tendrá un
aspecto similar a la ventana siguiente:
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 7: INTERFAZ GRÁFICO DE USUARIO 231

Antes de la programación deberíamos diseñar el formulario en base a


contenedores (paneles) y disposiciones (layouts). En primer lugar necesitamos una
ventana, que conseguiremos mediante un objeto Frame al que podemos denominar,
igual que en los ejemplos anteriores, MiMarco. A MiMarco le añadiremos un panel
“Formulario” con disposición BorderLayout, que encaja perfectamente con el diseño
pedido.
Región norte
MiMarco

Formulario
Región centro
Región este
Región sur

El grupo de datos nombre, apellidos y nacionalidad requieren, cada uno, de


una etiqueta y un campo de texto dispuestos en secuencia horizontal (FlowLayout) y
alineados a la izquierda. A su vez estos tres grupos de componentes se deben situar
en secuencia vertical, para lo que resulta adecuado un GridLayout 3 x 1.

EtiquetaNombre CampoNombre

PanelDatosPersonales PanelNombre

PanelApellidos

PanelDireccion

EtiquetaDireccion CampoDireccion
232 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

Los botones de radio que sirven para seleccionar el estado civil se pueden
añadir a un único panel “PanelEstadoCivil” asociado a un GridLayout 5 x 1:

PanelEstadoCivil Soltero
Casado
Separado
Divorciado
Otros

El resto de los componentes no requiere de una disposición especial,


pudiendo añadirse directamente a sus regiones correspondientes en el panel
Formulario .

7.5.2 Implementación en una sola clase

Si pensamos que los elementos de nuestro formulario no se van a poder


reutilizar en otras ventanas de GUI podemos implementar todo el diseño en una sola
clase, el siguiente ejemplo (Formulario1) codifica el diseño realizado:

En primer lugar se instancian el marco y los paneles que hemos previsto


(líneas 5 a 10), como variables globales a la clase, privadas a la misma y estáticas
para poder ser usadas desde el método estático main.

En el método principal (main), en primer lugar se hacen las llamadas a los


métodos privados PreparaDatosPersonales, PreparaEstadoCivil y
PreparaProvincia (líneas 66 a 68), que añaden los componentes necesarios a los
paneles PanelDatosPersonales, PanelEstadoCivil y PanelProvincia . Estos métodos
podrían haber admitido un panel como parámetro, evitándose las variables globales
y estáticas, que en este caso se habrían declarado en el propio método main.

En las líneas 70 a 72 se asignan los colores deseados a cada uno de los


paneles principales. En las líneas 74 a 77 se añaden los paneles principales a las
regiones deseadas en el panel Formulario . El código de cada uno de los métodos
privados se obtiene de manera directa sin más que seguir los gráficos diseñados en el
apartado anterior.

1 import java.awt.*;
2
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 7: INTERFAZ GRÁFICO DE USUARIO 233

3 public class Formulario1 {


4
5 private static Frame MiMarco = new Frame();
6 private static Panel Formulario = new Panel(new
BorderLayout());
7 private static Panel PanelDatosPersonales =
8 new Panel(new
GridLayout(3,1));
9 private static Panel PanelEstadoCivil = new Panel(new
GridLayout(5,1));
10 private static Panel PanelProvincia = new Panel();
11
12 private static void PreparaDatosPersonales(){
13 Panel PanelNombre = new Panel(new
FlowLayout(FlowLayout.LEFT));
14 Panel PanelApellidos = new Panel(new
FlowLayout(FlowLayout.LEFT));
15 Panel PanelDireccion = new Panel(new
FlowLayout(FlowLayout.LEFT));
16
17 TextField CampoNombre = new TextField(12);
18 TextField CampoApellidos = new TextField(50);
19 TextField CampoDireccion = new TextField(12);
20 Label EtiquetaNombre = new Label("Nombre",Label.LEFT);
21 Label EtiquetaApellidos = new
Label("Apellidos",Label.LEFT);
22 Label EtiquetaDireccion = new
Label("Direccion",Label.LEFT);
23
24 PanelDatosPersonales.add(PanelNombre);
25 PanelDatosPersonales.add(PanelApellidos);
26 PanelDatosPersonales.add(PanelDireccion);
27 PanelNombre.add(EtiquetaNombre);
28 PanelNombre.add(CampoNombre);
29 PanelApellidos.add(EtiquetaApellidos);
30 PanelApellidos.add(CampoApellidos);
31 PanelDireccion.add(EtiquetaDireccion);
32 PanelDireccion.add(CampoDireccion);
33 }
34
35 private static void PreparaEstadoCivil() {
36 CheckboxGroup EstadoCivil = new CheckboxGroup();
37
38 Checkbox Soltero = new
Checkbox("Soltero",false,EstadoCivil);
39 Checkbox Casado = new
Checkbox("Casado",false,EstadoCivil);
40 Checkbox Separado = new
234 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

Checkbox("Separado",false,EstadoCivil);
41 Checkbox Divorciado = new
Checkbox("Divorciado",false,EstadoCivil);
42 Checkbox Otros = new
Checkbox("Otros",false,EstadoCivil);
43
44 PanelEstadoCivil.add(Soltero);
45 PanelEstadoCivil.add(Casado);
46 PanelEstadoCivil.add(Separado);
47 PanelEstadoCivil.add(Divorciado);
48 PanelEstadoCivil.add(Otros);
49 }
50
51 private static void PreparaProvincia() {
52 Choice Ciudades = new Choice();
53
54 Ciudades.add("Alicante");
55 Ciudades.add("Avila");
56 Ciudades.add("Granada");
57 Ciudades.add("Madrid");
58 Ciudades.add("Segovia");
59 Ciudades.add("Sevilla");
60 Ciudades.add("Toledo");
61
62 PanelProvincia.add(Ciudades);
63 }
64
65 public static void main(String[] args) {
66 PreparaDatosPersonales();
67 PreparaEstadoCivil();
68 PreparaProvincia();
69
70 PanelDatosPersonales.setBackground(Color.orange);
71 PanelEstadoCivil.setBackground(Color.yellow);
72 PanelProvincia.setBackground(Color.green);
73
74 Formulario.add(PanelDatosPersonales,
BorderLayout.NORTH);
75 Formulario.add(PanelEstadoCivil,BorderLayout.WEST);
76 Formulario.add(PanelProvincia,BorderLayout.EAST);
77 Formulario.add(new
Button("Enviar"),BorderLayout.SOUTH);
78
79 MiMarco.add(Formulario);
80 MiMarco.setSize(600,250);
81 MiMarco.setTitle("Formulario");
82 MiMarco.setVisible(true);
83 } }
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 7: INTERFAZ GRÁFICO DE USUARIO 235

7.5.3 Implementación en varias clases

El formulario diseñado contiene secciones que posiblemente nos interese


reutilizar en otras ventanas de interfaz gráfico, por ejemplo, el panel que permite
introducir el nombre, apellidos y nacionalidad con toda probabilidad podrá ser
utilizado de nuevo en la misma aplicación o bien en otras diferentes.

Para facilitar la reutilización al máximo, crearemos una clase por cada uno
de los paneles principales y después crearemos una o varias clases que hagan uso de
las anteriores. El esquema que seguiremos es el siguiente:

Formulario2DatosPersonales Formulario2EstadoCivil Formulario2Provincia

Formulario2main Formulario2main
1 2

En primer lugar presentamos la clase Formulario2DatosPersonales, en


donde creamos la propiedad privada PanelDatosPersonales (línea 5). Esta propiedad
es accesible a través del método público DamePanel() situado en la línea 18.

La clase tiene un constructor con argumento de tipo Color (línea 7), lo que
nos permite asignar el color seleccionado al panel PanelDatosPersonales (línea 11).
El segundo constructor de la clase (línea 14) no presenta parámetros; internamente
llama al primero con el parámetro de color blanco.

El contenido del constructor se obtie ne directamente del método


PreparaDatosPersonales().

1 import java.awt.*;
2
3 public class Formulario2DatosPersonales {
4
5 private Panel PanelDatosPersonales = new Panel(new
GridLayout(3,1));
6
7 Formulario2DatosPersonales(Color ColorDelPanel){
8
9 // *** Codigo del metodo PreparaDatosPersonales() ***
10
11 PanelDatosPersonales.setBackground(ColorDelPanel);
236 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

12 }
13
14 Formulario2DatosPersonales(){
15 this(Color.white);
16 }
17
18 Panel DamePanel() {
19 return PanelDatosPersonales;
20 }
21
22 }

La clase Formulario2EstadoCivil se diseña con los mismos principios que


Formulario2DatosPersonales. A continuación se muestra su código abreviado:

1 import java.awt.*;
2
3 public class Formulario2EstadoCivil {
4
5 private Panel PanelEstadoCivil = new Panel(new
GridLayout(5,1));
6
7 Formulario2EstadoCivil(Color ColorDelPanel) {
8
9 // *** Codigo del metodo PreparaEstadoCivil() ***
10
11 PanelEstadoCivil.setBackground(ColorDelPanel);
12 }
13
14 Formulario2EstadoCivil(){
15 this(Color.white);
16 }
17
18 Panel DamePanel() {
19 return PanelEstadoCivil;
20 }
21
22 }

Finalmente se presenta el código resumido de la clase


Formulario2Provincia:

1 import java.awt.*;
2
3 public class Formulario2Provincia {
4
5 private Panel PanelProvincia = new Panel();
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 7: INTERFAZ GRÁFICO DE USUARIO 237

6
7 Formulario2Provincia(Color ColorDelPanel){
8
9 // *** Codigo del metodo PreparaProvincia() ***
10
11 PanelProvincia.setBackground(ColorDelPanel);
12 }
13
14 Formulario2Provincia() {
15 this(Color.white);
16 }
17
18 Panel DamePanel() {
19 return PanelProvincia;
20 }
21
22 }

La clase Formulario2main1 presenta exactamente el mismo resultado que la


clase Formulario1, por ello no representamos de nuevo la ventana de resultado.
238 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

En primer lugar se crean las propiedades de clase MiMarco y Formulario


(también se podrían haber creado como variables de instancia dentro del método
main), después, en el método main, se crean sendas instancias de los objetos
Formulario2DatosPersonales, Formulario2EstadoCivil y Formulario2Provincia
(líneas 9 a 13), cada uno con el color deseado.

En este momento se encuentran preparados (como estructuras de datos) los


paneles PanelDatosPersonales, PanelEstadoCivil y PanelProvincia, cada uno como
propiedad privada de su objeto correspondiente (DatosPersonales, EstadoCivil y
Provincia).

En las líneas 15 a 17 se obtienen los paneles PanelDatosPersonales,


PanelEstadoCivil y PanelProvincia invocando al método DamePanel() en las
instancias DatosPersonales, EstadoCivil y Provincia . En estas mismas líneas se
añaden los paneles obtenidos a las regiones deseadas del panel Formulario .

1 import java.awt.*;
2
3 public class Formulario2main1 {
4
5 private static Frame MiMarco = new Frame();
6 private static Panel Formulario = new Panel(new
BorderLayout());
7
8 public static void main(String[] args) {
9 Formulario2DatosPersonales DatosPersonales =
10 new Formulario2DatosPersonales(Color.orange);
11 Formulario2EstadoCivil EstadoCivil =
12 new Formulario2EstadoCivil(Color.yellow);
13 Formulario2Provincia Provincia = new
Formulario2Provincia(Color.green);
14
15 Formulario.add(DatosPersonales.DamePanel(),
BorderLayout.NORTH);
16 Formulario.add(EstadoCivil.DamePanel(),
BorderLayout.WEST);
17 Formulario.add(Provincia.DamePanel(),
BorderLayout.EAST);
18 Formulario.add(new Button("Enviar"),
BorderLayout.SOUTH);
19
20 MiMarco.add(Formulario);
21 MiMarco.setSize(600,250);
22 MiMarco.setTitle("Formulario");
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 7: INTERFAZ GRÁFICO DE USUARIO 239

23 MiMarco.setVisible(true);
24 }
25 }

La clase Formulario2main2 hace uso de los objetos


Formulario2DatosPersonales, Formulario 2EstadoCivil y Formulario2Provincia
para crear un interfaz de usuario con la misma funcionalidad que la del ejemplo
anterior, pero con una disposición diferente de los componentes.

La ventana de resultado se presenta a continuación y la naturaleza del


código es muy similar a la del ejemplo anterior. Al reutilizar las clases básicas
tenemos la posibilidad de cambiar la apariencia final de la ventana de interfaz de
usuario.

1 import java.awt.*;
2
3 public class Formulario2main2 {
4
5 private static Frame MiMarco = new Frame();
6 private static Panel Formulario = new Panel(new
GridLayout(3,1));
7
8 public static void main(String[] args) {
9
10 Panel EstadoCivil_Provincia =
11 new Panel(new FlowLayout(FlowLayout.CENTER));
12 Panel BotonEnviar = new Panel();
13
14 Formulario2DatosPersonales DatosPersonales =
15 new Formulario2DatosPersonales();
16 Formulario2EstadoCivil EstadoCivil =
17 new Formulario2EstadoCivil(Color.yellow);
18 Formulario2Provincia Provincia = new
Formulario2Provincia(Color.yellow);
19
240 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

20 EstadoCivil_Provincia.add(EstadoCivil.DamePanel());
21 EstadoCivil_Provincia.add(Provincia.DamePanel());
22 BotonEnviar.add(new Button("Enviar"));
23
24 Formulario.add(DatosPersonales.DamePanel());
25 Formulario.add(EstadoCivil_Provincia);
26 Formulario.add(BotonEnviar);
27
28 MiMarco.add(Formulario);
29 MiMarco.setSize(600,250);
30 MiMarco.setTitle("Formulario");
31 MiMarco.setVisible(true);
32 }
33 }

Este mismo ejercicio se podría haber resuelto con un enfoque más elegante
desde el punto de vista de programación orientada a objetos: podríamos haber
definido las clases Formulario2DatosPersoanles, Formulario2EstadoCivil y
Formulario2Provincia como subclases de Panel. En ese caso no necesitaríamos
crear instancias del objeto Panel dentro de cada una de las tres clases, ni tampoco
haría falta definir y utilizar el método DimePanel().

7.6 DIÁLOGOS Y MENÚS


7.6.1 Introducción

Los siguie ntes componentes de AWT que vamos a estudiar son los diálogos,
con los que podemos crear ventanas adicionales a la principal, especializadas en
diferentes tipos de interacción con el usuario (por ejemplo selección de un fichero).
También nos centraremos en la manera de crear menús.

Los diálogos los basaremos en las clases Dialog y FileDialog, mientras que
los menús los crearemos con los objetos MenuBar y Menu. El siguiente diagrama
sitúa todas estas clases en la jerarquía que proporciona Java:
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 7: INTERFAZ GRÁFICO DE USUARIO 241

Object java.lang

MenuComponent Component

Container
MenuItem MenuBar

Window java.awt
Menu

Dialog

FileDialog

7.6.2 Diálogo (Dialog)

Un diálogo es una ventana que se usa para recoger información del usuario;
su disposición por defecto es BorderLayout. Para poder crear un diálogo hay que
indicar quién es su propietario (un marco o bien otro diálogo).

Las ventanas de diálogo pueden ser modales o no modales (opción por


defecto). Las ventanas de diálogo modales se caracterizan porque realizan un
bloqueo de las demás ventanas de la aplicación (salvo las que hayan podido ser
creadas por el propio diálogo).

Existen 5 constructores de la clase Dialog, los más utilizados son:


Dialog (Frame Propietario, String Titulo, boolean Modal)
Dialog (Dialog Propietario, String Titulo, boolean Modal)
Dialog (Frame Propietario)
Dialog (Dialog Propietario)

Entre los métodos existentes tenemos:


setTitle (String Titulo)
setModal (boolean Modal)
hide() // oculta el diálogo
show() // muestra el diálogo
setResizable(boolean CambioTamanio) // permite o no el cambio de tamaño de la
// ventana por parte del usuario
242 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

El primer ejemplo del componente diálogo (Dialogo1), en la línea 9


instancia un diálogo Dialogo cuyo propietario es el marco MiMarco, el título
“Ventana de diálogo” y su naturaleza no modal (parámetro a false).

La línea 14 invoca al método show del objeto Dialogo para conseguir que la
ventana se haga visible. El diálogo que se obtiene es una ventana vacía y sin
dimensiones.

1 import java.awt.*;
2
3 public class Dialogo1 {
4
5 public static void main(String[] args) {
6 final boolean NO_MODAL = false;
7 Frame MiMarco = new Frame();
8
9 Dialog Dialogo = new Dialog(MiMarco,"Ventana de
diálogo",NO_MODAL);
10
11 MiMarco.setSize(200,100);
12 MiMarco.setTitle("Ventana con diálogo");
13 MiMarco.setVisible(true);
14 Dialogo.show();
15 }
16 }

En la clase Dialogo2 se crea un diálogo modal de propietario MiMarco


(línea 11). Con el diálogo (Dialogo) instanciado trabajamos de manera análoga a
como lo hemos venido haciendo con los marcos. En el ejemplo se le añade un panel
(línea 12), se le asigna un tamaño (línea 13) y también una posición (línea 14). Al
igual que en el ejemplo anterior, debemos mostrar el componente (línea 19).

1 import java.awt.*;
2
3 public class Dialogo2 {
4
5 public static void main(String[] args) {
6 final boolean MODAL = true;
7 Frame MiMarco = new Frame();
8 Panel MiPanel = new Panel();
9 MiPanel.add(new Label("Un elemento en el diálogo"));
10
11 Dialog Dialogo = new Dialog(MiMarco,"Ventana de
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 7: INTERFAZ GRÁFICO DE USUARIO 243

diálogo",MODAL);
12 Dialogo.add(MiPanel);
13 Dialogo.setSize(250,100);
14 Dialogo.setLocation(new Point(50,80));
15
16 MiMarco.setSize(200,100);
17 MiMarco.setTitle("Ventana con diálogo");
18 MiMarco.setVisible(true);
19 Dialogo.show();
20 }
21 }

7.6.3 Diálogo de carga / almacenamiento de ficheros (FileDialog)

FileDialog es una subclase de Dialog. Como clase especializada de Dialog,


FileDialog gestiona la selección de un fichero entre los sistemas de ficheros
accesibles. Los diálogos de fichero son modales y sus propietarios deben ser marcos.

Los constructores de la clase son:


FileDialog (Frame Propietario)
FileDialog (Frame Propietario, String Titulo)
FileDialog (Frame Propietario, String Titulo, int ModoCargaOGrabacion)

Entre los métodos más utilizados de esta clase están:


getMode() y setMode(int ModoCargaOGrabacion)
getFile() y setFile(String NombreFichero) // para seleccionar por programa el
// fichero (o consultar cuál
// es el fichero que ha sido seleccionado)
getDirectory y setDirectory(String NombreDirectorio) // para seleccionar el
// directorio inicial que
// utilizará el diálogo (o consultarlo)
getFilenameFilter() y setFilenameFilter(FilenameFilter Filtro) // para mostrar
// únicamente los ficheros que pasan el filtro
244 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

En el siguiente ejemplo (DialogoFichero) se crea una instancia Grabar de la


clase FileDialog (línea 8). El diálogo tiene como propietario el marco MiMarco,
como título el texto “Guardar” y como modo SAVE. La línea 9 (comentada) ilustra la
forma de crear un diálogo de fichero en modo LOAD.

En la línea 15 se invoca al método show, perteneciente a la clase Dialog


(superclase de FileDialog).

1 import java.awt.*;
2
3 public class DialogoFichero {
4
5 public static void main(String[] args) {
6 Frame MiMarco = new Frame();
7
8 FileDialog Grabar = new
FileDialog(MiMarco,"Guardar",FileDialog.SAVE);
9 // FileDialog Cargar =
10 new FileDialog(MiMarco,"Cargar",
FileDialog.LOAD);
11
12 MiMarco.setSize(200,100);
13 MiMarco.setTitle("Ventana con diálogo de carga
de fichero");
14 MiMarco.setVisible(true);
15 Grabar.show();
16 }
17 }
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 7: INTERFAZ GRÁFICO DE USUARIO 245

7.6.4 Menús (Menu y MenuBar)

La clase MenuBar se utiliza para dotar de una barra principal de menú a un


marco. A continuación se presenta un marco sin barra de menú y otro con una barra
de menú compuesto de dos menús.

MenuBar solo tiene el constructor sin argumentos. En la línea 8 de la clase


Menus se instancia una barra de menú con identificador MenuPrincipal.

Para añadir una barra de menú a un marco se emplea el método setMenuBar


(no add), tal y como aparece en la línea 24 del ejemplo; setMenuBar es un método
de la clase Frame.

Una vez que disponemos de un objeto barra de menú, podemos añadirle


menús (con el método add), tal y como se realiza en las líneas 21 y 22 del ejemplo.
Veamos primero qué es y como se emplea un menú: un menú es un componente
desplegable que parte de una barra de menús. Su representación gráfica se puede ver
en las ventanas situadas al final del código de la clase Menus.

El constructor más utilizado de la clase menú es:

Menu (String Etiqueta)

El constructor definido crea un menú vacío con la etiqueta de título que se le


pasa como parámetro; en nuestro ejemplo tenemos un menú Fichero con etiqueta
“Fichero” (línea 9) y un menú Transformaciones con etiqueta “Transformación”
(línea 10). La etiqueta representa el texto que se visualiza en la barra de menús.

En las líneas 12 a 19 del ejemplo se añaden (usando el método add) las


diferentes opciones que el usuario puede seleccionar en los menús.

El siguiente esquema muestra la manera en la que se disponen las clases


involucradas, así como los métodos necesarios para asociarlas entre sí.
246 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

setMenuBar (....)

Frame
MenuBar

add (....)
Menu
String o MenuItem
add (....)

1 import java.awt.*;
2
3 public class Menus {
4
5 public static void main(String[] args) {
6 Frame MiMarco = new Frame();
7
8 MenuBar MenuPrincipal = new MenuBar();
9 Menu Fichero = new Menu("Fichero");
10 Menu Transformaciones = new Menu("Transformación");
11
12 Fichero.add("Abrir");
13 Fichero.add("Cerrar");
14 Fichero.add("Imprimir");
15 Fichero.add("Guardar");
16
17 Transformaciones.add("Rotación");
18 Transformaciones.add("Traslación");
19 Transformaciones.add("Cambio de escala");
20
21 MenuPrincipal.add(Fichero);
22 MenuPrincipal.add(Transformaciones);
23
24 MiMarco.setMenuBar(MenuPrincipal);
25 MiMarco.setSize(200,100);
26 MiMarco.setTitle("Ventana con menú");
27 MiMarco.setVisible(true);
28 }
29 }
CAPÍTULO 8

EVENTOS

8.1 MECANISMO DE EVENTOS EN JAVA


8.1 Introducción

Java es un lenguaje orientado a objetos, por lo que los objetos (las clases)
son los elementos más importantes en el diseño y desarrollo de una aplicación.
También podemos afirmar que Java es un lenguaje orientado a eventos, puesto que
nos proporciona todos los elementos necesarios para definirlos y utilizarlos; los
eventos son objetos fundamentales en el desarrollo de la mayor parte de las
aplicaciones.

Los eventos de Java proporcionan un mecanismo adecuado para tratar


situaciones que habitualmente se producen de manera asíncrona a la ejecución del
programa; situaciones normalmente producidas desde el exterior de la aplicación,
por ejemplo cuando se pulsa una tecla. Cuando llega un evento, en ocasiones nos
interesa tratarlo (por ejemplo la pulsación de un número en una aplicación
calculadora) y otras veces no deseamos tratar el evento con ninguna acción (por
ejemplo cuando el usuario pulsa con el ratón sobre un texto al que no hemos
asignado ninguna información complementaria).

Para afianzar el significado de los eventos resulta conveniente enumerar


algunos de los más comunes que se pueden producir en la ejecución de una
aplicación que proporcione interfaz gráfico de usuario (GUI):
• Pulsación de ratón
• El puntero del ratón “entra en” (se sitúa sobre) un componente gráfico (por
ejemplo sobre un botón o una etiqueta)
• El puntero del ratón “sale de” un componente gráfico
• Se pulsa una tecla
• Se cierra una ventana
248 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

• etc.

De esta manera, a modo de ejemplo, podemos desear que en una aplicación


que consta de GUI, cuando se pulse en un botón Calcular se ejecute un método
CalculoEstructura que determina el diámetro mínimo que debe tener una columna
de hormigón para soportar una estructura determinada. En este caso, cuando el
usuario de la aplicación pulse en el botón Calcular, esperamos que se produzca un
evento y que este evento me permita invocar al método CalculoEstructura.

8.2 Arquitectura de los eventos


Los eventos habitualmente se originan desde el exterior de la aplicación,
producidos por los usuarios que hacen uso de la misma. El simple movimiento
del ratón genera multitud de eventos que pueden ser tratados por nuestros
programas Java. Veamos con mayor detalle como se pasa de las acciones de los
usuarios a las ejecuciones que deseamos que se produzcan en los programas:
1 El usuario interacciona con la aplicación por medio de dispositivos de
entrada/salida (ratón, teclado, etc.)
2 Los dispositivos de entrada/salida generan señales eléctricas (que
codifican información) que son recogidas por los controladores
(habitualmente placas controladoras de puerto serie, paralelo, USB, etc.)
3 Los drivers (manejadores) de cada dispositivo recogen las señales
eléctricas, las codifican como datos y traspasan esta información al
procesador (CPU). En concreto activan los pines de interrupción
(excepción) de la CPU.
4 La CPU habitualmente deja de ejecutar la acción en curso (salvo que
esta acción tenga mayor prioridad que la asociada a la interrupción que le
llega) y pasa a ejecutar la rutina de tratamiento de interrupción
(excepción) de entrada/salida que le ha asignado el sistema operativo.
5 El sistema operativo determina si tiene que tratar él mismo esta
interrupción o bien si tiene que pasársela a alguna aplicación que se
ejecuta sobre él (este sería nuestro caso). Si la interrupción se produjo
sobre una ventana de nuestra aplicación Java, el sistema operativo
traspasa a nuestra máquina virtual Java (JVM) la información de la
interrupción.
6 La máquina virtual Java determina el componente sobre el que se ha
producido la interrupción (pensemos en este momento en un botón que
ha sido pulsado)
7 La máquina virtual Java consulta si se ha definido alguna acción a
realizar como respuesta a esa interrupción sobre dicho componente. Si
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 8: EVENTOS 249

no es así no se hace nada y la CPU continúa con la acción que estaba


ejecutando cuando llego la interrupción.
8 Si se ha definido alguna respuesta:
a. JVM crea un objeto evento con la información de la acción
b. Se pasa el evento al objeto (clase) que tratará la interrupción
c. Se pasa el flujo de control (de ejecución) a la clase Java que hemos
creado para tratar la interrupción.
d. Se pasa el flujo de control a la CPU para que continúe con la acción
que estaba ejecutando cuando llego la interrupción.

La figura siguiente muestra un esquema gráfico de las 7 primeras etapas


expuestas.

Podemos observar como las interrupciones físicas que le llegan a la CPU


(etapa 3) se convierten en eventos de alto nivel (etapa 8) que son tratados por la
propia aplicación Java. Este mecanismo es adecuado, puesto que el sistema
operativo puede tratar interrupciones relacionadas con el sistema (por ejemplo
interrupciones de DMA indicando la finalización de la lectura de una pista del disco
duro), pero no puede procesar lo que hay que hacer cuando se pulsa el botón
Calcular de una aplicación Java.

La máquina virtual Java (JVM) realiza un papel fundamental en el


mecanismo de eventos: recoge la información de interrupción del sistema operativo
(etapa 5), determina la ventana y el componente de la ventana donde se ha producido
el evento (etapa 6) y ejecuta la acción asociada al tipo de evento que llega, en caso
de haber alguna (etapa 7).

En este momento se pueden obtener 3 conclusiones importantes relacionadas


con los mecanismos de programación de eventos en Java:
1 Necesitamos objetos (clases) que nos definan las posibles acciones a
realizar cuando llegan distintos tipos de eventos.
2 No es necesario definir acciones para todos los eventos ni para todos los
objetos (por ejemplo podemos definir etiquetas sin ninguna acción
asociada, también podemos definir una acción para el evento pulsar en
un botón, pero dejar sin acción los eventos “al entrar” y “al salir” con el
puntero del ratón sobre dicho botón).
3 Necesitamos un mecanismo para asociar acciones a los distintos eventos
que se puedan producir sobre cada componente de nuestro GUI.
250 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

8.3 Interfaces que soportan el mecanismo de eventos

Java proporciona una serie de interfaces que agrupan métodos relacionados


para el tratamiento de eventos. Los interfaces más comunes, normalmente
relacionados con los GUI’s basados en AWT, se encuentran en el paquete
java.awt.event. Más adelante se detallará con ejemplos el funcionamiento de los más
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 8: EVENTOS 251

utilizados; en este apartado se presentan y describen los principales interfaces y sus


clases asociadas.

El mecanismo básico de eventos de Java se basa en la existencia de las


clases de eventos y los interfaces “listeners” (escuchadores). Los listeners son
interfaces que debemos implementar, colocando las acciones deseadas en sus
métodos; también podemos basarnos en las implementaciones con métodos vacios
que proporciona el SDK: los “adaptadores”.

AWT proporciona una ámplia gama de eventos que pueden ser recogidos
por los métodos existentes en las implementaciones que hagamos de los listeners (o
las clases que especializemos a partir de los adaptadores). El siguiente gráfico
muestra los eventos, interfaces y adaptadores más utilizados en las aplicaciones de
carácter general.

Eventos Interfaces Adaptadores

AWTEvent EventListener java.awt.event

MouseListener
MouseAdapter
MouseEvent
MouseMotionListener
MouseMotionAdapter

KeyEvent KeyListener
KeyAdapter

WindowEvent WindowListener
WindowAdapter

ActionEvent ActionListener

ItemEvent ItemListener

Para entender con mayor facilidad la manera con la que se tratan los eventos
en Java, vamos a analizar uno de los interfaces existentes: MouseListener. Este
interfaz contiene los siguientes métodos:
• void mousePressed (MouseEvent e)
252 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

• void mouseReleased (MouseEvent e) )


• void mouseClicked (MouseEvent e)
• void mouseEntered (MouseEvent e)
• void mouseExited (MouseEvent e)

El primer método se invoca cuando un botón del ratón ha sido pulsado en un


componente, el segundo método se invoca en la acción contraria (soltar el botón). El
tercer método se invoca cuando se han producido las dos acciones consecutivas
anteriores (pulsar y soltar). Los dos últimos métodos se activan al entrar y salir,
respectivamente, de un componente con el puntero del ratón.

De esta forma, si deseamos que un texto se ponga de color rojo al situarnos


sobre él y de color gris al salir del mismo (con el ratón), deberemos crear una clase
que implemente los métodos mouseEntered y mouseExited; el primer método se
encargará de poner el texto en rojo y el segundo método se encargará de poner el
texto en gris. Si deseamos que al hacer ‘click’ (subir y bajar) con el ratón un
componente botón de un GUI se ejecute una acción, entonces debemos implementar
la acción en el método mouseClicked.

En todos estos casos podemos implementar el interface MouseListener o


bien extender el adaptador MosuseAdapter. Nótese que aunque utilizar el adaptador
es más cómodo, también es menos seguro. El problema que nos podemos encontrar
es que hayamos escrito mal el nombre de algún método utilizado, por ejemplo
mousePresed(MouseEvent e) en lugar de mousePressed(MouseEvent e); en este caso
no existe ningún problema sintáctico detectable por el compilador, pero el método
mousePressed(MouseEvent e) nunca será invocado porque no ha sido definido. Si
utilizamos los interfaces el compilador puede detectar este tipo de errores.

Todos los métodos que hemos analizado incluyen un parámetro de tipo


MouseEvent. Como se puede apreciar en el gráfico anterior, MouseEvent es una
clase que se le suministra a los métodos de los interfaces MouseListener y
MouseMotionListener. El mecanismo de creación y paso de esta clase ha sido
descrito en la etapa 8 del apartado anterior (arquitectura de eventos).

En el siguiente gráfico, las flechas de color gris representan:


1. La manera en la que la información de la interrupción se traspasa a la
máquina virtual Java (8)
2. La creación de la instancia del tipo MouseEvent que JVM produce a
partir de la información de la interrupción (8a)
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 8: EVENTOS 253

3. El traspaso de la instancia creada al método invocado, vía el parámetro e


de tipo MouseEvent (8b)

Los objetos de tipo evento, en nuestro ejemplo MouseEvent, nos permiten


consultar información relativa a la interrupción que genera el evento. La clase
MouseEvent nos proporciona entre otra información: el botón que ha sido pulsado,
las coordenadas X e Y en pixels en las que se encontraba el puntero del ratón
respecto al componente que ha generado la interrupción (el evento), el componente
que ha generado la interrupción, etc.

8.4 Esquema general de programación

A lo largo de los apartados anteriores se ha explicado el funcionamiento


general del mecanismo de eventos y se han mostrado los objetos que Java
proporciona para su utilización. En este apartado desarrollaremos un esquema de
programación que capture eventos y nos sirva como introducción a las siguientes
explicaciones en las que se realizarán ejemplos completos de programación.

El esquema se basará en una porción de aplicación que permita las


siguientes acciones:
• Cuando se pulse un botón determinado se realizará una acción
254 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

• Cuando el puntero del ratón se coloque sobre el botón, el color de fondo


del mismo se pondrá rojo
• Cuando el puntero del ratón salga del botón, el color de fondo del mismo
se pondrá gris

En primer lugar crearemos una clase que asigne las acciones deseadas a los
eventos que hemos determinado. Podemos utilizar el interface MouseListener o la
clase MouseAdapter.

Utilizando el interface:

1 import java.awt.event.*;
2 import java.awt.Color;
3
4 public class EsquemaRaton implements MouseListener {
5
6 public void mouseClicked(MouseEvent EventoQueLlega){
7 // aqui se implementa la accion deseada
8 }
9
10 public void mousePressed(MouseEvent EventoQueLlega){
11 }
12
13 public void mouseReleased(MouseEvent EventoQueLlega){
14 }
15
16 public void mouseEntered(MouseEvent EventoQueLlega){
17 EventoQueLlega.getComponent().setBackground(Color.red);
18 }
19
20 public void mouseExited(MouseEvent EventoQueLlega){
21 EventoQueLlega.getComponent().
setBackground(Color.gray);
22 }
23 }

Cuando se implementa un interfaz es obligatorio definir todos sus métodos,


por lo que incluimos mousePressed y mouseReleased (líneas 10 y 13) aunque no
programemos ninguna acción asociada a los mismos. En el método mouseClicked
(línea 6) programaríamos la acción deseada, mientras que mouseEntered y
mouseExited (líneas 16 y 20) se encargan de variar el color de fondo del componente
que ha generado el evento.
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 8: EVENTOS 255

Obsérvese como a través del objeto MouseEvent podemos conocer


información que proviene del sistema operativo y la máquina virtual Java, en este
caso el componente que ha generado el evento (a través del método getComponent
en las líneas 17 y 21).

Para finalizar el esquema nos falta implementar un interfaz gráfico de


usuario que contenga al menos un botón y enlazar de alguna manera los
componentes deseados con la clase EsquemaRaton. La manera genérica de realizar
este enlace es mediante los métodos addMouseListener, addMouseMotionListener,
addKeyListener, etc. La mayoría de estos métodos pertenecen a la clase Component
y son heredados por los distintos componentes de un GUI. Existen otros métodos
“de enlace” que pertenecen a componentes concretos (por ejemplo la clase Button
contiene el método addActionListener).

Siguiendo nuestro ejemplo, podemos implementar una clase que contenga


un GUI con dos botones y asignar el “listener” EsquemaRaton a cada uno de ellos:

1 import java.awt.*;
2
3 public class PruebaEsquemaRaton {
4
5 public static void main(String[] args) {
6 Frame MiFrame = new Frame("Esquema de eventos");
7 Panel MiPanel = new Panel();
256 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

8 Button Hola = new Button("Saludo");


9 Button Adios = new Button("Despedida");
10 MiPanel.add(Hola); MiPanel.add(Adios);
11 MiFrame.add(MiPanel);
12 MiFrame.setSize(200,100);
13 MiFrame.show();
14
15 Hola.addMouseListener (new EsquemaRaton());
16 Adios.addMouseListener(new EsquemaRaton());
17
18 }
19 }

PruebaEsquemaRaton contiene los botones Hola y Adios (líneas 8 y 9) a los


que se ha asociado el mismo “listener” EsquemaRaton (líneas 15 y 16). El resultado
son dos botones que se comportan de idéntica forma: se ponen de color rojo o gris
según se entre o se salga de cada uno de ellos.

Podemos implementar tantas clases “listeners” como comportamientos


diferentes deseemos y asignar diferentes componentes a distintos “listeners”:

ValidaTexto

RecogeCiudad

EstadoCivil ValidaFormulario

En el gráfico anterior, EstadoCivil, ValidaFormulario , ValidaTexto y


RecogeCiudad son 4 “listeners” diferentes que realizarán las acciones adecuadas
respecto a los componentes del GUI. Las flechas grises representan los distintos
métodos “addxxxxxListener” utilizados para asociar componentes con
comportamientos.
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 8: EVENTOS 257

Un componente no tiene por que estar limitado a la funcionalidad de un solo


“listener”, por ejemplo podemos hacer que el estado civil pueda seleccionarse con el
ratón o con el teclado, utilizando una letra significativa de cada estado civil posible;
de esta manera el componente “EstadoCivil” podría asociarse a un MouseListener y
a un KeyListener:
EstadoCivil.addMouseListener(new RecogeEstadoCivilConRaton());
EstadoCivil.addKeyListener(new RecogeEstadoCivilConTeclado());

Botones de radio: RecogeEstadoCivilConRaton


EstadoCivil

RecogeEstadoCivilConTeclado

8.2 EVENTOS DE RATÓN Y DE MOVIMIENTO DE


RATÓN

8.2.1 Introducción

Los eventos de ratón se capturan haciendo uso de los siguientes interfaces (o


adaptadores) y eventos:

Interface Adaptador Evento


MouseListener MouseAdapter MouseEvent
MouseMotionListener MouseMotionAdapter MouseEvent
MouseWheelListener MouseWheelEvent

Los dos primeros interfaces y adaptadores se usan mucho más a menudo que
MouseWheelListener, por lo que centraremos las explicaciones en ellos. Como se
puede observar en la tabla anterior, el evento MouseEvent se utiliza en los métodos
pertenecientes a los interfaces MouseListener y MouseMotionListener. El interfaz
258 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

MouseWheelListener solo consta de un método, por lo que no resulta interesante


incorporarle un adaptador.

Eventos Interfaces Adaptadores

AWTEvent EventListener java.awt.event

MouseListener
MouseAdapter
MouseEvent
MouseMotionListener
MouseMotionAdapter

MouseWheelEvent MouseWheelListener

A continuación se presenta un resumen de los objetos más importantes en el


proceso de tratamiento de eventos de ratón:

MouseListener

Método El método se invoca cuando…


mouseClicked (MouseEvent e) Se hace click (presión y liberación) con un botón del
ratón
mouseEntered (MouseEvent e) Se introduce el puntero del ratón en el interior de un
componente
mouseExited (MouseEvent e) Se saca el puntero del ratón del interior de un
componente
mousePressed (MouseEvent e) Se presiona un botón del ratón
mouseReleased (MouseEvent e) Se libera un botón del ratón (que había sido
presionado)

MouseMotionListener

Método El método se invoca cuando…


mouseDragged (MouseEvent e) Se presiona un botón y se arrastra el ratón
mouseMoved (MouseEvent e) Se mueve el puntero del ratón en un componente (sin
pulsar un botón)

MouseWheelListener

Método El método se invoca cuando…


MouseWheelMoved Se mueve la rueda del ratón
(MouseWheelEvent e)
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 8: EVENTOS 259

MouseEvent

Métodos más utilizados Explicación


int getButton() Indica qué botón del ratón ha cambiado su estado (en caso
de que alguno haya cambiado de estado). Se puede hacer
la consulta comparando con los valores BUTTON1,
BUTTON2 y BUTTON3
int getClickCount() Número de clicks asociados con este evento
Point getPoint() Devuelve la posición x, y en la que se ha generado este
evento (posición relativa al componente)
int getX() Devuelve la posición x en la que se ha generado este
evento (posición relativa al componente)
int getY() Devuelve la posición y en la que se ha generado este
evento (posición relativa al componente)
Object getSource() Método perteneciente a la clase EventObject (superclase
de MouseEvent). Indica el objeto que produjo el evento

8.2.2 Eventos de ratón

En este apartado vamos a desarrollar una serie de ejemplos que muestran el


uso del interfaz MouseListener, la clase MouseAdapter y el evento MouseEvent.
Utilizaremos ejemplos muy sencillos que ayuden a afianzar el mecanismo que Java
ofrece para capturar eventos.

En primer lugar se presenta la clase InterrupcionDeRaton1, que implementa


el interfaz MouseListener (línea 6). En la línea 1 se importan los objetos
pertenecie ntes al paquete java.awt.event, donde se encuentran los que necesitamos
en este momento: MouseListener y MouseEvent.

La clase InterrupcionDeRaton1 implementa los 5 métodos del interfaz


MouseListener, condición necesaria para que el compilador no nos genere un
mensaje de error (a no ser que la declaremos abstracta). En este primer ejemplo la
única acción que realizamos es imprimir por consola un mensaje identificando el
evento que ha llegado.

1 import java.awt.event.*;
2
3 // Clase que recoge los eventos de raton mediante sus
// metodos "mousePressed,
4 // mouseReleased, mouseClicked, etc." con parametro
// "MouseEvent"
260 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

5
6 public class InterrupcionDeRaton1 extends Object implements
MouseListener {
7
8 public void mouseClicked(MouseEvent EventoQueLlega){
9 System.out.println("Click de raton");
10 }
11
12 public void mousePressed(MouseEvent EventoQueLlega){
13 System.out.println("Presion de raton");
14 }
15
16 public void mouseReleased(MouseEvent EventoQueLlega){
17 System.out.println("Se ha levantado el boton del
raton");
18 }
19
20 public void mouseEntered(MouseEvent EventoQueLlega){
21 System.out.println("'Focus' de raton");
22 }
23
24 public void mouseExited(MouseEvent EventoQueLlega){
25 System.out.println("'Blur' de raton");
26 }
27 }

Para conseguir que la clase InterrupcionDeRaton1 actúe sobre uno o varios


componentes, debemos instanciarla y “enlazarla” con los componentes deseados.
Esto lo vamos a realizar en la clase PruebaEventosRaton1:

1 import java.awt.*;
2
3 public class PruebaEventosRaton1 {
4
5 public static void main(String[] args) {
6 Frame MiFrame = new Frame("Prueba eventos de raton");
7 Panel MiPanel = new Panel();
8 Button Hola = new Button("Saludo");
9 Button Adios = new Button("Despedida");
10 MiPanel.add(Hola); MiPanel.add(Adios);
11 MiFrame.add(MiPanel);
12 MiFrame.setSize(200,100);
13 MiFrame.show();
14
15 Hola.addMouseListener (new InterrupcionDeRaton1());
16 Adios.addMouseListener(new InterrupcionDeRaton1());
17 }
18 }
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 8: EVENTOS 261

La clase PruebaEventosRaton1 crea un interfaz gráfico de usuario que


contiene un marco, un panel y dos botones (líneas 6 a 13), posteriormente crea una
instancia de la clase InterrupcionDeRaton1 y la “enlaza” al botón Hola mediante el
método addMouseListener (línea 15). En la línea 16 se hace lo mismo con el botón
Adios; de esta manera ambos botones presentan el mismo comportamiento respecto
a los eventos de ratón.

El método addMouseListener (MouseListener l) pertenece a la clase


Component, por lo que podemos capturar eventos de ratón en todos los objetos
subclases de Component (Button, Label, List, Choice, etc.).

Una posible ejecución del ejemplo anterior nos da el siguiente resultado:

La clase IntrrupcionDeRaton1 puede ser utilizada por otras aplic aciones, en


el ejemplo PruebaEventosRaton2 se asocia su comportamiento a un panel situado
sobre un marco (línea 12):

1 import java.awt.*;
2
3 public class PruebaEventosRaton2 {
4
5 public static void main(String[] args) {
6 Frame MiFrame = new Frame("Prueba eventos de raton");
7 Panel MiPanel = new Panel();
8 MiFrame.add(MiPanel);
9 MiFrame.setSize(200,100);
10 MiFrame.show();
11
12 MiPanel.addMouseListener (new InterrupcionDeRaton1());
13
14 }
15 }
262 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

Para modificar el comportamiento de algunos componentes de un interfaz


gráfico de usuario nos basta con variar, en esos componentes, la clase que
implementa el interfaz ‘Listener’. En el ejemplo siguiente (PruebaEventosRaton3)
se utiliza el mismo GUI que presenta la clase PruebaEventosRaton1, pero ahora
empleamos el objeto InterrupcionDeRaton2.

1 import java.awt.*;
2
3 public class PruebaEventosRaton3 {
4
5 public static void main(String[] args) {
6 // como en PruebaEventosRaton1
7
8 Hola.addMouseListener (new InterrupcionDeRaton2());
9 Adios.addMouseListener(new InterrupcionDeRaton2());
10
11 }
12 }

La clase InterrupcionDeRaton2 aumenta la funcionalidad de


InterrupcionDeRaton1. Los métodos mousePressed (línea 11) y mouseReleased
(línea 17) utilizan los métodos getX y getY de la clase EventoQueLlega, de tipo
MouseEvent; de esta manera podemos conocer las coordenadas del puntero del ratón
(respecto al origen del componente) cuando se pulsa y cuando se suelta el botón.

Los métodos mouseEntered (línea 23) y mouseExited (línea 29) hacen uso
del método getSource para obtener el objeto que generó el evento. Conociendo el
objeto, podemos cambiar sus propiedades (en este ejemplo su color de fondo). En
este ejemplo, en una serie de dos botones, conseguimos cambiar a azul el color de
fondo del botón sobre el que nos situamos con el ratón, dejándolo en gris al salir del
mismo.

1 import java.awt.event.*;
2 import java.awt.Component;
3 import java.awt.Color;
4
5 public class InterrupcionDeRaton2 extends Object implements
MouseListener {
6
7 public void mouseClicked(MouseEvent EventoQueLlega){
8 System.out.println("Click de raton");
9 }
10
11 public void mousePressed(MouseEvent EventoQueLlega){
12 System.out.println("Presion de raton");
13 System.out.println(EventoQueLlega.getX());
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 8: EVENTOS 263

14 System.out.println(EventoQueLlega.getY());
15 }
16
17 public void mouseReleased(MouseEvent EventoQueLlega){
18 System.out.println("Se ha levantado el boton del
raton");
19 System.out.println(EventoQueLlega.getX());
20 System.out.println(EventoQueLlega.getY());
21 }
22
23 public void mouseEntered(MouseEvent EventoQueLlega){
24 System.out.println("'Focus' de raton");
25 Component Boton = (Component)EventoQueLlega.getSource();
26 Boton.setBackground(Color.blue);
27 }
28
29 public void mouseExited(MouseEvent EventoQueLlega){
30 System.out.println("'Blur' de raton");
31 Component Boton = (Component)EventoQueLlega.getSource();
32 Boton.setBackground(Color.gray);
33 }
34 }

No debemos olvidar que al implementar un interfaz con una clase no


abstracta debemos implementar todos y cada uno de sus métodos, de esta forma la
clase InterrupcionDeRaton3 no puede ser compilada sin errores:

1 import java.awt.event.*;
2 import java.awt.Component;
3 import java.awt.Color;
4
5 public class InterrupcionDeRaton3 extends Object implements
MouseListener {
6
7 // public void mouseClicked(MouseEvent EventoQueLlega){
8 // System.out.println("Click de raton");
9 // }
10
11 // Como en la clase InterrupcionDeRaton2
12 }
264 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

Java proporciona el adaptador MouseAdapter, que implementa todos los


métodos del interfaz MouseListener; de esta manera, si usamos MouseAdapter,
podremos sobrecargar únicamente los métodos que deseemos. La clase
InterrupcionDeRaton4 funciona de esta manera.

1 import java.awt.event.*;
2 import java.awt.Component;
3 import java.awt.Color;
4
5 public class InterrupcionDeRaton4 extends MouseAdapter {
6
7 public void mouseEntered(MouseEvent EventoQueLlega){
8 System.out.println("'Focus' de raton");
9 Component Boton = (Component)EventoQueLlega.getSource();
10 Boton.setBackground(Color.blue);
11 }
12
13 public void mouseExited(MouseEvent EventoQueLlega){
14 System.out.println("'Blur' de raton");
15 Component Boton = (Component)EventoQueLlega.getSource();
16 Boton.setBackground(Color.gray);
17 }
18 }

El último ejemplo de este apartado PruebaEventosRaton5 permite crear un


número arbitrario de botones, pudiendo desplazarse por los mismos provocando su
cambio de color. Cuando se pulsa en cualquiera de ellos aparecerá un mensaje
aclarativo; este mensaje podría sustituirse con facilidad por la ejecución de un
método que tuviese asociado cada botón.

En la clase PruebaEventosRaton5 se crea un GUI con una etiqueta Mensaje


(líneas 7 y 20) y NUM_OPCIONES botones (líneas 8 a 15, 21 y 22). Nótese el uso
del método setName (líneas 12 y 14) para asignar un nombre a cada botón (no
confundir el nombre del botón con su identificador o su texto). En las líneas 28 y 29
se asigna a todos los botones instancias de la misma clase de tratamiento de eventos
de ratón: InterrupcionDeRaton5. Esta clase admite un constructor con un parámetro
de tipo Label, donde pasamos el apuntador al objeto Mensaje.
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 8: EVENTOS 265

1 import java.awt.*;
2
3 public class PruebaEventosRaton5 {
4
5 public static void main(String[] args) {
6 final int NUM_OPCIONES = 12;
7 Label Mensaje = new Label("Mensaje asociado al botón
pulsado");
8 Button[] Botones = new Button[NUM_OPCIONES];
9 for (int i=0;i<NUM_OPCIONES;i++) {
10 Botones[i] = new Button("Opcion " + i);
11 if (i<10)
12 Botones[i].setName("0"+i);
13 else
14 Botones[i].setName(String.valueOf(i));
15 }
16
17 Frame MiFrame = new Frame("Prueba eventos de raton");
18 Panel PanelPrincipal = new Panel(new
GridLayout(NUM_OPCIONES+1,1));
19
20 PanelPrincipal.add(Mensaje);
21 for (int i=0;i<NUM_OPCIONES;i++)
22 PanelPrincipal.add(Botones[i]);
23
24 MiFrame.add(PanelPrincipal);
25 MiFrame.setSize(200,300);
26 MiFrame.show();
27
28 for (int i=0;i<NUM_OPCIONES;i++)
29 Botones[i].addMouseListener(new
InterrupcionDeRaton5(Mensaje));
30
31
32 }
33 }

La clase InterrupcionDeRaton5 extiende el adaptador MouseAdapter (línea


6) y contiene un constructor (línea 11) a través del cual se puede indicar una etiqueta
donde los métodos de la clase podrán asignar diferentes textos.

Los métodos mouseEntered (línea 22) y mouseExited (línea 27) se encargan


de mantener los colores de los botones según pasamos el puntero del ratón por los
mismos. El método mouseClicked (línea 15) en primer lugar determina el
componente (en nuestro caso un botón) que ha generado el evento (línea 16),
después obtiene su nombre mediante el método getName (línea 17), posteriormente
(línea 18) aísla los dos últimos dígitos del nombre (que en nuestro ejemplo indican
266 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

el número del botón), finalmente se modifica el texto de la etiqueta que ha recibido


la clase a través de su constructor (línea 19).

1 import java.awt.event.*;
2 import java.awt.Component;
3 import java.awt.Label;
4 import java.awt.Color;
5
6 public class InterrupcionDeRaton5 extends MouseAdapter {
7
8 private Label Mensaje;
9 private Component ComponenteQueInvoca;
10
11 public InterrupcionDeRaton5(Label Mensaje) {
12 this.Mensaje = Mensaje;
13 }
14
15 public void mouseClicked(MouseEvent EventoQueLlega){
16 Component ComponenteQueInvoca = (Component)
EventoQueLlega.getSource();
17 String Nombre = ComponenteQueInvoca.getName();
18 String Opcion = Nombre.substring(Nombre.length()
-2,Nombre.length());
19 Mensaje.setText("Ejecutar la accion "+Opcion);
// invocar a un método
20 }
21
22 public void mouseEntered(MouseEvent EventoQueLlega){
23 Component ComponenteQueInvoca = (Component)
EventoQueLlega.getSource();
24 ComponenteQueInvoca.setBackground(Color.red);
25 }
26
27 public void mouseExited(MouseEvent EventoQueLlega){
28 Component ComponenteQueInvoca = (Component)
EventoQueLlega.getSource();
29 ComponenteQueInvoca.setBackground(Color.orange);
30 }
31 }
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 8: EVENTOS 267

Una última consideración respecto a este último ejemplo se centra en la


decisión de pasar la etiqueta del GUI como argumento al constructor de la clase de
tratamiento de eventos. Si analizamos lo que hemos hecho, desde un punto de vista
orientado a objetos no es muy adecuado, puesto que la etiqueta no debería ser visible
en la clase InterrupcionDeRaton5. Este es un problema clásico en el diseño y
desarrollo de interfaces de usuario. Habitua lmente se resuelve uniendo en un solo
fichero ambas clases, aunque esta solución es menos modular y reutilizable que la
adoptada. Java proporciona medios de mayor complejidad para establecer una
solución más elegante a esta interacción GUI/Adaptador, aunque, en general, estos
medios no son muy utilizados.

8.2.3 Eventos de movimiento de ratón

Los eventos de movimiento de ratón nos permiten asociar acciones a cada


movimiento del ratón a través de un componente, de esta manera, por ejemplo,
podríamos implementar una aplicación que dibuje la trayectoria del ratón, o que
vaya dibujando las rectas con origen el punto donde pulsamos el ratón y con final
cada posición por donde movemos el ratón con el botón pulsado.

Tal y como vimos anteriormente, los métodos involucrados en el interfaz


MouseMotionListener son: mouseMoved y mouseDragged. En el siguiente ejemplo
PruebaEventosMovimientoRaton1 vamos a utilizar de una manera muy sencilla
estos métodos. La clase PruebaEventosMovimientoRaton1 contiene un GUI con un
panel MiPanel al que se le añade una instancia de la clase de tratamiento de eventos
de movimiento de ratón: InterrupciónDeMovimientoDeRaton1 (línea 12).
268 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

1 import java.awt.*;
2
3 public class PruebaEventosMovimientoRaton1 {
4
5 public static void main(String[] args) {
6 Frame MiFrame = new Frame("Prueba eventos de movimiento
de raton");
7 Panel MiPanel = new Panel();
8 MiFrame.add(MiPanel);
9 MiFrame.setSize(200,100);
10 MiFrame.show();
11
12 MiPanel.addMouseMotionListener (new
InterrupcionDeMovimientoDeRaton1());
13 }
14 }

La clase InterrupcionDeMovimientoDeRaton1 implementa el interfaz


MouseMotionListener (línea 3), por lo que también implementa sus métodos
mouseMoved (línea 5) y mouseDragged (línea 9). Ambos métodos, en este primer
ejemplo, simplemente sacan por consola un mensaje representativo del evento que
se ha producido (líneas 6 y 10).

1 import java.awt.event.*;
2
3 public class InterrupcionDeMovimientoDeRaton1 implements
MouseMotionListener {
4
5 public void mouseMoved(MouseEvent EventoQueLlega){
6 System.out.println("Movimiento del raton");
7 }
8
9 public void mouseDragged(MouseEvent EventoQueLlega){
10 System.out.println("Movimiento del raton con un boton
pulsado");
11 }
12
13 }
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 8: EVENTOS 269

El segundo ejemplo de este apartado muestra en una ventana las posiciones


x,y que va tomando el puntero del ratón según el usuario lo va moviendo por la
pantalla. Las posiciones x, y son relativas a la esquina superior izquierda del
componente que genera el evento, en nuestro caso el panel MiPanel (líneas 7, 11, 12
y 16). El color del texto en las etiquetas será de color azul si tenemos pulsado un
botón del ratón y naranja si no lo tenemos.

La clase PruebaEventosMovimientoRaton2 crea un GUI en el que existe un


marco (línea 6), un panel (línea 7) y dos etiquetas (líneas 9 y 10). En la línea 16 se
añade una instancia de la clase de tratamiento de eventos de movimiento de ratón:
InterrupcionDeMovimientoDeRato n2; esta clase contiene un constructor que recoge
las referencias de las etiquetas, con el fin de poder variar sus valores.

1 import java.awt.*;
2
3 public class PruebaEventosMovimientoRaton2 {
4
5 public static void main(String[] args) {
6 Frame MiFrame = new Frame("Prueba eventos de movimiento
de raton");
7 Panel MiPanel = new Panel(new
FlowLayout(FlowLayout.LEFT));
8 MiFrame.add(MiPanel);
9 Label PosicionX = new Label("000");
10 Label PosicionY = new Label("000");
11 MiPanel.add(PosicionX);
12 MiPanel.add(PosicionY);
13
14 MiFrame.setSize(300,200);
15 MiFrame.show();
16 MiPanel.addMouseMotionListener(new
InterrupcionDeMovimientoDeRaton2(PosicionX,PosicionY));
17 }
18 }
270 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

La clase InterrupcionDeMovimientoDeRaton2 (línea 5) implementa el


interfaz de tratamiento de eventos de movimiento de ratón (MouseMotionListener).
Su único constructor contiene como parámetros dos etiquetas (línea 10).

Los métodos mouseMoved y mouseDragged (líneas 22 y 28) asignan el


color adecuado a las etiquetas e invocan (líneas 25 y 31) al método privado
AsignaPosicion (línea 15). Este método obtiene la posición en pixels del puntero del
ratón, usando los métodos getX y getY (líneas 16 y 18) pertenecientes a la clase
MouseEvent. Estos valores de tipo int se convierten a String y se asignan a las
etiquetas (líneas 17 y 19).

1 import java.awt.event.*;
2 import java.awt.*;
3 import java.lang.String;
4
5 public class InterrupcionDeMovimientoDeRaton2 implements
6 MouseMotionListener {
7
8 private Label EtiquetaX, EtiquetaY;
9
10 InterrupcionDeMovimientoDeRaton2(Label EtiquetaX, Label
EtiquetaY) {
11 this.EtiquetaX = EtiquetaX;
12 this.EtiquetaY = EtiquetaY;
13 }
14
15 private void AsignaPosicion(MouseEvent EventoQueLlega) {
16 String PosicionX =
String.valueOf(EventoQueLlega.getX());
17 EtiquetaX.setText(PosicionX);
18 String PosicionY =
String.valueOf(EventoQueLlega.getY());
19 EtiquetaY.setText(PosicionY);
20 }
21
22 public void mouseMoved(MouseEvent EventoQueLlega){
23 EtiquetaX.setForeground(Color.orange);
24 EtiquetaY.setForeground(Color.orange);
25 AsignaPosicion(EventoQueLlega);
26 }
27
28 public void mouseDragged(MouseEvent EventoQueLlega){
29 EtiquetaX.setForeground(Color.blue);
30 EtiquetaY.setForeground(Color.blue);
31 AsignaPosicion(EventoQueLlega);
32 }
33 }
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 8: EVENTOS 271

Si deseamos evitar el uso del constructor con parámetros podemos unificar


el GUI y el adaptador en un solo fichero en el que las etiquetas son propiedades
globales al adaptador:

1 import java.awt.event.*;
2 import java.awt.*;
3 import java.lang.String;
4
5 public class PruebaEventosMovimientoRaton2b {
6 Frame MiFrame = new Frame("Prueba eventos de movimiento
de raton");
7 Panel MiPanel = new Panel(new
FlowLayout(FlowLayout.LEFT));
8 Label PosicionX = new Label("000");
9 Label PosicionY = new Label("000");
10
11 PruebaEventosMovimientoRaton2b() {
12 MiFrame.add(MiPanel);
13 MiPanel.add(PosicionX);
14 MiPanel.add(PosicionY);
15 MiFrame.setSize(300,200);
16 MiFrame.show();
17 MiPanel.addMouseMotionListener(new
18 InterrupcionDeMovimientoDeRaton2b());

19 } // Constructor
20
21
22 class InterrupcionDeMovimientoDeRaton2b implements
MouseMotionListener {
23
24 private void AsignaPosicion(MouseEvent EventoQueLlega) {
25 String X = String.valueOf(EventoQueLlega.getX());
26 PosicionX .setText(X);
27 String Y = String.valueOf(EventoQueLlega.getY());
28 PosicionY .setText(Y);
29 }
30
31 public void mouseMoved(MouseEvent EventoQueLlega){
32 PosicionX .setForeground(Color.orange);
33 PosicionY .setForeground(Color.orange);
34 AsignaPosicion(EventoQueLlega);
35 }
36
37 public void mouseDragged(MouseEvent EventoQueLlega){
38 PosicionX .setForeground(Color.blue);
39 PosicionY .setForeground(Color.blue);
40 AsignaPosicion(EventoQueLlega);
272 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

41 }
42
43 } // InterrupcionDeMovimientoDeRaton2b
44
45 } // PruebaEventosMovimientoRaton2b

Para ejecutar la aplicación instanciamos la clase


PruebaEventosMovimientoRaton2b:

1 public class Prueba {


2 public static void main(String[] args) {
3 PruebaEventosMovimientoRaton2b Instancia =
4 new PruebaEventosMovimientoRaton2b();
5 }
6 }

8.3 EVENTOS DE TECLADO Y DE VENTANA


8.3.1 Introducción

Los eventos de teclado se capturan haciendo uso de los siguientes interfaces


(o adaptadores) y eventos:

Interface Adaptador Evento


KeyListener KeyAdapter KeyEvent
WindowListener WindowAdapter WindowEvent

Eventos Interfaces Adaptadores

AWTEvent EventListener java.awt.event

KeyListener
KeyAdapter
KeyEvent

WindowEvent WindowListener
WindowAdapter
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 8: EVENTOS 273

A continuación se presenta un resumen de los objetos más importantes en el


proceso de tratamiento de eventos de teclado y de ventana:

KeyListener

Método El método se invoca cuando…


keyPressed (KeyEvent e) Se ha presionado una tecla
keyReleased (KeyEvent e) Se ha terminado la presión de una tecla
keyTyped (KeyEvent e) Se ha presionado (en algunas ocasiones presionado y
soltado) una tecla

WindowListener

Método El método se invoca cuando…


windowActivated (windowEvent e) La ventana pasa a ser la activa
windowDeactivated (windowEvent e) La ventana deja de ser la activa
windowOpened (windowEvent e) La primera vez que la ventana se hace visible
windowClosing (windowEvent e) El usuario indica que se cierre la ventana
windowClosed (windowEvent e) La ventana se ha cerrado
windowIconified (windowEvent e) La ventana pasa de estado normal a un estado
minimizado
windowDeiconified (windowEvent e) La ventana pasa de estado minimizado a un
estado normal

KeyEvent

Métodos más utilizados Explicación


char getKeyChar() Devuelve el carácter asociado con la tecla pulsada
int getKeyCode() Devuelve el valor entero que representa la tecla pulsada
String getKeyText() Devuelve un texto que representa el código de la tecla
Object getSource() Método perteneciente a la clase EventObject. Indica el
objeto que produjo el evento

WindowEvent

Métodos más utilizados Explicación


Window getWindow() Devuelve la ventana que origina el evento
Window Devuelve la ventana involucrada en el cambio de
getOppositeWindow() activación o de foco
int getNewState() Para WINDOW_STATE_CHANGED indica el nuevo
estado de la ventana
int getOldState() Para WINDOW_STATE_CHANGED indica el antiguo
estado de la ventana
Object getSource() Método perteneciente a la clase EventObject. Indica el
objeto que produjo el evento
274 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

8.3.2 Eventos de teclado

En este apartado vamos a desarrollar dos ejemplos que muestran el uso del
interfaz KeyListener, la clase KeyAdapter y el evento KeyEvent.

En el primer ejemplo utilizamos una clase de tratamiento de eventos de


teclado (InterrupcionDeTeclado1) que escribe un texto por consola cada vez que se
activa uno de los tres métodos que proporciona el interfaz KeyListener. Los métodos
keyTyped, keyPressed y keyReleased se implementan en las líneas 5, 9 y 13.

1 import java.awt.event.*;
2
3 public class InterrupcionDeTeclado1 implements KeyListener
{
4
5 public void keyTyped(KeyEvent EventoQueLlega){
6 System.out.println("Tecla pulsada y soltada");
7 }
8
9 public void keyPressed(KeyEvent EventoQueLlega){
10 System.out.println("Tecla pulsada");
11 }
12
13 public void keyReleased(KeyEvent EventoQueLlega){
14 System.out.println("Tecla soltada");
15 }
16
17 }

Para probar el funcionamiento de la clase anterior creamos


PruebaEventosTeclado1 (línea 3), que incorpora un GUI en el que al panel MiPanel
le añadimos una instancia del “listener” InterrupcionDeTeclado1 (línea 13).

1 import java.awt.*;
2
3 public class PruebaEventosTeclado1 {
4
5 public static void main(String[] args) {
6
7 Frame MiFrame = new Frame("Prueba eventos de raton");
8 Panel MiPanel = new Panel();
9 MiFrame.add(MiPanel);
10 MiFrame.setSize(200,300);
11 MiFrame.show();
12
13 MiPanel.addKeyListener(new InterrupcionDeTeclado1());
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 8: EVENTOS 275

14
15 }
16 }

Según el tipo de tecla pulsada (letras, teclas de función, etc.) la activación


del evento keyTyped puede variar.

Nuestro segundo ejemplo imprime por consola los caracteres tecleados hasta
que pulsamos el asterisco, momento en el que abandonamos de la aplicación. Para
conseguir esta funcionalidad nos basta con hacer uso de un solo método del interfaz
KeyListener. Para facilitarnos la labor empleamos el adaptador KeyAdapter (línea 3
de la clase InterrupcionDeTeclado2), sobrecargando únicamente el método
keyTyped (cualquiera de los otros dos métodos nos hubiera servido de igual manera).

En el método keyTyped (línea 5) imprimimos (línea 6) el carácter


correspondiente a la tecla pulsada; este carácter lo obtenemos invocando al método
getKeyChar perteneciente a la clase KeyEvent. Posteriormente consultamos si su
valor es un asterisco (línea 7), en cuyo caso abandonamos la aplicación (línea 8).

1 import java.awt.event.*;
2
3 public class InterrupcionDeTeclado2 extends KeyAdapter {
4
5 public void keyTyped(KeyEvent e){
6 System.out.print(e.getKeyChar());
7 if (e.getKeyChar()=='*')
8 System.exit(0);
9 }
10
11 }

Tal y como hemos venido haciendo en ejercicios anteriores, implementamos


una clase que incorpora un GUI con algún elemento al que se le añade la rutina de
tratamiento de interrupción. En este caso la clase es PruebaEventosTeclado2 (línea
3) y el método addKeyListener se utiliza en la línea 17.

1 import java.awt.*;
2
276 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

3 public class PruebaEventosTeclado2 {


4
5 public static void main(String[] args) {
6
7 Frame MiFrame = new Frame("Prueba eventos de raton");
8 Panel MiPanel = new Panel();
9 Button Boton1 = new Button("Silencio");
10 Button Boton2 = new Button("Máquina de escribir");
11 MiPanel.add(Boton1);
12 MiPanel.add(Boton2);
13 MiFrame.add(MiPanel);
14 MiFrame.setSize(200,300);
15 MiFrame.show();
16
17 Boton2.addKeyListener(new InterrupcionDeTeclado2());
18
19 }
20 }

8.3.3 Eventos de ventana

En este apartado vamos a desarrollar un solo ejemplo que muestra el uso del
interfaz WindowListener y el evento WindowEvent. Simplemente implementamos
los métodos del interfaz WindowListener para que imprima, cada uno, un texto
significativo del evento.

1 import java.awt.event.*;
2
3 public class InterrupcionDeVentana1 implements
WindowListener {
4
5 public void windowOpened(WindowEvent e) {
6 System.out.println("Ventana " + e.getWindow().getName()
+ " abierta");
7 }
8
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 8: EVENTOS 277

9 public void windowClosing(WindowEvent e) {


10 System.out.println("Si pulsas aqui se cierra la
ventana " + e.getWindow().getName() );
11 // System.exit(0);
12 }
13
14 public void windowClosed(WindowEvent e) {
15 System.out.println("Ventana " + e.getWindow().getName()
+ " cerrada");
16 }
17
18 public void windowActivated(WindowEvent e) {
19 System.out.println("Ventana " + e.getWindow().getName()
+ " activa (en uso)");
20 }
21
22 public void windowDeactivated(WindowEvent e) {
23 System.out.println("Ventana " + e.getWindow().getName()
+ " desactivada (fuera de uso)");
24 }
25
26 public void windowIconified(WindowEvent e) {
27 System.out.println("Ventana " + e.getWindow().getName()
+ " minimizada");
28 }
29
30 public void windowDeiconified(WindowEvent e) {
31 System.out.println("Ventana " + e.getWindow().getName()
+ " restaurada");
32 }
33
34 }

El programa de prueba PruebaEventosVentana1 crea dos marcos (líneas 7 y


11) y les añade instancias de la clase de tratamiento de eventos de ventana
InterrupcionDeVentana1, haciendo uso del método addWindowListener (líneas 16 y
17).

1 import java.awt.*;
2
3 public class PruebaEventosVentana1 {
4
5 public static void main(String[] args) {
6
7 Frame MiFrame = new Frame("Prueba eventos de ventana");
8 MiFrame.setSize(200,100);
9 MiFrame.show();
10
278 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

11 Frame OtroFrame = new Frame("Prueba eventos de


ventana");
12 OtroFrame.setSize(100,50);
13 OtroFrame.setLocation(200,0);
14 OtroFrame.show();
15
16 MiFrame.addWindowListener(new InterrupcionDeVentana1());
17 OtroFrame.addWindowListener(new InterrupcionDeVentana1());
18
19 }
20 }

8.4 EVENTOS DE ACCIÓN, ENFOQUE Y ELEMENTO


8.4.1 Introducción

Los eventos de acción, enfoque y elemento se capturan haciendo uso de los


siguientes interfaces (o adaptadores) y eventos:

Interface Adaptador Evento


ActionListener ActionEvent
FocusListener FocusAdapter FocusEvent
ItemListener ItemEvent

Estos eventos tienen un nivel de abstracción superior a los de ratón y


teclado; en estos tres casos el evento se produce cuando ocurre una acción sobre un
componente, independientemente de la causa física que produce ese evento. Por
ejemplo, podemos pulsar un botón haciendo uso del ratón, del teclado, e incluso por
programa.
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 8: EVENTOS 279

Eventos Listeners Adaptadores

AWTEvent EventListener java.awt.event

FocusEvent FocusListener
FocusAdapter

ActionEvent ActionListener

ItemEvent ItemListener

Empleando los “listeners” vistos hasta ahora, si quisiéramos, por ejemplo,


ejecutar un método asociado a la pulsación de un botón, tendríamos que añadir a ese
botón dos instancias de tratamiento de eventos: una de tipo MouseListener para
capturar pulsaciones de ratón sobre el botón y otra de tipo KeyListener para capturar
pulsaciones de teclado. En este caso sería mucho más cómodo utilizar el interfaz
ActionListener que proporciona un método que se activa con independencia de la
causa física o lógica que “activa” el componente.

A continuación se presenta un resumen de los objetos más importantes en el


proceso de tratamiento de eventos de acción, enfoque y elemento:

ActionListener

Método El método se invoca cuando…


actionPerformed (ActionEvent e) Ocurre una acción sobre el elemento

FocusListener

Método El método se invoca cuando…


focusGained (FocusEvent e) Nos posicionamos sobre el componente con el
teclado
focusLost (FocusEvent e) Salimos del elemento haciendo uso del teclado

ItemListener

Método El método se invoca cuando…


itemStateChanged (ItemEvent e) Un elemento ha sido seleccionado o deseleccionado
por el usuario
280 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

ActionEvent

Métodos más utilizados Explicación


String getActionCommand() Identifica/explica la acción
long getWhen() Momento en el que se produce el evento
Object getSource() Método perteneciente a la clase EventObject. Indica el
objeto que produjo el evento

FocusEvent

Métodos más utilizados Explicación


String Indica el componente involucrado en el cambio de foco.
getOppositeComponent() Si el tipo de evento es FOCUS_GAINED se refiere al
componente que ha perdido el foco, si es
FOCUS_LOST al que ha ganado el foco.
int getID() Método perteneciente a la clase AWTEvent. Indica el
tipo de evento
Object getSource() Método perteneciente a la clase EventObject. Indica el
objeto que produjo el evento

ItemEvent

Métodos más utilizados Explicación


int getStateChange() Tipo de cambio producido (SELECTED,
DESELECTED)
Object getItem() Objeto afectado por el evento (por ejemplo una lista)
ItemSelectable Elemento que ha originado el evento (por ejemplo un
getItemSelectable() elemento de una lista)
Object getSource() Método perteneciente a la clase EventObject. Indica el
objeto que produjo el evento

8.4.2 Eventos de acción

En este apartado vamos a desarrollar dos ejemplos que muestran el uso del
interfaz ActionListener y el evento ActionEvent.

En el primer ejemplo utilizamos una clase de tratamiento de eventos de


acción (InterrupcionDeAccion1) que escribe por consola información del evento
cada vez que se activa actionPerformed (el único método que proporciona el interfaz
ActionListener).
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 8: EVENTOS 281

En la línea 6 se implementa el método actionPerformed, que admite como


parámetro un objeto de tipo ActionEvent. En primer lugar (línea 7) obtenemos el
componente que generó el evento, para ello hacemos uso del método getSource. En
la línea 8 obtenemos un literal que identifica el comando (en este caso el texto
asociado al objeto); utilizamos el método getActionCommand sobre la instancia del
evento que nos ha llegado. En las líneas 10 y 11 se imprime la información obtenida
y el nombre del componente (como no hemos asignado explícitamente nombre a los
componentes, conseguimos los que JVM asigna implícitamente).

1 import java.awt.event.*;
2 import java.awt.*;
3
4 public class InterrupcionDeAccion1 implements
ActionListener {
5
6 public void actionPerformed(ActionEvent Evento){
7 Component Componente = (Component) Evento.getSource();
8 String AccionRealizada = Evento.getActionCommand();
9
10 System.out.println("Componente: " +
Componente.getName());
11 System.out.println("Suceso: " + AccionRealizada);
12 System.out.println();
13 }
14 }

Para probar nuestra implementación del interfaz ActionListener, creamos


una clase PruebaEventosAccion1 (línea 3) que incorpora un GUI con un botón, un
campo de texto y una lista, a estos tres elementos se les asocian sendas instancias de
la clase InterrupcionDeAccion1 (líneas 20 a 22); el resultado de una posible
ejecución se muestra tras el código de la clase.

1 import java.awt.*;
2
3 public class PruebaEventosAccion1 {
4
5 public static void main(String[] args) {
6
7 Frame MiFrame = new Frame("Prueba eventos de acción");
8 Panel MiPanel = new Panel(new GridLayout(3,1));
9
10 Button Boton = new Button("Etiqueta del boton");
11 TextField CampoDeTexto = new TextField("Texto del
campo",8);
12 List Lista = new List(3);
13 Lista.add("Opcion 1 de la lista");
Lista.add("Opcion 2 de la lista");
282 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

14 Lista.add("Opcion 3 de la lista");
Lista.add("Opcion 4 de la lista");
15
16 MiPanel.add(Boton);
17 MiPanel.add(CampoDeTexto);
18 MiPanel.add(Lista);
19
20 Boton.addActionListener(new InterrupcionDeAccion1());
21 CampoDeTexto.addActionListener(new
InterrupcionDeAccion1());
22 Lista.addActionListener(new InterrupcionDeAccion1());
23
24 MiFrame.add(MiPanel);
25 MiFrame.setSize(400,200);
26 MiFrame.show();
27
28 }
29 }

En el segundo ejemplo de eventos de acción se crea un GUI con tres


botones: “rojo”, “verde” y “azul”; al pulsar en cada uno de ellos el color del fondo
varía al color seleccionado. La clase PruebaEventosAccion2 proporciona el interfaz
gráfico de usuario con los tres botones (líneas 10 a 12) y les añade instancias de un
objeto de tratamiento de eventos de acción: InterrupcionDeAccion2 (líneas 18 a 20);
a través del constructor de esta clase se pasa la referencia del panel que registrará los
cambios de color a medida que se produzcan los eventos.

1 import java.awt.*;
2
3 public class PruebaEventosAccion2 {
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 8: EVENTOS 283

4
5 public static void main(String[] args) {
6
7 Frame MiFrame = new Frame("Prueba eventos de acción");
8 Panel MiPanel = new Panel();
9
10 Button Rojo = new Button("Rojo");
11 Button Verde = new Button("Verde");
12 Button Azul = new Button("Azul");
13
14 MiPanel.add(Rojo);
15 MiPanel.add(Verde);
16 MiPanel.add(Azul);
17
18 Rojo.addActionListener(new
InterrupcionDeAccion2(MiPanel));
19 Verde.addActionListener(new
InterrupcionDeAccion2(MiPanel));
20 Azul.addActionListener(new
InterrupcionDeAccion2(MiPanel));
21
22 MiFrame.add(MiPanel);
23 MiFrame.setSize(400,200);
24 MiFrame.show();
25
26 }
27 }

La clase InterrupcionDeAccion2 (línea 4) implementa el interfaz


ActionListener e incorpora un constructor (línea 8) que permite recoger la referencia
de un panel. El único método del interfaz se define en la línea 12, su implementación
consiste en identificar la fuente del evento (línea 16) y obtener su etiqueta (línea 17)
con la ayuda de los métodos getSource y getLabel. Una vez conocida la etiqueta
resulta sencillo modificar adecuadamente el color del panel (líneas 19 a 27).

1 import java.awt.event.*;
2 import java.awt.*;
3
4 public class InterrupcionDeAccion2 implements
ActionListener {
5
6 private Panel PanelPrincipal;
7
8 InterrupcionDeAccion2 (Panel PanelPrincipal) {
9 this.PanelPrincipal = PanelPrincipal;
10 }
11
12 public void actionPerformed(ActionEvent Evento){
284 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

13
14 Color ColorFondo;
15
16 Button Componente = (Button) Evento.getSource();
17 String ColorSeleccionado = Componente.getLabel();
18
19 if (ColorSeleccionado=="Rojo")
20 ColorFondo = Color.red;
21 else
22 if (ColorSeleccionado=="Verde")
23 ColorFondo = Color.green;
24 else
25 ColorFondo = Color.blue;
26
27 PanelPrincipal.setBackground(ColorFondo);
28 }
29
30
31 }

8.4.3 Eventos de enfoque

En este apartado vamos a desarrollar dos ejemplos que muestran el uso del
interfaz FocusListener, el adaptador FocusAdapter y el evento FocusEvent.

En el primer ejemplo utilizamos una clase de tratamiento de eventos de


enfoque (InterrupcionDeEnfoque1) que escribe un texto identificativo por consola
cada vez que se activa alguno de los métodos disponibles en el interfaz
FocusListener: focusGained y focusLost. Además hacemos uso de la clase
PruebaEventosEnfoque1 que define dos botones (líneas 9 y 10) y añade a uno de
ellos el tratamiento de eventos de enfoque desarrollado (línea 17).

1 import java.awt.*;
2
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 8: EVENTOS 285

3 public class PruebaEventosEnfoque1 {


4
5 public static void main(String[] args) {
6
7 Frame MiFrame = new Frame("Prueba eventos de enfoque");
8 Panel MiPanel = new Panel();
9 Button Boton1 = new Button("Componente 1");
10 Button Boton2 = new Button("Componente 2");
11 MiPanel.add(Boton1);
12 MiPanel.add(Boton2);
13 MiFrame.add(MiPanel);
14 MiFrame.setSize(300,200);
15 MiFrame.show();
16
17 Boton2.addFocusListener(new InterrupcionDeEnfoque1());
18
19 }
20 }

1 import java.awt.event.*;
2
3 public class InterrupcionDeEnfoque1 implements
FocusListener {
4
5 public void focusGained(FocusEvent Evento){
6 System.out.println("Se ha entrado en el componente");
7 }
8
9 public void focusLost(FocusEvent Evento){
10 System.out.println("Se ha salido del componente");
11 }
12
13 }

El segundo ejemplo de este apartado muestra la manera de obtener


información acerca del objeto origen cuando nos llega un evento que indica que se
ha “ganado” enfoque. De igual manera podríamos haber realizado la
implementación con el evento de pérdida de enfoque.
286 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

La clase PruebaEventosEnfoque2 define un interfaz gráfico de usuario con


un conjunto de botones y de campos de texto; a todos estos elementos se les añade
instancias del adaptador InterrupcionDeEnfoque2, de tipo FocusListener (líneas 19
y 20).

1 import java.awt.*;
2
3 public class PruebaEventosEnfoque2 {
4
5 public static void main(String[] args) {
6
7 final int NUM_FILAS = 5;
8 Button[] Boton = new Button[NUM_FILAS];
9 TextField[] Campo = new TextField[NUM_FILAS];
10
11 Frame MiFrame = new Frame("Prueba eventos de enfoque");
12 Panel MiPanel = new Panel(new GridLayout(NUM_FILAS,2));
13
14 for (int i=0;i<NUM_FILAS;i++) {
15 Boton[i] = new Button("Boton "+i);
16 Campo[i] = new TextField(15);
17 MiPanel.add(Boton[i]);
18 MiPanel.add(Campo[i]);
19 Boton[i].addFocusListener(new InterrupcionDeEnfoque2());
20 Campo[i].addFocusListener(new InterrupcionDeEnfoque2());
21 }
22
23 MiFrame.add(MiPanel);
24 MiFrame.setSize(400,200);
25 MiFrame.show();
26
27 }
28 }

La clase InterrupcionDeEnfoque2 extiende el adaptador FocusAdapter


(línea 4) con el fin de sobrecargar únicamente uno de sus métodos: focusGained
(línea 6). En la línea 7 se obtiene el elemento que ha generado el evento (usando el
método getSource); en la línea 8 se imprime su nombre y una descripción del
elemento que ha perdido el foco (usando el método getOppositeComponent).

1 import java.awt.event.*;
2 import java.awt.*;
3
4 public class InterrupcionDeEnfoque2 extends FocusAdapter {
5
6 public void focusGained(FocusEvent Evento){
7 Component Componente = (Component) Evento.getSource();
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 8: EVENTOS 287

8 System.out.println("Se ha entrado en " +


Componente.getName() + "\nsaliendo de " +
Evento.getOppositeComponent() + "\n");
9 }
10
11 }

8.4.4 Eventos de elemento

En este apartado vamos a desarrollar dos ejemplos que muestran el uso del
interfaz ItemListener y el evento ItemEvent. En el primer ejemplo utilizamos la clase
PruebaEventosElemento1 que crea cuatro cajas de texto (líneas 10 a 13) y les añade
la funcionalidad implementada en la clase InterrupcionDeElemento1 (líneas 20 a
23).

1 import java.awt.*;
2
3 public class PruebaEventosElemento1 {
4
5 public static void main(String[] args) {
6
7 Frame MiFrame = new Frame("Prueba eventos de
elemento");
288 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

8 Panel MiPanel = new Panel(new GridLayout(4,1));


9
10 Checkbox Diesel = new Checkbox("Diesel", true);
11 Checkbox FarosXenon = new Checkbox("Faros de Xenon",
false);
12 Checkbox LlantasAleacion = new Checkbox("Llantas de
aleacion", false);
13 Checkbox PinturaMetalizada = new Checkbox("Pintura
Metalizada", true);
14
15 MiPanel.add(Diesel);
16 MiPanel.add(FarosXenon);
17 MiPanel.add(LlantasAleacion);
18 MiPanel.add(PinturaMetalizada);
19
20 Diesel.addItemListener(new InterrupcionDeElemento1());
21 FarosXenon.addItemListener(new
InterrupcionDeElemento1());
22 LlantasAleacion.addItemListener(new
InterrupcionDeElemento1());
23 PinturaMetalizada.addItemListener(new
InterrupcionDeElemento1());
24
25 MiFrame.add(MiPanel);
26 MiFrame.setSize(400,200);
27 MiFrame.show();
28
29 }
30 }

La clase InterrupcionDeElemento1 implementa el único método del interfaz


ItemListener: itemStateChanged (línea 6). En primer lugar obtenemos la caja de
texto que ha producido el evento (línea 8), para ello utilizamos el método getSource
sobre el evento que nos llega. Podemos consultar el estado de la caja de texto
utilizando el método getState perteneciente a la case Checkbox (línea 9) o bien
usando getStateChange perteneciente a la clase ItemEvent (línea 10). El elemento
que ha generado el evento se puede obtener, además de mediante getSource,
utilizando getItem (línea 12).

1 import java.awt.event.*;
2 import java.awt.*;
3
4 public class InterrupcionDeElemento1 implements
ItemListener {
5
6 public void itemStateChanged(ItemEvent Evento){
7
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 8: EVENTOS 289

8 Checkbox Componente = (Checkbox) Evento.getSource();


9 boolean Estado = Componente.getState();
10 int NuevoEstado = Evento.getStateChange();
11
12 System.out.println("Componente: " + Evento.getItem());
13 System.out.println("Estado actual: " + Estado);
14 if (NuevoEstado==ItemEvent.SELECTED)
15 System.out.println("Seleccionado");
16 else
17 System.out.println(" No seleccionado");
18 System.out.println();
19 }
20
21 }

El segundo ejemplo de este apartado muestra la manera de programar


opciones y validaciones interactivas en interfaces gráficos de usuario. La clase
PruebaEventosElemento2 crea un GUI con 5 cajas de verificación (líneas 13 a 18),
una lista (línea 20 a 24), una lista desplegable (líneas 26 a 29) y una etiqueta (línea
31). Todos estos elementos se asignan a un vector de componentes (líneas 34 a 41) y
se les añaden instancias de la clase de tratamiento de eventos de elemento
InterrupcionDeElemento2 (líneas 54 a 60).

1 import java.awt.*;
2
3 public class PruebaEventosElemento2 {
4
5 public static void main(String[] args) {
6
7 Frame MiFrame = new Frame("Prueba eventos de
elemento");
8 Panel PanelPrincipal = new Panel(new
FlowLayout(FlowLayout.LEFT));
9 Panel PanelIzq = new Panel(new GridLayout(6,1));
10
11 Component[] Componentes = new Component[8];
290 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

12
13 Checkbox Diesel = new Checkbox("Diesel", true);
14 Checkbox AsientosDeportivos = new Checkbox("Asientos
15 deportivos", false);
16 Checkbox TapiceriaCuero = new Checkbox("Tapiceria de
cuero", false);
17 Checkbox LlantasAleacion = new Checkbox("Llantas de
aleación", false);
18 Checkbox PinturaMetalizada = new Checkbox("Pintura
metalizada", true);
19
20 Choice Llantas = new Choice();
21 Llantas.add("Tres radios");
22 Llantas.add("Cinco radios");
23 Llantas.add("Siete radios");
24 Llantas.setEnabled(false);
25
26 List ColoresMetalizados = new List(2);
27 ColoresMetalizados.add("Rojo");
28 ColoresMetalizados.add("Azul");
29 ColoresMetalizados.add("Verde");
30
31 Label Seleccionado = new Label("Seleccionado:");
32 Seleccionado.setForeground(Color.red);
33
34 Componentes[0] = Diesel;
35 Componentes[1] = AsientosDeportivos;
36 Componentes[2] = TapiceriaCuero;
37 Componentes[3] = LlantasAleacion;
38 Componentes[4] = PinturaMetalizada;
39 Componentes[5] = Llantas;
40 Componentes[6] = ColoresMetalizados;
41 Componentes[7] = Seleccionado;
42
43 PanelPrincipal.add(PanelIzq);
44 PanelPrincipal.add(ColoresMetalizados);
45 PanelPrincipal.add(Llantas);
46
47 PanelIzq.add(Diesel);
48 PanelIzq.add(AsientosDeportivos);
49 PanelIzq.add(TapiceriaCuero);
50 PanelIzq.add(LlantasAleacion);
51 PanelIzq.add(PinturaMetalizada);
52 PanelIzq.add(Seleccionado);
53
54 Diesel.addItemListener(new
InterrupcionDeElemento2(Componentes));
55 AsientosDeportivos.addItemListener(new
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 8: EVENTOS 291

InterrupcionDeElemento2(Componentes));
56 TapiceriaCuero.addItemListener(new
InterrupcionDeElemento2(Componentes));
57 LlantasAleacion.addItemListener(new
InterrupcionDeElemento2(Componentes));
58 PinturaMetalizada.addItemListener(new
InterrupcionDeElemento2(Componentes));
59 Llantas.addItemListener(new
InterrupcionDeElemento2(Componentes));
60 ColoresMetalizados.addItemListener(new
InterrupcionDeElemento2(Componentes));
61
62 MiFrame.add(PanelPrincipal);
63 MiFrame.setSize(500,200);
64 MiFrame.show();
65
66 }

La clase InterrupcionDeElemento2 implementa el interfaz ItemListener


(línea 4) y admite un constructor (línea 8) que recoge el vector de componentes con
el fin de actuar sobre los mismos. El único método del interfaz (itemStateChanged)
se define en la línea 12; su funcionalidad es la siguiente:

Si el elemento que ha generado el evento esta definido como “Llantas de


aleación”, la lista Llantas (Componentes[5]) se habilita en caso de que la caja de
verificación este seleccionada (SELECTED) o se deshabilita en caso de que la caja
de verificación no esté seleccionada (líneas 16 a 20). La misma lógica se aplica con
la caja de verificació n “Pintura metalizada”, a través de la cual se controla si la lista
desplegable ColoresMetalizados (Componentes[6]) se hace visible o no (líneas 22 a
26).

La activación de la caja de verificación “Asientos Deportivos” (línea 28)


controla el estado y disponibilidad de la caja de verificación “Tapicería de cuero”
(Componentes[2]), de esta manera, si la primera se selecciona (línea 30), la segunda
se selecciona automáticamente (línea 31) y se limita su disponibilidad (línea 32).

La etiqueta Seleccionado muestra en cada instante un texto relativo al


elemento que ha sido seleccionado más recientemente (líneas 38 y 39).

1 import java.awt.event.*;
2 import java.awt.*;
3
4 public class InterrupcionDeElemento2 implements
ItemListener {
5
6 Component[] Componentes = new Component[8];
292 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

7
8 InterrupcionDeElemento2(Component[] Componentes) {
9 this.Componentes = Componentes;
10 }
11
12 public void itemStateChanged(ItemEvent Evento){
13
14 int Estado = Evento.getStateChange();
15
16 if (Evento.getItem().equals("Llantas de aleación"))
17 if (Estado==ItemEvent.SELECTED)
18 Componentes[5].setEnabled(true);
19 else
20 Componentes[5].setEnabled(false);
21
22 if (Evento.getItem().equals("Pintura metalizada"))
23 if (Estado==ItemEvent.SELECTED)
24 Componentes[6].setVisible(true);
25 else
26 Componentes[6].setVisible(false);
27
28 if (Evento.getItem().equals("Asientos deportivos")) {
29 Checkbox Asientos = (Checkbox) Componentes[2];
30 if (Estado==ItemEvent.SELECTED) {
31 Asientos.setState(true);
32 Componentes[2].setEnabled(false);
33 }
34 else
35 Componentes[2].setEnabled(true);
36 }
37
38 Label Seleccionado = (Label) Componentes[7];
39 Seleccionado.setText(""+Evento.getItem());
40 }
41
42 }
CAPÍTULO 9

APLICACIONES DE EJEMPLO

9.1 EJEMPLO: CALCULADORA


9.1.1 Definición del ejemplo

Vamos a implementar una aplicación que ofrezca el interfaz y el


comportamiento de una calculadora sencilla. Nuestra calculadora contendrá los diez
dígitos (del cero al nueve), el punto decimal, el signo de igual y los operadores de
suma, resta, multiplicación y división; además se proporcionará un espacio para
visualizar las pulsaciones del usuario y los resultados obtenidos. Nuestro interfaz
gráfico de usuario debe, por tanto, mostrar un aspecto similar al siguiente:

El comportamiento de la calculadora también será sencillo. Su esquema


básico de funcionamiento lo programaremos con el siguiente patrón:
294 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

Por ejemplo, podemos ir pulsando la secuencia: 8*4=, obteniendo el


resultado 32; posteriormente ir pulsando la secuencia –2=, obteniendo 30 y así
sucesivamente.

Los operandos podrán ser valores numéricos (enteros o decimales) con


signo, por ejemplo 8, 8.2, -3, -309.6, 108.42345. Un operando del tipo definido lo
podemos modelizar de la siguiente manera:

-
Operando
Dígito . Dígito

Por último, deseamos que la calculadora cumpla las siguientes condiciones:


• Diseñada según los principios básicos de la programación orientada
a objetos
• Utilización amplia de las posibilidades de Java que han sido
estudiadas
• Los botones pueden definirse de diversos colores
• Cuando nos situamos sobre un botón, éste debe cambiar de color y
al salir del mismo recuperar el color original. Este comportamiento
se debe cumplir tanto al usar el teclado como al usar el ratón
• Cuando pulsemos una opción no válida (por ejemplo un dígito
después del signo igual, dos operadores seguidos, etc.) nos muestre
una indicación de error en el área de resultados y el botón pulsado
de color rojo

9.1.2 Diseño del interfaz gráfico de usuario

Para realizar el diseño de la aplicación estableceremos en primer lugar la


forma de crear el interfaz gráfico de usuario, posteriormente decidiremos como
estructurar las clases que implementarán su comportamiento. El in terfaz gráfico de
usuario lo vamos a basar en tres clases muy sencillas: Digitos, Operadores y
Resultados, la primera define un panel con botones que representan los dígitos del 0
al 9, el signo punto y el igual; la clase operadores define un panel con 4 botones de
signos: +, -, *, /; Resultados se encarga del visor de la calculadora.
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 9: APLICACIONES DE EJEMPLO 295

Utilizando las clases Digitos, Operadores y Resultados podemos definir


diferentes representaciones de calculadoras, implementando clases
GUICalculadora1, GUICalculadora2, etc. El esquema de clases, hasta el momento,
nos queda de la siguiente manera:

GUICalculadora2

GUICalculadora1

Digitos Resultados

Operadores

Las clases GUICalculadorax necesitan tener acceso a los botones


individuales que se definen en las clases Digitos y Operadores, de esta manera se
podrá asignar diferentes objetos de tratamiento de eventos a los botones. También
resulta necesario que las clases Digitos y Operadores proporcionen sus respectivos
paneles con la estructura de botones. Finalmente, para permitir que los botones de
cada panel presenten el color deseado, se proporciona un constructor que admite un
parámetro de tipo Color.

La clase Resultados proporciona un campo de texto deshabilitado que hará


las veces de visor de la calculadora. A continuación se muestra el diseño de las
clases Digitos, Operadores y Resultados:
296 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

9.1.3 Implementación del interfaz gráfico de usuario

La clase Digitos crea un panel (línea 8) en el que introduce 12 botones


(líneas 9, 10 y 23). El panel es accesible a través del método DamePanel (línea 31) y
los botones son accesibles a través del método DameBotones (línea 35). El color de
los botones se determina en la llamada al constructor con parámetro (líneas 12 y 22);
el constructor vacío asigna el color gris claro (líneas 27 y 28).

Los botones se sitúan en una disposición GridLayout de 4 x 3 (líneas 13 y


20); las tres primeras filas albergan los dígitos de 0 a 8 (líneas 14 a 16) y la tercera el
dígito 9, el punto y el igual (líneas 17 a 19).

1 import java.awt.Button;
2 import java.awt.Panel;
3 import java.awt.GridLayout;
4 import java.awt.Color;
5
6 public class Digitos {
7
8 private Panel MiPanel = new Panel();
9 private final int NUM_DIGITOS=12;
10 private Button[] Digito = new Button[NUM_DIGITOS];
11
12 Digitos(Color ColorBotones) {
13 GridLayout LayoutBotones = new GridLayout(4,3);
14 for (int fila=0;fila<3;fila++)
15 for (int col=0;col<3;col++)
16 Digito[fila*3+col] = new
Button(String.valueOf(fila*3+col));
17 Digito[9] = new Button("9");
18 Digito[10] = new Button(".");
19 Digito[11] = new Button("=");
20 MiPanel.setLayout(LayoutBotones);
21 for (int i=0;i<NUM_DIGITOS;i++) {
22 Digito[i].setBackground(ColorBotones);
23 MiPanel.add(Digito[i]);
24 }
25 }
26
27 Digitos() {
28 this(Color.lightGray);
29 }
30
31 public Panel DamePanel() {
32 return MiPanel;
33 }
34
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 9: APLICACIONES DE EJEMPLO 297

35 public Button[] DameBotones() {


36 return Digito;
37 }
38
39 }

Las clases Operadores y Resultados siguen el mismo esquema que Digitos:

1 import java.awt.Button;
2 import java.awt.Panel;
3 import java.awt.GridLayout;
4 import java.awt.Color;
5
6 public class Operadores {
7
8 private Panel MiPanel = new Panel();
9 private final int NUM_OPERADORES=4;
10 private Button[] Operador = new Button[NUM_OPERADORES];
11
12 Operadores(Color ColorBotones) {
13 GridLayout LayoutBotones = new
GridLayout(NUM_OPERADORES,1);
14 Operador[0] = new Button("+");
15 Operador[1] = new Button("-");
16 Operador[2] = new Button("*");
17 Operador[3] = new Button("/");
18 MiPanel.setLayout(LayoutBotones);
19 for (int i=0;i<NUM_OPERADORES;i++) {
20 Operador[i].setBackground(ColorBotones);
21 MiPanel.add(Operador[i]);
22 }
23 }
24
25 Operadores() {
26 this(Color.lightGray);
27 }
28
29 public Panel DamePanel() {
30 return MiPanel;
31 }
32
33 public Button[] DameBotones() {
34 return Operador;
35 }
36
37 }
298 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

1 import java.awt.*;
2
3 public class Resultados {
4
5 private Panel MiPanel = new Panel();
6 private TextField Resultado = new TextField("",10);
7
8 Resultados(Color ColorEtiqueta) {
9 FlowLayout LayoutResultado = new
FlowLayout(FlowLayout.LEFT);
10 MiPanel.setLayout(LayoutResultado);
11 Resultado.setForeground(ColorEtiqueta);
12 MiPanel.add(Resultado);
13 Resultado.setEnabled(false);
14 }
15
16 Resultados() {
17 this(Color.black);
18 }
19
20 public Panel DamePanel() {
21 return MiPanel;
22 }
23
24 public TextField DameCampo() {
25 return Resultado;
26 }
27
28 }

La clase GUICalculadora1 implementa el interfaz de calculadora que se ha


presentado en el primer apartado: “Definición del ejemplo”. Primero se define un
panel con disposición BorderLayout (líneas 7 a 9), posteriormente se crea una
instancia de la clase Digitos, otra de la clase Operadores y una tercera de la clase
Resultados (líneas 11 a 13). Finalmente se obtienen los paneles de cada una de las
instancias y se situan en las posic iones este, centro y norte de nuestro panel (líneas
16 a 18).

1 import java.awt.*;
2
3 public class GUICalculadora1 {
4
5 GUICalculadora1() {
6 Frame MiMarco = new Frame();
7 Panel MiPanel = new Panel();
8 BorderLayout PuntosCardinales = new BorderLayout();
9 MiPanel.setLayout(PuntosCardinales);
10
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 9: APLICACIONES DE EJEMPLO 299

11 Digitos InstanciaDigitos = new Digitos(Color.orange);


12 Operadores InstanciaOperadores = new
Operadores(Color.magenta);
13 Resultados InstanciaResultados = new Resultados();
14
15 MiMarco.add(MiPanel);
16 MiPanel.add(InstanciaOperadores.DamePanel(),
BorderLayout.EAST);
17 MiPanel.add(InstanciaDigitos.DamePanel(),
BorderLayout.CENTER);
18 MiPanel.add(InstanciaResultados.DamePanel(),
BorderLayout.NORTH);
19
20 // Aqui prepararemos el tratamiento de eventos
21
22 MiMarco.setSize(150,150);
23 MiMarco.setTitle("Calculadora");
24 MiMarco.setVisible(true);
25 }
26 }

1 public class Calculadora1 {


2
3 public static void main(String[] args) {
4 GUICalculadora1 MiCalculadora = new GUICalculadora1();
5 }
6 }

Utilizando las clases Digitos, Operadores y Resultados podemos crear de


forma muy sencilla nuevas apariencia s de calculadoras:

1 import java.awt.*;
2
3 public class GUICalculadora2 {
4
5 GUICalculadora2() {
6 Frame MiMarco = new Frame();
7 Panel MiPanel = new Panel();
8 FlowLayout TodoSeguido = new
FlowLayout(FlowLayout.CENTER);
9 MiPanel.setLayout(TodoSeguido);
10
11 Digitos PanelDigitos = new Digitos(Color.cyan);
12 Operadores PanelOperadores = new
Operadores(Color.green);
13 Resultados PanelResultados = new Resultados(Color.red);
14
15 MiMarco.add(MiPanel);
300 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

16 MiPanel.add(PanelOperadores.DamePanel());
17 MiPanel.add(PanelDigitos.DamePanel());
18 MiPanel.add(PanelResultados.DamePanel());
19
20 MiMarco.setSize(250,150);
21 MiMarco.setTitle("Calculadora");
22 MiMarco.setVisible(true);
23 }
24 }

9.1.4 Diseño del tratamiento de eventos

La calculadora que estamos desarrollando en este ejemplo va a atender a tres


tipos de eventos diferentes:
• Eventos de ventana
• Eventos de enfoque
• Eventos de ratón

Los dos primeros tipos de eventos van a recibir un tratamiento muy sencillo,
mientras que el tercer tipo, eventos de ratón, va a incorporar la mayor parte del
dinamismo de la aplicación.

La clase de tratamiento de eventos de ventana atenderá al método


windowClosing, con el fin de permitir la finalización de la aplicación. La clase de
tratamiento de eventos de enfoque implementará los dos métodos del interfaz:
focusGained y focusLost, de esta manera conseguiremos cambiar el color de los
botones cuando nos situemos sobre ellos con el teclado: verde al colocarse sobre
cada uno de ellos y su color original (proporcionado a través del constructor) al salir
de los mismos. El diseño de las clases queda de la siguiente manera:
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 9: APLICACIONES DE EJEMPLO 301

La clase de tratamiento de eventos de ratón (ControlRaton) contiene un


constructor que permite indicar el color del fondo de los botones y el campo de texto
que hace de visor de la calculadora. El primer parámetro sirve para que el método
mouseExited restituya el color de cada botón al salir del mismo (mouseEntered pone
de color verde cada botón en el que se coloca el puntero del ratón). El segundo
parámetro (TextField ) permite que el método mouseClicked sitúe en el visor un
mensaje de situación errónea (circunstancia que conoce por la llegada de la
excepción OpcionErronea).
302 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

9.1.5 Implementación de las clases de tratamiento de eventos

La clase controlVentana se encarga de finalizar la aplicación (línea 6)


cuando el usuario selecciona una opción de cierre de la ventana (línea 5).

1 import java.awt.event.*;
2
3 public class ControlVentana extends WindowAdapter {
4
5 public void windowClosing(WindowEvent EventoQueLlega){
6 System.exit(0);
7 }
8
9 }

La clase controlFoco se encarga de variar el color de los botones a medida


que el usuario se desplaza por los mismos usando el teclado. El método focusGained
(línea 12) pone los botones por los que se pasa en color verde y el método focusLost
(línea 17) los pone, al salir, del color original (línea 19).

1 import java.awt.event.*;
2 import java.awt.*;
3
4 public class ControlFoco implements FocusListener {
5
6 private Color ColorBoton;
7
8 ControlFoco(Color ColorBoton) {
9 this.ColorBoton = ColorBoton;
10 }
11
12 public void focusGained(FocusEvent EventoQueLlega){
13 Button Boton = (Button) EventoQueLlega.getSource();
14 Boton.setBackground(Color.green);
15 }
16
17 public void focusLost(FocusEvent EventoQueLlega){
18 Button Boton = (Button) EventoQueLlega.getSource();
19 Boton.setBackground(ColorBoton);
20 }
21
22 }

El último de los controladores de eventos utilizados en nuestro ejemplo es


ControlRaton, de tipo MouseAdapter. Su constructor (línea 9) permite indicar el
color de los botones y el visor (de tipo TextField) que se está utilizando. Los
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 9: APLICACIONES DE EJEMPLO 303

métodos mouseEntered (línea 26) y mouseExited (línea 31) realizan en esta clase la
misma acción que focusGained y focusLost en la clase anterior, de esta forma, el
color de los botones también varía al desplazarnos con el ratón.

El método mouseClicked (línea 14) obtiene el botón que generó el evento


(línea 15) y posteriormente extrae el primer carácter de su etiqueta (línea 16). El
carácter obtenido (0,1,...,9,+,-,*,/,.,=) se le pasa a un método de la clase Automata
para que lo procese; esta clase se explica en el siguiente apartado; se puede producir
una excepción del tipo OpcionErronea si el botón seleccionado es inadecuado (línea
20) en cuyo caso se coloca un mensaje de error sobre el visor (línea 21) y se asigna
el color rojo al botón que ha sido pulsado (línea 22).

1 import java.awt.event.*;
2 import java.awt.*;
3
4 public class ControlRaton extends MouseAdapter {
5
6 private TextField Resultado;
7 private Color ColorBoton;
8
9 ControlRaton(TextField Resultado,Color ColorBoton) {
10 this.Resultado = Resultado;
11 this.ColorBoton = ColorBoton;
12 }
13
14 public void mouseClicked(MouseEvent EventoQueLlega){
15 Button Boton = (Button) EventoQueLlega.getSource();
16 char Car = Boton.getLabel().charAt(0);
17 System.out.print(Car);
18 try {
19 Automata.CaracterIntroducido(Car);
20 } catch(OpcionErronea e) {
21 Resultado.setText(e.getMessage());
22 Boton.setBackground(Color.red);
23 }
24 }
25
26 public void mouseEntered(MouseEvent EventoQueLlega){
27 Button Boton = (Button) EventoQueLlega.getSource();
28 Boton.setBackground(Color.green);
29 }
30
31 public void mouseExited(MouseEvent EventoQueLlega){
32 Button Boton = (Button) EventoQueLlega.getSource();
33 Boton.setBackground(ColorBoton);
34 }
35 }
304 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

Para finalizar este apartado se presenta la clase GUICalculadora1 completa,


con los tratamientos de eventos asociados a los botones. En las líneas 20 a 22 se
obtienen las referencias de los botones y el campo de texto que componen la
calculadora, para ello se hace uso de los métodos DameBotones y DameCampo
pertenecientes a las clases Digitos, Operadores y Resultados. En la línea 23 se
instancia el autómata de control que veremos en el próximo apartado.

En las líneas 25 a 35 se asocian instancias de las clases de tratamientos de


eventos ControlRaton y ControlFoco a todos los botones de la calculadora;
finalmente, en la línea 37 se asocia una instancia de la clase ControlVentana al
marco principal de la aplicación (MiMarco).

1 import java.awt.*;
2
3 public class GUICalculadora1 {
4
5 GUICalculadora1() {
6 Frame MiMarco = new Frame();
7 Panel MiPanel = new Panel();
8 BorderLayout PuntosCardinales = new BorderLayout();
9 MiPanel.setLayout(PuntosCardinales);
10
11 Digitos InstanciaDigitos = new Digitos(Color.orange);
12 Operadores InstanciaOperadores = new
Operadores(Color.magenta);
13 Resultados InstanciaResultados = new Resultados();
14
15 MiMarco.add(MiPanel);
16 MiPanel.add(InstanciaOperadores.DamePanel(),
BorderLayout.EAST);
17 MiPanel.add(InstanciaDigitos.DamePanel(),
BorderLayout.CENTER);
18 MiPanel.add(InstanciaResultados.DamePanel(),
BorderLayout.NORTH);
19
20 Button[] BotonesDigitos =
InstanciaDigitos.DameBotones();
21 Button[] BotonesOperadores =
InstanciaOperadores.DameBotones();
22 TextField Resultado = InstanciaResultados.DameCampo();
23 Automata InstanciaAutomata = new Automata(Resultado);
24
25 for (int i=0;i<BotonesDigitos.length;i++) {
26 BotonesDigitos[i].addMouseListener(new
27 ControlRaton(Resultado,Color.orange));
28 BotonesDigitos[i].addFocusListener(new
ControlFoco(Color.orange));
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 9: APLICACIONES DE EJEMPLO 305

29 }
30
31 for (int i=0;i<BotonesOperadores.length;i++) {
32 BotonesOperadores[i].addMouseListener(new
33 ControlRaton(Resultado,Color.magenta));
34 BotonesOperadores[i].addFocusListener(new
ControlFoco(Color.magenta));
35 }
36
37 MiMarco.addWindowListener(new ControlVentana());
38
39 MiMarco.setSize(150,150);
40 MiMarco.setTitle("Calculadora");
41 MiMarco.setVisible(true);
42 }
43
44 }

9.1.6 Diseño del control

Hasta ahora hemos resuelto la manera de visualizar la calculadora y la forma


de interactuar con el usuario recogiendo sus pulsaciones de ratón, modificando los
colores de los botones a medida que se pasa por ellos, etc. Lo único que nos queda
por hacer es implementar la lógica de control de la calculadora: utilizar las
pulsaciones del usuario como entrada y proporcionar resultados como salida.

La clase que recoge las pulsaciones que realiza el usuario es ControlRaton,


que se encarga del tratamiento de eventos de ratón; para no mezclar su función
básica (recoger eventos) con el tratamiento (control) de los mismos crearemos una
clase Automata que realice esta última labor; ControlRaton le pasará en forma de
caracteres (‘0’, ‘1’, ..., ‘9’, ‘.’, ‘+’, ‘-‘, ‘*’, ‘/’,’=’) las pulsaciones del usuario y
Automata las procesará. En caso de que el usuario pulse un botón inadecuado (por
ejemplo dos operandos seguidos) Automata generará una excepción OpcionErronea
y ControlRaton se encargará de recogerla. OpcionErronea es una excepción que nos
definimos nosotros mismos.

ControlRaton

Automata

OpcionErronea
306 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

El esquema completo de clases de la aplicación nos queda de la siguiente


manera:

GUICalculadora1
ControlFoco
ControlVentana
Digitos Resultados
ControlRaton

Automata
Operadores
OpcionErronea

Siguiendo el modelo gráfico con el que fomalizabamos el formato de un


número, podemos establecer el control completo de la calculadora. Recordemos que
el esquema general es:

... y el de un número:

-
Operando
Dígito . Dígito

Combinando los dos grafos anteriores podemos obtener con facilidad el


autómata de estados en el que nos vamos a basar para implementar el control de la
calculadora. Las cajas representan los diferentes estados en los que nos podemos
encontrar y las flechas son las transiciones permitidas desde cada uno de esos
estados. Numerando los estados, en el gráfico siguiente, podemos expresar, por
ejemplo, que en el estado 5 hemos recibido un número y un operador y estamos a la
espera de un dígito o del signo menos.
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 9: APLICACIONES DE EJEMPLO 307

Siguiendo el esquema nos resulta muy sencillo saber como vamos


evolucionando según el usuario pulsa botones; también resulta inmediato conocer
las pulsaciones permitidas en cada situación (por ejemplo, en el estado 7 solo
podemos admitir como válidas las pulsaciones en los botones punto, igual o
cualquier dígito).

9.1.7 Implementación del control

La clase Automata sigue fielmente el grafo de estados que hemos diseñado.


La clase ControlRaton invoca al método CaracterIntroducid o (línea 15) por cada
pulsación de botón que realiza el usuario. La cuestión más importante en este
momento es darse cuenta de que el estado del autómata debe mantenerse entre
308 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

pulsación y pulsación del usuario, por lo que debemos emplear propiedades que
tengan existencia durante toda la aplicación: no tendría sentido guardar el estado del
autómata, el valor de los operandos, etc. en variables locales al método
CaracterIntroducido.

Las propiedades que necesitaremos para almacenar el estado del autómata


son: Estado (indica el valor numérico del estado en el que nos encontramos), Visor
(contiene los últimos caracteres pulsados), Operador (operador seleccionado),
Operando1 y Operando2 (valores a partir de los que se calcula el resultado).

A modo de ejemplo hemos definido estas propiedades como estáticas, así


como el método CaracterIntroducido que las utiliza: la aplicación funciona
correctamente y evitamos tener que pasar la referencia de la instancia del autómata
hasta la clase ControlRaton, sin embargo debemos tener en cuenta que si en una
misma aplicación deseamos crear más de una calculadora, el control no funcionaría,
puesto que todas las calculadoras de la aplicación compartirían las mismas
propiedades de estado. Si cada calculadora se encontrase en una aplicación separada
si funcionaría, puesto que cada calculadora se ejecutaría sobre una JVM diferente.

Evitar las propiedades estáticas en nuestra aplicación es muy sencillo, basta


con pasar a la clase ControlRaton la referencia del objeto Automata y, en su línea
19, utilizar la referencia en lugar del nombre de la clase.

El código de la clase Automata sigue el diseño realizado: su método


CaracterIntroducido consulta en primer lugar el estado en el que se encuentra el
autómata (línea 19) para realizar un tratamiento u otro (marcado por el grafo de
estados desarrollado en el apartado anterior); se consulta el carácter que nos llega
(líneas 22, 48, etc.), correspondiente al último botón pulsado por el usuario y se
actúa en consecuencia.

Por ejemplo, si estamos en el estado 1 (línea 47) y nos llega un dígito (líneas
49 a 58), pasamos al estado 2 (tal y como marca el grafo de estados) y actualizamos
el visor de la calculadora (línea 60); si nos llega un carácter distinto a un dígito
(línea 62) nos encontramos ante un error del usuario, por lo que volvemos al estado
0 del autómata (línea 63) y levantamos la excepción OpcionErronea (línea 64).

Los demás estados se tratan de una manera análoga al explicado, en todos se


levanta la misma excepción (OpcionErronea) cuando llega un carácter equivocado.
Resultaría muy sencillo y útil pasarle a la excepción un texto indicando los
caracteres permitidos en cada caso: por ejemplo en el estado 10: throw new
OpcionErronea(“+-*/”), incorporando un constructor con parámetro de tipo String
a la excepción OpcionErronea.
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 9: APLICACIONES DE EJEMPLO 309

Los resultados se calculan cuando nos encontramos en el estado 9 (línea


260) y nos llega el carácter ‘=’ (línea 262): pasamos al estado 10 (línea 263),
obtenemos el segundo operando a partir de los caracteres almacenados en el visor
(línea 264), calculamos el resultado (línea 265), lo visualizamos (línea 266) y
asignamos el resultado como primer operando para la siguiente operación (línea
267).

La clase OpcionErronea se ha mantenido lo más sencilla posible;


únicamente almacena el texto “No valido” como indicación de la causa de la
excepción, que siempre levantamos cuando el usuario pulsa un botón inapropiado.
El código de esta clase se muestra al final del apartado.

1 import java.awt.TextField;
2
3 public class Automata {
4
5 private static byte Estado=0;
6 private static TextField Visor;
7 private static double Operando1=0d;
8 private static double Operando2=0d;
9 private static char Operador=' ';
10
11 Automata(TextField Visor) {
12 this.Visor = Visor;
13 }
14
15 public static void CaracterIntroducido(char Car) throws
OpcionErronea {
16 if (Visor.getText().equals("No valido"))
17 Visor.setText("");
18
19 switch(Estado) {
20
21 case 0:
22 switch(Car) {
23 case '-':
24 Estado=1;
25 Visor.setText(Visor.getText()+Car);
26 break;
27 case '0':
28 case '1':
29 case '2':
30 case '3':
31 case '4':
32 case '5':
33 case '6':
34 case '7':
310 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

35 case '8':
36 case '9':
37 Estado=2;
38 Visor.setText(Visor.getText()+Car);
39 break;
40 default:
41 Iniciar();
42 throw new OpcionErronea();
43 }
44 break;
45
46
47 case 1:
48 switch(Car) {
49 case '0':
50 case '1':
51 case '2':
52 case '3':
53 case '4':
54 case '5':
55 case '6':
56 case '7':
57 case '8':
58 case '9':
59 Estado=2;
60 Visor.setText(Visor.getText()+Car);
61 break;
62 default:
63 Iniciar();
64 throw new OpcionErronea();
65 }
66 break;
67
68
69 case 2:
70 switch(Car) {
71 case '.':
72 Estado=3;
73 Visor.setText(Visor.getText()+Car);
74 break;
75 case '+':
76 case '-':
77 case '*':
78 case '/':
79 Estado=5;
80 Operador=Car;
81 Operando1=Double.parseDouble(Visor.getText());
82 Visor.setText("");
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 9: APLICACIONES DE EJEMPLO 311

83 break;
84 case '0':
85 case '1':
86 case '2':
87 case '3':
88 case '4':
89 case '5':
90 case '6':
91 case '7':
92 case '8':
93 case '9':
94 Estado=2;
95 Visor.setText(Visor.getText()+Car);
96 break;
97 default:
98 Iniciar();
99 throw new OpcionErronea();
100 }
101 break;
102
103
104 case 3:
105 switch(Car) {
106 case '0':
107 case '1':
108 case '2':
109 case '3':
110 case '4':
111 case '5':
112 case '6':
113 case '7':
114 case '8':
115 case '9':
116 Estado=4;
117 Visor.setText(Visor.getText()+Car);
118 break;
119 default:
120 Iniciar();
121 throw new OpcionErronea();
122 }
123 break;
124
125
126 case 4:
127 switch(Car) {
128 case '+':
129 case '-':
130 case '*':
312 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

131 case '/':


132 Estado=5;
133 Operador=Car;
134 Operando1=Double.parseDouble(Visor.getText());
135 Visor.setText("");
136 break;
137 case '0':
138 case '1':
139 case '2':
140 case '3':
141 case '4':
142 case '5':
143 case '6':
144 case '7':
145 case '8':
146 case '9':
147 Estado=4;
148 Visor.setText(Visor.getText()+Car);
149 break;
150 default:
151 Iniciar();
152 throw new OpcionErronea();
153 }
154 break;
155
156
157 case 5:
158 switch(Car) {
159 case '-':
160 Estado=6;
161 Visor.setText(Visor.getText()+Car);
162 break;
163 case '0':
164 case '1':
165 case '2':
166 case '3':
167 case '4':
168 case '5':
169 case '6':
170 case '7':
171 case '8':
172 case '9':
173 Estado=7;
174 Visor.setText(Visor.getText()+Car);
175 break;
176 default:
177 Iniciar();
178 throw new OpcionErronea();
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 9: APLICACIONES DE EJEMPLO 313

179 }
180 break;
181
182
183 case 6:
184 switch(Car) {
185 case '0':
186 case '1':
187 case '2':
188 case '3':
189 case '4':
190 case '5':
191 case '6':
192 case '7':
193 case '8':
194 case '9':
195 Estado=7;
196 Visor.setText(Visor.getText()+Car);
197 break;
198 default:
199 Iniciar();
200 throw new OpcionErronea();
201 }
202 break;
203
204
205 case 7:
206 switch(Car) {
207 case '.':
208 Estado=8;
209 Visor.setText(Visor.getText()+Car);
210 break;
211 case '=':
212 Estado=10;
213 Operando2=Double.parseDouble(Visor.getText());
214 double Resultado=ObtenerResultado();
215 Visor.setText(String.valueOf(Resultado));
216 Operando1=Resultado;
217 break;
218 case '0':
219 case '1':
220 case '2':
221 case '3':
222 case '4':
223 case '5':
224 case '6':
225 case '7':
226 case '8':
314 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

227 case '9':


228 Estado=7;
229 Visor.setText(Visor.getText()+Car);
230 break;
231 default:
232 Iniciar();
233 throw new OpcionErronea();
234 }
235 break;
236
237
238 case 8:
239 switch(Car) {
240 case '0':
241 case '1':
242 case '2':
243 case '3':
244 case '4':
245 case '5':
246 case '6':
247 case '7':
248 case '8':
249 case '9':
250 Estado=9;
251 Visor.setText(Visor.getText()+Car);
252 break;
253 default:
254 Iniciar();
255 throw new OpcionErronea();
256 }
257 break;
258
259
260 case 9:
261 switch(Car) {
262 case '=':
263 Estado=10;
264 Operando2=Double.parseDouble(Visor.getText());
265 double Resultado=ObtenerResultado();
266 Visor.setText(String.valueOf(Resultado));
267 Operando1=Resultado;
268 break;
269 case '0':
270 case '1':
271 case '2':
272 case '3':
273 case '4':
274 case '5':
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 9: APLICACIONES DE EJEMPLO 315

275 case '6':


276 case '7':
277 case '8':
278 case '9':
279 Estado=9;
280 Visor.setText(Visor.getText()+Car);
281 break;
282 default:
283 Iniciar();
284 throw new OpcionErronea();
285 }
286 break;
287
288
289 case 10:
290 switch(Car) {
291 case '+':
292 case '-':
293 case '*':
294 case '/':
295 Estado=5;
296 Operador=Car;
297 Visor.setText("");
298 break;
299 default:
300 Iniciar();
301 throw new OpcionErronea();
302 }
303 break;
304
305 }
306
307 }
308
309
310 private static void Iniciar(){
311 Estado=0;
312 Visor.setText("");
313 Operando1=0d;
314 Operando2=0d;
315 Operador=' ';
316 }
317
318
319 private static double ObtenerResultado() {
320 switch(Operador) {
321 case '+':
322 return Operando1+Operando2;
316 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

323 case '-':


324 return Operando1-Operando2;
325 case '*':
326 return Operando1*Operando2;
327 case '/':
328 return Operando1/Operando2;
329 default:
330 return 0d;
331 }
332 }
333
334 }

1 public class OpcionErronea extends Exception {


2
3 OpcionErronea(){
4 super("No valido");
5 }
6
7 }

9.2 EJEMPLO: EDITOR


9.2.1 Definición del ejemplo

La aplicación consiste en una primera aproximación a un editor de texto


sencillo basado en la utilización de componentes AWT. En este ejemplo se hace uso
de diversos interfaces y adaptadores de tratamiento de eventos. El interfaz gráfico
que incorpora la aplicación presenta el aspecto que muestra la figura siguiente.

El área central permite la edición de texto y está implementada con un


componente “área de texto”. La barra de menús situada en la parte superior de la
ventana contiene las opciones “Apariencia” y “Herramientas”; el primer menú
permite cambiar el tamaño y el estilo (itálica, negrita) del texto, el segundo hace
posible pasar todo el texto a mayúsculas o a minúsculas.
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 9: APLICACIONES DE EJEMPLO 317

La parte derecha de la ventana contiene un conjunto de botones que pueden


ser utilizados para seleccionar colores; cada columna presenta una gama de
gradación hacia el blanco partiendo del rojo, verde, azul y negro. La selección de un
color actúa en combinación con los botones de radio “Color texto” y “Color fondo”,
de manera que podemos conseguir variar el color del texto o del fondo del área de
edición de texto.

El campo de texto “Buscar” permite hacer una búsqueda del literal


suministrado sobre el texto editado; esta búsqueda se realiza sobre todo el texto
editado o bien sobre la parte seleccionada (con el ratón o teclado) del mismo, para
ello disponemos de los botones de radio “Texto marcado” y “Todo el texto”. La
búsqueda se realiza hasta que se encuentra la primera ocurrencia del texto,
seleccionándose automáticamente en el área de texto.

El campo de texto “Reemplazar” funciona en combinación con “Buscar”


para reemplazar porciones de texto. Se reemplaza la primera ocurrencia que se
encuentre coincidiendo con el texto suministrado en “Buscar”. Esta opción también
actúa atendiendo a los botones de radio “Texto marcado” y “Todo el texto”.
318 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

La figura anterior muestra el interfaz de la aplicación tras haber sido


empleadas algunas de sus posibilidades.

9.2.2 Estructura de la aplicación

A continuación se muestra un “esqueleto” de código que representa la


estructura básica con la que se ha implementado la aplicación.

En primer lugar se declaran las propiedades que utilizaremos en la


aplicación (componentes de AWT usados, constantes empleadas, variables
auxiliares, etc.); posteriormente se implementa un constructor que admite como
argumento un color que será utilizado como color de fondo del área de edición;
después se invoca al método PreparaMenus (línea 10 y 27), que define la barra de
menús y los propios menús desplegables.

El método PreparaZonaInferior (líneas 11 y 34) se encarga de presentar los


botones de radio y cajas de texto en la parte inferior de la ventana. En la línea 12 se
crea una instancia de la clase Colores; esta clase se ha implementado fuera de
nuestra aplic ación para facilitar su reutilización en otros programas. En la línea 13 se
hace una llamada al método DameBotones de la clase Colores, de esta manera
hacemos posible la asociación de manejadores de eventos a los botones que
representan colores.

El resto del constructor se encarga de definir el GUI de la aplicación, tanto a


nivel estático (visualización) como a nivel dinámico (enlace de los componentes con
las clases de tratamiento de eventos).

En la línea 39 se declara una clase que permite que la aplicación finalice


cuando el usuario cierra la ventana. El resto de las clases se encargan del tratamiento
de eventos de la aplicación, ofreciendo la parte más instructiva de este ejemplo.

1 import java.awt.*;
2 import java.awt.event.*;
3
4 public class GUIEditor {
5
6 // Propiedades de la clase
7
8 GUIEditor(Color ColorFondoEdicion) {
9
10 PreparaMenus();
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 9: APLICACIONES DE EJEMPLO 319

11 PreparaZonaInferior();
12 Colores ZonaColores = new Colores(NUM_COLORES);
13 Button[][] Botones = ZonaColores.DameBotones();
14
15 // definición del interfaz grafico de usuario (GUI)
16
17 // Enlace de los components con las clases de
// tratamiento de eventos
18
19 }
20
21
22 GUIEditor() {
23 this(Color.white);
24 }
25
26
27 private void PreparaMenus() {
28
29 // Defincion y presentacion de los menus declarados
// previamete
30
31 }
32
33
34 private void PreparaZonaInferior() {
35 // Definicion y presentacion de los botones de radio y
// cajas de texto
36 }
37
38
39 private class CerrarVentana extends WindowAdapter {
40 public void windowClosing(WindowEvent e) {
41 System.exit(0);
42 }
43 }
44
45
46 private class EventosAccion implements ActionListener {
47 public void actionPerformed(ActionEvent e) {
48
49 // Tratamiento de las opciones de menu
50
51 } // actionPerformed
52 } // EventosAccion
53
54
55
320 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

56 private class EventoBuscaReemplaza implements


ActionListener {
57 public void actionPerformed(ActionEvent e) {
58
59 // Tratamiento de las opciones “Buscar” y
// “Reemplazar”
60
61 }
62 } // EventoBuscaReemplaza
63
64
65
66 private class EventosElemento implements ItemListener {
67 public void itemStateChanged(ItemEvent e) {
68
69 // Tratamiento de los botones de radio “Colortexto” y
// “Color fondo”
70
71 } // itemStateChanged
72 } // EventosElemento
73
74
75
76 private class EventosRaton extends MouseAdapter {
77 public void mouseClicked(MouseEvent e) {
78
79 // Tratamiento a la pulsacion de los botones de
// colores
80
81 } // mouseClicked
82 } // EventosRaton
83
84 }

A continuación se explican los detalles más relevantes de cada “bloque” de


la aplicación:
• Propiedades de la clase
• Constructores
• Método PreparaMenus
• Método PreparaZonaInferior
• Clase Colores
• Clases de tratamiento de eventos
• Tratamiento de las opciones de menú
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 9: APLICACIONES DE EJEMPLO 321

• Tratamiento de las opciones “Buscar” y “Reemplazar”


• Tratamiento de los botones de radio “Color texto” y “Color fondo”
• Tratamiento a las pulsaciones de los botones de colores

9.2.3 Propiedades de la clase

La mayor parte de las propiedades de la clase definen los diferentes


componentes del interfaz gráfico. En las 2 primeras líneas de código se declaran y
definen el marco y su barra de menús; en las líneas 6, 7 y 8 se hace lo propio con los
menús.

En la línea 3 se define el panel principal de la aplicación y en la línea 4 el


panel inferior que contendrá los botones de radio y las cajas de texto. La línea 10
crea el área de introducción de texto.

En las líneas 11 y 12 se declaran las variables necesarias para saber a partir


de qué posición (y hasta que posición) del texto hay que hacer las
búsquedas/reemplazamientos. En las líneas 13 y 14 se definen constantes que nos
servirán para consultar la propiedad TextoFondo (línea 15) y saber si hay que
asignar el color seleccionado al fondo del área de edición o bien al texto.

En las líneas 17 a 25 se definen los dos grupos de botones de radio; en las


líneas 27 y 28 las dos cajas de texto. En las líneas 30 y 31 se declaran e inicializan
variables que permiten mantener el estilo y tamaño del texto. Por fin, en la línea 33
se declara una constante con el número de colores (en cada columna) que deseamos
que el usuario pueda seleccionar.

1 private Frame MiMarco = new Frame();


2 private MenuBar MenuPrincipal = new MenuBar();
3 private Panel PanelPrincipal = new Panel(new
BorderLayout());
4 private Panel ZonaInferior = new Panel(new
FlowLayout(FlowLayout.LEFT));
5
6 private Menu Apariencia = new Menu("Apariencia");
7 private Menu Tamanio = new Menu("Tamaño ");
8 private Menu Herramientas = new Menu("Herramientas");
9
10 private TextArea AreaEdicion = new TextArea("Hola
amigo");
11 private int PosicionInicial=0;
322 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

12 private int PosicionFinal=0;


13 private final int TEXTO = 1;
14 private final int FONDO = 2;
15 private int TextoFondo = TEXTO;
16
17 private CheckboxGroup Alcance = new CheckboxGroup();
18 private Checkbox TextoMarcado = new Checkbox("Texto
19 marcado", false, Alcance);
20 private Checkbox TextoCompleto = new Checkbox("Todo el
21 texto", true, Alcance);
22
23 private CheckboxGroup Marcado = new CheckboxGroup();
24 private Checkbox ColorTexto = new Checkbox("Color
texto",true, Marcado);
25 private Checkbox ColorFondo = new Checkbox("Color
fondo",false, Marcado);
26
27 private TextField Buscar = new TextField(15);
28 private TextField Reemplazar = new TextField(15);
29
30 private int TamanioFuente = 18;
31 private int TipoFuente = Font.PLAIN;
32
33 private final int NUM_COLORES = 16;

9.2.4 Constructores

El primer constructor invoca a los métodos auxiliares PreparaMenus y


PreparaZonaInferior (líneas 3 y 4), instancia un objeto de la clase Colores (línea 5)
y obtiene sus botones (línea 6); posteriormente asigna un nombre a la caja de texto
Buscar (línea 9) y establece el color de fondo y de texto inicial del área de texto
(líneas 10 a 15).

En las líneas 17 a 19 se añaden al panel principal: el área de texto, el panel


ZonaInferior y el panel que proporciona la instancia de la clase colores. Las líneas
21 a 24 completan los pasos necesarios para dimensionar y hacer visible la ventana.

El resto del constructor se dedica a asignar los manejadores de eventos más


adecuados para los componentes de la aplicación: cerrando la ventana podremos
finalizar la aplicación (línea 26), los menús los controlamos con eventos de acción
(líneas 27 a 29), las cajas de texto “Buscar y “reemplazar” también las controlamos
con eventos de acción, pero definidos en una clase diferente.
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 9: APLICACIONES DE EJEMPLO 323

Aunque no sería necesario asociar un tratamiento de eventos a los botones


de radio “Color texto” y “Color fondo”, hemos optado por asignarles un tratamiento
de eventos de elemento para mostrar su funcionamiento (líneas 32 y 33).
Finalmente, las líneas 34 a 36 se encargan de asignar una instancia de tratamiento de
eventos de ratón a cada botón de colores proporcionado por la clase Colores.

1 GUIEditor(Color ColorFondoEdicion) {
2
3 PreparaMenus();
4 PreparaZonaInferior();
5 Colores ZonaColores = new Colores(NUM_COLORES);
6 Button[][] Botones = ZonaColores.DameBotones();
7
8 PanelPrincipal.setBackground(new Color(255,255,128));
9 Buscar.setName("Busca");
10 AreaEdicion.setFont(new
Font("Serif",TipoFuente,TamanioFuente));
11 AreaEdicion.setBackground(ColorFondoEdicion);
12 if (ColorFondo.equals(Color.black))
13 AreaEdicion.setForeground(Color.white);
14 else
15 AreaEdicion.setForeground(Color.black);
16
17 PanelPrincipal.add(AreaEdicion,BorderLayout.CENTER);
18 PanelPrincipal.add(ZonaColores.DamePanel(),
BorderLayout.EAST);
19 PanelPrincipal.add(ZonaInferior,BorderLayout.SOUTH);
20
21 MiMarco.add(PanelPrincipal);
22 MiMarco.setSize(700,400);
23 MiMarco.setTitle("Editor de texto");
24 MiMarco.setVisible(true);
25
26 MiMarco.addWindowListener(new CerrarVentana());
27 Apariencia.addActionListener(new EventosAccion());
28 Tamanio.addActionListener(new EventosAccion());
29 Herramientas.addActionListener(new EventosAccion());
30 Buscar.addActionListener(new EventoBuscaReemplaza());
31 Reemplazar.addActionListener(new
EventoBuscaReemplaza());
32 ColorTexto.addItemListener(new EventosElemento());
33 ColorFondo.addItemListener(new EventosElemento());
34 for (int i=0;i<NUM_COLORES;i++)
35 for (int j=0;j<=3;j++)
36 Botones[i][j].addMouseListener(new EventosRaton());
37 }
38
39
324 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

40 GUIEditor() {
41 this(Color.white);
42 }

9.2.5 Método PreparaMenus

Las líneas 2 a 6 se utilizan para definir las opciones del menú Tamanio. Las
líneas 8 a 11 definen las opciones del menú Apariencia; obsérvese como una opción
de menú puede incluir a su vez un nuevo menú (línea 11). En las líneas 13 y 14 se
definen las dos opciones del menú Herramientas.

En las líneas 16 y 17 se añaden los menús Apariencia y Herramientas a la


barra de menús MenuPrincipal, que a su vez se asigna al marco MiMarco mediante
el método setMenuBar (línea 19).

1 private void PreparaMenus() {


2 Tamanio.add("Muy pequeño");
3 Tamanio.add("Pequeño");
4 Tamanio.add("Normal");
5 Tamanio.add("Grande");
6 Tamanio.add("Muy grande");
7
8 Apariencia.add("Tipo normal");
9 Apariencia.add("Italica");
10 Apariencia.add("Negrita");
11 Apariencia.add(Tamanio);
12
13 Herramientas.add("Todo mayúsculas");
14 Herramientas.add("Todo minúsculas");
15
16 MenuPrincipal.add(Apariencia);
17 MenuPrincipal.add(Herramientas);
18
19 MiMarco.setMenuBar(MenuPrincipal);
20 }
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 9: APLICACIONES DE EJEMPLO 325

9.2.6 Método PreparaZonaInferior

1 private void PreparaZonaInferior() {


2 Panel PanelAlcance = new Panel(new GridLayout(2,1));
3 Panel PanelBuscar = new Panel(new GridLayout(2,1));
4 Panel PanelMarcado = new Panel(new GridLayout(2,1));
5 Panel PanelBuscarArriba = new
Panel(new FlowLayout(FlowLayout.LEFT));
6 Panel PanelBuscarAbajo = new
Panel(new FlowLayout(FlowLayout.LEFT));
7
8 ZonaInferior.add(PanelAlcance);
9 ZonaInferior.add(PanelBuscar);
10 ZonaInferior.add(PanelMarcado);
11
12 PanelAlcance.setBackground(Color.orange);
13 PanelAlcance.add(TextoMarcado);
14 PanelAlcance.add(TextoCompleto);
15
16 PanelBuscar.setBackground(Color.orange);
17 PanelBuscar.add(PanelBuscarArriba);
18 PanelBuscar.add(PanelBuscarAbajo);
19
20 PanelMarcado.setBackground(new Color(255,255,128));
21 PanelMarcado.add(ColorTexto);
22 PanelMarcado.add(ColorFondo);
23
24 PanelBuscarArriba.add(new Label("Buscar "));
25 PanelBuscarArriba.add(Buscar);
26
27 PanelBuscarAbajo.add(new Label("Reemplazar "));
28 PanelBuscarAbajo.add(Reemplazar);
29 }

9.2.7 Clase Colores

La clase Colores proporciona un conjunto de botones de colores situados en


un panel; se ha implementado como una clase independiente con el fin de facilitar su
reutilización.

Existen tres propiedades globales en la clase:


326 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

• NumColores, donde se almacena el número de colores con el que se


instancia la clase (línea 5)
• La matriz bidimensional BotonesColores (Línea 6), donde se
almacenan los botones; se puede obtener a través del método
DameBotones (línea 30).
• El panel PanelColores (línea 7), donde se ubican los botones y que
puede ser obtenido usando el método DamePanel (línea 26).

En la línea 9 se implementa el constructor de la clase; admite como


argumento un entero indicando el número de colores que se desea en cada
gradación, de esta manera, en la línea 11 se define el tamaño de la matriz
BotonesColores con el número de colores (NumColores) como dimensión de las
filas y con 4 columnas: “rojo”, ”verde”, “azul” y “negro”.

Entre las líneas 13 y 17 se crean los botones individuales asignándose a la


matriz bidimensional (línea 15) y añadiéndose al panel PanelColores (línea 16).

Las líneas 18 a 22 asignan un color de fondo diferente a cada botón, para


ello se instancian objetos Color formados con valores RGB(Red, Green, Blue), por
ejemplo, cuando c=0 obtenemos los colores: rojo (255,0,0), verde (0,255,0), azul
(0,0,255) y negro (0,0,0). A medida que c aumenta, Factor (línea 18) también
aumenta y los colores se acla ran (el valor 255,255,255 es el blanco).

1 import java.awt.*;
2
3 public class Colores {
4
5 private int NumColores;
6 private Button[][] BotonesColores;
7 private Panel PanelColores = new Panel(new
GridLayout(NumColores,4));
8
9 Colores(int NumColores) {
10 this.NumColores=NumColores;
11 BotonesColores = new Button[NumColores][4];
12
13 for (int c=0;c<NumColores;c++) {
14 for (int col=0;col<=3;col++) {
15 BotonesColores[c][col] = new Button();
16 PanelColores.add(BotonesColores[c][col]);
17 }
18 int Factor = c*(255/NumColores);
19 BotonesColores[c][0].setBackground(new
Color(255,Factor,Factor));
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 9: APLICACIONES DE EJEMPLO 327

20 BotonesColores[c][1].setBackground(new
Color(Factor,255,Factor));
21 BotonesColores[c][2].setBackground(new
Color(Factor,Factor,255));
22 BotonesColores[c][3].setBackground(new
Color(Factor,Factor,Factor));
23 }
24 }
25
26 public Panel DamePanel() {
27 return PanelColores;
28 }
29
30 public Button[][] DameBotones() {
31 return BotonesColores;
32 }
33
34 }

9.2.8 Tratamiento de las opciones de menú

Los objetos Menu y MenuItem admiten la utilización del interfaz


ActionListener para tratar eventos. En el constructor de la clase GUIEditor hemos
asignado instancias de la clase EventosAccion (línea 1) a los menús Apariencia,
Tamanio y Herramientas, de esta forma, cuando se ejecuta el método
actionPerformed (línea 3), sabemos que ha sido seleccionada alguna opción de
menú.

Para saber cuál de las opciones de menú ha sido seleccionada, consultamos


el método getActionCommand (línea 5) perteneciente a la clase ActionEvent (línea
3); comparando (equals) el resultado con el texto que define cada opción de menú
(“Negrita”, “Italica”, etc.) podemos determinar la opción pulsada.

Las tres primeras sentencias condicionales (líneas 5, 10 y 15) realizan el


tratamiento de cada posible selección de estilo de letra. La variable TipoFuente se
actualiza (líneas 6, 11 y 16) y se aplica una nueva fuente al texto del área de edición
(líneas 7, 12 y 17).

Las siguientes 5 instrucciones condicionales se encargan de aplicar el


tamaño de letra seleccionado. Como se puede observar, el tratamiento es igual al
explicado, pero en este caso lo que variamos es la propiedad TamanioFuente.
328 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

Las últimas dos instrucciones condicionales se encargan de poner el texto en


mayúsculas o minúsculas haciendo uso de los métodos toUpperCase y
toLowerCase, pertenecientes a la clase String.

1 private class EventosAccion implements ActionListener {


2
3 public void actionPerformed(ActionEvent e) {
4
5 if (e.getActionCommand().equals("Negrita")) {
6 TipoFuente=Font.BOLD;
7 AreaEdicion.setFont(new
Font("Serif",TipoFuente,TamanioFuente));
8 return;
9 }
10 if (e.getActionCommand().equals("Italica")) {
11 TipoFuente=Font.ITALIC;
12 AreaEdicion.setFont(new
Font("Serif",TipoFuente,TamanioFuente));
13 return;
14 }
15 if (e.getActionCommand().equals("Tipo normal")) {
16 TipoFuente=Font.PLAIN;
17 AreaEdicion.setFont(new
Font("Serif",TipoFuente,TamanioFuente));
18 return;
19 }
20 if (e.getActionCommand().equals("Muy pequeño")) {
21 TamanioFuente=10;
22 AreaEdicion.setFont(new
Font("Serif",TipoFuente,TamanioFuente));
23 return;
24 }
25 if (e.getActionCommand().equals("Pequeño")) {
26 TamanioFuente=14;
27 AreaEdicion.setFont(new
Font("Serif",TipoFuente,TamanioFuente));
28 return;
29 }
30 if (e.getActionCommand().equals("Normal")) {
31 TamanioFuente=18;
32 AreaEdicion.setFont(new
Font("Serif",TipoFuente,TamanioFuente));
33 return;
34 }
35 if (e.getActionCommand().equals("Grande")) {
36 TamanioFuente=23;
37 AreaEdicion.setFont(new
Font("Serif",TipoFuente,TamanioFuente));
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 9: APLICACIONES DE EJEMPLO 329

38 return;
39 }
40 if (e.getActionCommand().equals("Muy grande")) {
41 TamanioFuente=30;
42 AreaEdicion.setFont(new
Font("Serif",TipoFuente,TamanioFuente));
43 return;
44 }
45 if (e.getActionCommand().equals("Todo mayúsculas")) {
46 AreaEdicion.setText(AreaEdicion.getText().
toUpperCase());
47 return;
48 }
49 if (e.getActionCommand().equals("Todo minúsculas")) {
50 AreaEdicion.setText(AreaEdicion.getText().
toLowerCase());
51 return;
52 }
53
54 } // actionPerformed
55
56 } // EventosAccion

9.2.9 Tratamiento de las opciones “Buscar” y “Reemplazar”

En el constructor de la clase GUIEditor se crearon 2 instancias de la clase


EventoBuscaReemplaza y se “añadieron” a las cajas de texto Buscar y Reemplazar.
Obsérvese como no existe ningún problema en utilizar varias clases que
implementen un mismo “Listener” (en nuestra aplicación EventosAccion y
EventoBuscaReemplaza).

En la línea 5 del método actionPerformed se consulta si el botón de radio


TextoCompleto está activo (getState), si es así asignamos el valor 0 a PosicionInicial
(de búsqueda) y el tamaño del texto escrito en el área a PosicionFinal, es decir se
realiza la búsqueda en todo el texto; si TextoCompleto no está activo (línea 8), las
posiciones de búsqueda inicial y final se obtienen consultando el comienzo
(getSelectionStart) y final (getSelectionEnd) del área seleccionada en AreaEdicion.

Posteriormente, en la línea 13 obtenemos la longitud (length ) del texto


buscado (TamanioTexto) y en la línea 15 la selección que ha hecho el usuario
(búsqueda o reemplazamiento).
330 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

El bucle situado en la línea 17 itera desde la posición inicial hasta la


posición final (menos el tamaño del propio texto buscado); nos servirá para
comparar, a partir de cada posición, el texto siguiente en AreaEdicion con el texto
buscado. Esta comparación se realiza en la línea 18 (y 19) utilizando el método
equals sobre los literales: texto de Buscar y porción de texto (substring) situado
entre la posición actual y la actual más TamanioTexto .

Si encontramos una coincidencia realizamos la búsqueda/reemplazamiento


(líneas 21 a 28) y terminamos el método (línea 30). Si la opción que ha elegido el
usuario es “Busca” (línea 21) se selecciona el texto a partir de la posición (i)
encontrada (línea 22). Si el usuario ha seleccionado reemplazar (línea 23), aislamos
el texto anterior (línea 24) y posterior (línea 25) al que deseamos reemplazar y
creamos el nuevo texto (línea 27).

1 private class EventoBuscaReemplaza implements


ActionListener {
2
3 public void actionPerformed(ActionEvent e) {
4
5 if (TextoCompleto.getState()) {
6 PosicionInicial=0;
7 PosicionFinal=AreaEdicion.getText().length();
8 } else {
9 PosicionInicial=AreaEdicion.getSelectionStart();
10 PosicionFinal =AreaEdicion.getSelectionEnd();
11 }
12
13 int TamanioTexto=Buscar.getText().length();
14
15 TextField Accion = (TextField) e.getSource();
16
17 for (int i=PosicionInicial;i<=(PosicionFinal-
TamanioTexto);i++)
18 if
(AreaEdicion.getText().substring(i,i+TamanioTexto).
19 equals(Buscar.getText())){
20
21 if (Accion.getName().equals("Busca")) // Busqueda
22 AreaEdicion.select(i,i+TamanioTexto);
23 else { //Reemplaza
24 String Antes =
AreaEdicion.getText().substring(0,i);
25 String Despues = AreaEdicion.getText().
26 substring(i+TamanioTexto,
AreaEdicion.getText().length());
27 AreaEdicion.setText(Antes+Reemplazar.getText()
+Despues);
 JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES) CAPÍTULO 9: APLICACIONES DE EJEMPLO 331

28 }
29
30 return;
31 }
32 }
33 } // EventoBuscaReemplaza

9.2.10 Tratamiento de los botones de radio “Color texto” y


“Color fondo”

La clase EventosElemento (línea1) implementa el único método del interfaz


ItemListener: itemStateChanged (línea 3). Usando el método getItem (línea 4)
perteneciente a la clase ItemEvent, podemos conocer el botón de radio que ha
pulsado el usuario; si ha sido “Color texto” (línea 4) asignamos la constante TEXTO
a la propiedad TextoFondo (línea 5), y si ha sido “Color fondo” hacemos lo propio
en la línea 9.

1 private class EventosElemento implements ItemListener {


2
3 public void itemStateChanged(ItemEvent e) {
4 if (e.getItem().equals("Color texto")) {
5 TextoFondo=TEXTO;
6 return;
7 }
8 if (e.getItem().equals("Color fondo")) {
9 TextoFondo=FONDO;
10 return;
11 }
12
13 } // itemStateChanged
14 } // EventosElemento

9.2.11 Tratamiento a las pulsaciones de los botones de colores

La clase EventosRaton ha sido utilizada para crear todas las instancias de los
tratamientos de eventos de ratón de los botones de colores. El único método
redefinido del adaptador MouseAdapter (línea 1) ha sido mouseClicked (línea 3).
332 JAVA A TRAVÉS DE EJEMPLOS  JESÚS BOBADILLA SANCHO (JBOBI@EUI.UPM.ES)

En primer lugar se obtiene el botón que ha generado el evento (línea 4). Si


los botones de radio indican que hay que modificar el color de fondo del área de
edición (línea 5) se realiza esta acción (línea 6); idéntico tratamiento se hace para
modificar el color del texto (líneas 7 y 8).

Nótese que modificando la línea 5 por la sentencia: if


(ColorFondo.getState()) el comportamiento de la aplicación no varía y evitamos la
implementación de la clase EventosElemento . Esta clase ha sido incluida únicamente
para mostrar un uso habitual del interfaz itemListener.

1 private class EventosRaton extends MouseAdapter {


2
3 public void mouseClicked(MouseEvent e) {
4 Button Boton = (Button) e.getSource();
5 if (TextoFondo==FONDO)
6 AreaEdicion.setBackground(Boton.getBackground());
7 else // TEXTO
8 AreaEdicion.setForeground(Boton.getBackground());
9 } // mouseClicked
10
11
12 } // EventosRaton
LECCIÓN 1

APLICACIONES WEB EN CLIENTE:


APPLETS

1.1 INTRODUCCIÓN
Entre las diversas ventajas que presenta Java, su independencia de la
plataforma en la que se van a ejecutar las aplicaciones resulta de especial utilidad
para servir de soporte a una de sus posibilidades más notables: la distribución de
programas asociándolos a páginas web y su ejecución a través de los navegadores.

Los programas de Java diseñados e implementados para su ejecución en un


navegador se denominan applets. Cualquier ordenador que disponga de un
navegador con máquina virtual Java (JVM) podrá ejecutar nuestros programas,
independientemente del procesador, arquitectura y sistema operativo que conformen
la máquina.

Las mayores ventajas de los applets son:


• La programación general de un applet es igual a la de una aplicación,
siendo necesario únicamente tener en cuenta unas pocas reglas
específicas a seguir.
• Su ejecución es segura, en el sentido de que el usuario no podrá sufrir
consecuencias negativas derivadas de su utilización; por ejemplo
borrado de datos en el disco, establecimiento de comunicaciones no
deseadas, etc.

Las mayores desventajas de los applets son:


• No son tan eficientes en ejecución como sus más directos competidores,
los ActiveX, ni tan “ligeros” en la descarga como el HTML dinámico.
• Por motivos de seguridad, tienen restricciones en su ejecución: no se
puede leer ni escribir de unidades de disco, sólo se pueden establecer
comunicaciones entre el servidor web utilizado y el ordenador del
cliente, etc. Esta característica presenta la otra “cara de la moneda”
2 © JESÚS BOBADILLA SANCHO

respecto a la segunda ventaja expuesta. Existen casos en los que


conviene saltarse, de forma controlada, estas restricciones. Java
incorpora la manera de hacerlo, actuando sobre el Security Manager que
incorpora JVM.
• La tendencia de Microsoft es la de no incorporar la máquina virtual Java
en su programa Explorer; debido a su cuota de mercado, esto supone un
serio problema a la hora de universalizar el uso de los applets.

Desde un punto de vista técnico, la utilización de los applets es muy


adecuada en las aplicaciones web, sobre todo por el equilibrio que ofrecen entre las
posibilidades brindadas y la seguridad que aportan.

En los últimos años, la tendencia de Microsoft respecto a la incorporación de


JVM en el Explorer ha sido:
1 .............
2 Incorpora su propio JVM no totalmente compatible con el de SUN
3 Incorpora su propio JVM pero hay que activarlo en las opciones Avanzadas
4 No incorpora JVM pero lo proporciona a través de su sitio web
5 No incorpora JVM y te indica que lo puedes obtener en el sitio web de SUN

La tendencia mostrada nos debe dar una idea de las dificultades por las que
pueden pasar los applets de Java para imponerse en el mercado, dificultades unidas a
la competencia que tienen con otras poderosas tecnologías web:
• El HTML dinámico (DHTML), que ofrece la posibilidad de “animar”
las páginas web e interactuar con todos los objetos del navegador.
• Aplicaciones tipo FLASH, que gracias a la implantación generalizada de
sus plug-in necesarios permiten grandes posibilidades dinámicas en las
páginas Web.

Las puntualizaciones mostradas en este apartado pretenden servir para que el


lector pueda hacer un balance equilibrado acerca de la adecuación de los applets en
las aplicaciones web que pueda desarrollar en el futuro, sin olvidar en ningún caso
que esta tecnología es potente, segura, muy utilizada y adecuada para los equipos de
desarrollo habituados al lenguaje.

En los siguientes apartados se presentarán diversos ejemplos sencillos de


applets de Java con el obje tivo de que sirvan de plantilla para implementar clases de
mayor entidad basándose en los conocimientos adquiridos hasta el momento.
© JESÚS BOBADILLA SANCHO 3

1.2 INCLUSIÓN DE UN APPLET EN UNA PÁGINA WEB


Para incluir un applet dentro de una página web se utiliza la etiqueta
<APPLET>. Esta etiqueta admite los atributos CODE, HEIGHT, WIDTH, ALIGN,
ALT, ARCHIVE, OBJECT, CODEBASE, HSPACE y VSPACE. El atributo CODE
es obligatorio y en él se especifica el fichero .class del applet. El uso de WIDTH y
HEIGHT resulta muy recomendable; con estos atributos podemos establecer la
anchura y altura en pixels que el applet ocupará dentro de la página web.

De esta forma, para que una página web visualice el applet “Applet1.class”
con una anchura de 400 pixels y una altura de 100 pixels, podemos crear el siguiente
fichero HTML:
1 <HTML>
2 <BODY>
3 <APPLET CODE="Applet1.class" WIDTH=400 HEIGHT=100>
4 <BODY>
5 </HTML>

Hay que tener en cuenta que el applet de Java se visualiza en la página web
como un elemento más, por lo que habitualmente se encontrará entre otros objetos
HTML:
1 <HTML>
2 <BODY>
3 <TABLE BORDER=1>
4 <TR>
5 <TD> A nuestra derecha tenemos un applet </TD>
6 <TD><APPLET CODE="Applet1.class" WIDTH=400 HEIGHT=100></TD>
7 <TD> A nuestra izquierda tenemos un applet </TD>
8 </TR>
9 </TABLE>
10 <BODY>
11 </HTML>

En el ejemplo mostrado, se crea una página web que contiene una tabla de
una fila y tres columnas. En la primera y la tercera columna se inserta un texto,
reservando la segunda para incluir el applet; el applet imprime el texto “Hola
mundo”.
4 © JESÚS BOBADILLA SANCHO

1.3 EL PRIMER APPLET: HOLA MUNDO


Los navegadores están preparados para interactuar con programas Java que
extiendan la clase Applet, situada en el paquete java.applet. Este paquete contiene,
además de la clase Applet, tres interfaces, uno de ellos (AppletContext) dedicado a
facilitar la interacción con el documento que contiene el applet.

Una vez que sabemos que los applets deben extender la clase Applet, nuestra
clase Applet1 se definirá de la siguiente manera:
1 import java.applet.Applet;
2 public class Applet1 extends Applet {
3 }

El applet ya tiene una estructura y se compilará sin errores, pero no tiene un


punto donde comience la ejecución de las instrucciones cuando se carga en la página
web, es decir, le falta algo equivalente al método main (que en los applets no se
utiliza).

La clase Applet incorpora el método void init(), que se ejecuta


automáticamente la primera vez que se carga el applet en la página web. Este
método nos puede servir para incorporar las instrucciones que impriman el mensaje
deseado: “Hola mundo”.

1 import java.applet.Applet;
2 import java.awt.Label;
3
4 public class Applet1 extends Applet {
5
6 public void init() {
7 add(new Label("Hola mundo"));
8 }
9 }

El método init se define en la línea 6. En él, únicamente creamos una


etiqueta con el texto “Hola mundo” y la añadimos al applet (línea 7), como hacemos
en los paneles; eso es porque la clase Applet deriva de la clase Panel. Es decir, un
applet es un panel, y por tanto un contenedor:
© JESÚS BOBADILLA SANCHO 5

+--java.awt.Container
|
+--java.awt.Panel
|
+--java.applet.Applet

1.4 CICLO DE VIDA DE UN APPLET


Los applets pasan por una serie de estados desde el momento en el que las
páginas web que los contienen se cargan hasta que se descargan. Cada estado
conlleva la ejecución de un método, uno de los cuales, init(), hemos empleado ya. La
tabla siguiente muestra los métodos y su explicación:

Métodos que pueden ser invocados a lo largo del ciclo de vida de un applet
init() Se llama la primera vez que se carga el applet
start() Se le llama después del método init y cada vez que el navegador regresa a una
página en la que el applet está contenido
stop() Se le llama cada vez que el navegador abandona la página que contiene el
applet y también antes de la invocación del método destroy
destroy() Se llama antes de que se cierre el navegador

Los métodos init y destroy son llamados una sola vez durante el ciclo de
vida del applet. Los métodos start y stop son llamados al menos una vez:

init start stop destroy

El lugar adecuado para crear los objetos que necesitará un applet es el


método init, siendo en el método destroy donde se deberían liberar los recursos. Los
métodos start y stop son especialmente útiles para iniciar y parar la ejecución del
applet a medida que el usuario accede o se sale de la página hacia o desde otras
páginas web.

La clase Applet2 muestra como se invocan los métodos init y start al


cargarse un applet:

1 import java.applet.Applet;
2 import java.awt.1;
6 © JESÚS BOBADILLA SANCHO

3
4 public class Applet2 extends Applet {
5
6 private String Mensaje="";
7 private Panel PanelSuperior = new Panel();
8 private Panel PanelInferior = new Panel();
9 private Label EtiquetaSuperior=new Label();
10 private Label EtiquetaInferior=new Label();
11
12 public void init() {
13 this.setLayout(new GridLayout(2,1));
14 this.add(PanelSuperior);
15 this.add(PanelInferior);
16 PanelSuperior.add(EtiquetaSuperior);
17 PanelInferior.add(EtiquetaInferior);
18 EtiquetaSuperior.setText("Applet Cargado");
19 }
20
21 public void start() {
22 EtiquetaInferior.setText("Applet Inicializado");
23 }
24
25 }

1.5 PASO DE PARÁMETROS A UN APPLET


Existe la posibilidad de parametrizar el comportamiento de los applets. Esto
se consigue insertando parámetros en el fichero HTML que contiene el applet y
accediendo, desde Java, a los mismos.
© JESÚS BOBADILLA SANCHO 7

Una vez que hemos leído el/los parámetros, podemos hacer que el applet se
comporte de manera diferente según sea su valor. Pongamos un ejemplo en el que se
utiliza esta posibilidad: un usuario recibe una página web en la que puede
seleccionar un mes en dos listas desplegables (año y mes); la página se procesa en el
servidor (con servlets, CGI, ASP, PHP, etc.) y le devuelve al usuario una página web
con los dos parámetros numéricos (año y mes) y el applet asociado que mostrará en
forma de calendario el mes seleccionado.

La parametrización permite, en el ejemplo anterior, que el applet visualice


información diferente atendiendo a los valores concretos de los parámetros que
recibe.

La manera de establecer los parámetros de un applet en una página web es el


uso de la etiqueta PARAM, cuya sintaxis es:

<PARAM NAME=Nombre VALUE=Valor>

Las etiquetas PARAM se sitúan dentro del ámbito de las etiquetas APPLET.
En el ejemplo del applet calendario (de un mes), una página web podría tener la
siguiente disposición para agosto del 2005:

1 <HTML>
2 <BODY>
3 <APPLET CODE="CalendarioMes.class" HEIGHT=200 WIDTH=200>
4 <PARAM NAME=Mes VALUE=8>
5 <PARAM NAME=Anio VALUE=2005>
6 </APPLET>
7 <BODY>
8 </HTML>

La clase CalendarioMes puede recoger los valores de los parámetros Mes y


Anio (8 y 2005) haciendo uso del método de la clase Applet:

public String getParameter(String Nombre)

En el siguiente applet, en las líneas 7 y 8 se hace uso del método


getParameter para recoger los valores asociados a los parámetros Mes y Anio
definidos en las líneas 4 y 5 de la página web.

1 import java.applet.Applet;
2 import java.awt.Label;
3
4 public class CalendarioMes extends Applet {
5
6 public void init() {
8 © JESÚS BOBADILLA SANCHO

7 String MesSeleccionado = getParameter("Mes");


8 String AnioSeleccionado = getParameter("Anio");
9
10 int ValorMes = Integer.parseInt(MesSeleccionado);
11 int ValorAnio = Integer.parseInt(AnioSeleccionado);
12
13 add(new Label(MesSeleccionado));
14 add(new Label(AnioSeleccionado));
15
16 // aqui se programaria la visualizacion del calendario
17
18 }
19 }

En el siguiente ejemplo, el applet PaletaColores (línea 5) utiliza la clase


Colores (líneas 7 y 14) para crear una paleta de NumColores colores (línea 14) en
cada gama: rojo, verde, azul, negro. El valor NumColores se obtiene a partir del
parámetro NumeroDeColores que proporciona la página web, para ello utilizamos el
método getParameter (línea 13).

La clase Colores que ya ha sido utilizada en el ejemplo Editor, proporciona


un panel con los botones dibujados (línea 18) y una matriz bidimensional con todos
los botones (línea 15).

En las líneas 21 a 23 se asigna a todos los botones la misma clase de


tratamiento de eventos: CambioColor (línea 27). En esta clase se utiliza el método
mouseClicked (línea 29) para variar el color del área de texto (línea 31) cuando se
pulsa sobre un botón.

1 import java.applet.Applet;
2 import java.awt.*;
3 import java.awt.event.*;
4
5 public class PaletaColores extends Applet {
6
7 private Colores Paleta;
8 private Button[][] Botones;
9 private TextArea AreaDeTexto;
10
© JESÚS BOBADILLA SANCHO 9

11 public void init() {


12 int NumColores =
13 Integer.parseInt(getParameter("NumeroDeColores"));
14 Paleta = new Colores(NumColores);
15 Botones = Paleta.DameBotones();
16 AreaDeTexto = new TextArea();
17 this.setLayout(new GridLayout(1,2));
18 add(Paleta.DamePanel());
19 add(AreaDeTexto);
20
21 for (int i=0;i<NumColores;i++)
22 for (int j=0;j<=3;j++)
23 Botones[i][j].addMouseListener(new CambioColor());
24 }
25
26
27 private class CambioColor extends MouseAdapter {
28
29 public void mouseClicked(MouseEvent e) {
30 Button Boton = (Button) e.getSource();
31 AreaDeTexto.setBackground(Boton.getBackground());
32 }
33
34 }
35
36 }

Los resultados anteriores se obtienen proporcionando valores 16 y 32 al


parámetro NumeroDeColores definido en la etiqueta PARAM de la página web que
incorpora el applet PaletaColores:
10 © JESÚS BOBADILLA SANCHO

1 <HTML>
2 <BODY>
3 <APPLET CODE="PaletaColores.class" HEIGHT=300 WIDTH=300>
4 <PARAM NAME=NumeroDeColores VALUE=32>
5 </APPLET>
6 <BODY>
7 </HTML>

Código de la clase Colores:

8 import java.awt.*;
9
10 public class Colores {
11
12 private int NumColores;
13 private Button[][] BotonesColores;
14 private Panel PanelColores = new
15 Panel(new GridLayout(NumColores,4));
16
17 Colores(int NumColores) {
18 this.NumColores=NumColores;
19 BotonesColores = new Button[NumColores][4];
20
21 for (int c=0;c<NumColores;c++) {
22 for (int col=0;col<=3;col++) {
23 BotonesColores[c][col] = new Button();
24 PanelColores.add(BotonesColores[c][col]);
25 }
26 int Factor = c*(255/NumColores);
27 BotonesColores[c][0].setBackground(new
28 Color(255,Factor,Factor));
29 BotonesColores[c][1].setBackground(new
30 Color(Factor,255,Factor));
31 BotonesColores[c][2].setBackground(new
32 Color(Factor,Factor,255));
33 BotonesColores[c][3].setBackground(new
34 Color(Factor,Factor,Factor));
35 }
36 }
37
38 public Panel DamePanel() {
39 return PanelColores;
40 }
41
42 public Button[][] DameBotones() {
43 return BotonesColores;
44 }
45
46 }
© JESÚS BOBADILLA SANCHO 11

1.6 OTROS MÉTODOS DE LA CLASE APPLET


La clase Applet dispone del constructor vacío y una serie de métodos que se
comentan brevemente en este apartado. Además de los métodos que ya han sido
presentados: init(), start(), stop(), destroy() y getParameter(String), se encuentran
accesibles otros 19 métodos propios de la clase más todos los heredados de los
objetos Panel, Container, Component y Object.

Los métodos más representativos de la clase Applet, que no han sido


explicados son:

Método Acción
URL getDocumentBase() Devuelve la dirección del directorio donde se
encuentra la página web que contiene el applet
URL getCodeBase() Devuelve la dirección completa (directorio+nombre de
la página web) donde se encuentra la página web que
contiene el applet
void resize(int Anchura, int Altura) Establece dinámicamente el tamaño del applet
void play(URL Direccion) Hace sonar el fichero de audio situado en Direccion
AudioClip getAudioClip (URL d) Devuelve el audio situado en la dirección especificada
Image getImage(URL Direccion) Devuelve la imagen situada en la dirección
especificada
boolean isActive() Indica si el applet está activo
void showStatus(String Mensaje) Sitúa ‘Mensaje’ en la barra de estado del navegador
String getAppletInfo() Devuelve la información que se suministre en el
método que sobrescriba a éste.
CAPÍTULO 2

PROGRAMACIÓN CONCURRENTE:
THREADS

2.1 INTRODUCCIÓN
Todos los programas Java que hemos visto hasta ahora se ejecutan de
manera independiente a otros programas Java que podamos tener en nuestro sistema.
Si quisiéramos, por ejemplo, desarrollar una aplicación Telefono, tendríamos que
programar estas dos actividades:
• Recoger sonidos con un micrófono y enviarlos en tiempo real a otro
ordenador.
• Recibir los sonidos que nos llegan desde otro ordenador y reproducirlos por
los altavoces.

Si queremos poder hablar y escuchar lo que nos dicen, de manera


simultánea, utilizando los conocimientos adquiridos hasta el momento solo tenemos
una opción: abrir dos consolas en nuestro sistema y ejecutar cada aplicación (la que
envía sonido y la que recibe sonido) en cada consola.

La solución adoptada aprovecha la capacidad que tienen los sistemas


operativos para ejecutar varios procesos de manera simultánea (paralela,
concurrente); en este caso, el sistema operativo ejecuta en paralelo cada una de
nuestras máquinas virtuales Java (JVM), que se encuentran en las dos consolas
abiertas.

Si quisiéramos darle un uso profesional a nuestra aplicación, enseguida


descubriríamos que no es factible “vender” una solución que requiere ejecutar
distintas partes del programa por separado. Lo adecuado es que una misma
aplicación Java pueda ejecutar varios procesos concurrentemente.

Los Threads (hilos) de Java permiten ejecutar en paralelo varios programas


que se encuentran en la misma aplicación. En el ejemplo Telefono, haciendo uso de
threads, podemos ejecutar simultáneamente el programa que captura y envía sonidos
14 © JESÚS BOBADILLA SANCHO

con el programa que los recibe y los manda a los altavoces; de esta manera,
podemos ejecutar toda la funcionalidad desde una sola consola.

2.2 DEFINICIÓN DE PROGRAMAS CONCURRENTES


Existen dos formas de definir una clase para que pueda ejecutarse en
paralelo a otras clases:
• Extendiendo la clase Thread
• Implementando el interfaz Runnable

Ejemplos:
public class SeEjecutaConcurrentemente extends Thread
public class SeEjecutaConcurrentemente implements Runnable

Puesto que Java no soporta herencia múltiple, si necesitamos heredar los


miembros de una clase resulta necesario hacer uso del interfaz Runnable; por
ejemplo, si deseamos crear un applet que se ejecute concurrentemente con otros
programas, podemos definir nuestra clase como:

public class AppletConcurrente extends java.applet.Applet implements Runnable

El interfaz Runnable es extremadamente sencillo: únicamente contiene el


método run(). Cuando un objeto que imple menta este interfaz se usa para crear un
thread, al arrancar el thread se ejecuta automáticamente el método run.

La clase Thread implementa el interfaz Runnable y contiene un variado


grupo de constructores y métodos que permiten definir el comportamiento y
evolución de los programas concurrentes.

Las estructuras más simples de programas que usan threads son:

Empleando la clase Tread:

1 class TelefonoEnvia extends Thread {


2 .... // propiedades, constructores y métodos de la clase
3 public void run() {
4 ……… // recoge sonidos del microfono y los envía por la
//red
5 }
6 }
© JESÚS BOBADILLA SANCHO 15

Para crear y arrancar un thread con el comportamiento de TelefonoEnvia :

1 TelefonoEnvia InstanciaEnvia = new TelefonoEnvia();


2 InstanciaEnvia.start();

Empleando el interfaz Runnable :

1 class TelefonoEnvia implements Runnable {


2 .... // propiedades, constructores y métodos de la
3 .... // clase
4 public void run() {
5 // recoge sonidos del microfono y los envía por la red
6 }
7 }

Para crear y arrancar un thread con el comportamiento de TelefonoEnvia :


1 TelefonoEnvia InstanciaEnvia = new TelefonoEnvia();
2 new Thread(InstanciaEnvia).start();

Como se puede apreciar en las estructuras de programa mostradas, para


arrancar un thread debemos utilizar el método start, que a su vez provoca la
ejecución de l método run. En la última línea de código mostrada, se utiliza un
constructor de la clase Thread que admite un objeto Runnable como parámetro.

Un método importante de la clase Thread es:


static void sleep (long milisegundos) // detiene la ejecución del thread al
// menos el número de milisegundos indicado

2.3 PRIMER EJEMPLO


Con los conceptos explicados hasta el momento estamos en condiciones de
afrontar nuestro primer ejemplo, que hace uso de dos threads: cada thread imprime
una frase en momentos diferentes.

La clase ThreadBasico (línea 3) implementa el interfaz Runnable, y por


tanto su único método run (línea 13). Cada instancia de la clase ThreadBasico
incorpora las propiedades Frase y Aleatorio, que contendrán el String a imprimir y
una medida del tiempo que se tardará en hacerlo.
16 © JESÚS BOBADILLA SANCHO

El String a imprimir le llega a la clase a través de su constructor (línea 8). La


propiedad Aleatorio contiene el valor numérico aleatorio que le proporciona la clase
Random (línea 10).

El método run (que se invoca automáticamente tras el método start)


imprime la frase por consola (línea 16) y se duerme durante un tiempo aleatorio
(línea 17). Estas acciones se repiten indefinidamente en un bucle (líneas 15 y 19). El
bloque “try-catch” resulta necesario para contemplar el caso de que nuestro thread
sea interrumpido.

1 import java.util.Random;
2
3 public class ThreadBasico implements Runnable {
4
5 private String Frase;
6 private Random Aleatorio;
7
8 public ThreadBasico (String Frase) {
9 this.Frase = Frase;
10 Aleatorio = new Random();
11 } // Constructor
12
13 public void run() {
14 try {
15 do {
16 System.out.println (Frase);
17 Thread.sleep( (long) (Math.abs(Aleatorio.nextInt())
18 % 500));
19 } while (true);
20 } catch (InterruptedException e) {}
21 } // run
22
23 } // class

Para probar el comportamiento de la clase ThreadBasico se proporciona la


clase ThreadBasicoMain, donde se crean dos instancias de ThreadBasico (líneas 4 a
7). Cada instancia recibe una palabra de la consola a través del parámetro de tipo
String[] del método main; de esta manera, tecleando en consola el comando de
ejecución: java ThreadBasicoMain uno dos, en args[0] recogemos el valor “uno” y
en args[1] el valor “dos”.

En las líneas 10 y 11 se arrancan los dos threads instanciados, lo que


provoca la ejecución del método run de ambas instancias. El resultado de una
ejecución concreta se muestra a continuación del código de la clase.

1 public class ThreadBasicoMain {


2
© JESÚS BOBADILLA SANCHO 17

3 public static void main(String[] args) {


4 Thread PrimerHilo = new Thread
5 (new ThreadBasico (args[0]));
6 Thread SegundoHilo = new Thread
7 (new ThreadBasico (args[1]));
8
9
10 PrimerHilo.start();
11 SegundoHilo.start();
12
13 }
14 }

2.4 EJEMPLO CON UN NÚMERO VARIABLE DE HILOS


(THREADS)
El ejemplo que se desarrolla en este apartado es bastante similar al anterior,
aunque en este caso el número de hilos que creamos varía dependiendo de los datos
de entrada.

La aplicación recoge una palabra que se le pasa como argumento y escribe


cada una de sus letras en un orden aleatorio; para conseguir este efecto se asigna un
hilo a cada letra (o carácter), se detiene cada hilo durante un tiempo aleatorio y
posteriormente se impr ime el carácter. La estructura de la aplicación presenta las
siguientes dependencias:
PruebaLetrasHilos

LetrasHilos

CHilo
18 © JESÚS BOBADILLA SANCHO

Chilo implementa el interfaz Runnable . En su constructor (línea 6) recoge


un String de tamaño uno. Cuando se activa el hilo (thread) en primer lugar se le
“duerme” durante un tiempo aleatorio (líneas 12 y 13) y posteriormente se imprime
el carácter (línea 14).

1 import java.util.Random;
2
3 public class CHilo implements Runnable {
4 private String Caracter;
5
6 public CHilo (String Caracter) {
7 this.Caracter = Caracter;
8 }
9
10 public void run() {
11 try {
12 Thread.sleep( (long)
13 (Math.abs(new Random().nextInt())%1000));
14 System.out.print(Caracter);
15 } catch (InterruptedException e) {}
16 }
17
18 }

La clase LetrasHilos define un array de threads (línea 4) y posteriormente


itera en un bucle tantas veces como posiciones tiene el String que recoge como
parámetro (línea 5). En cada iteración se crea un thread utilizando la clase CHilo y
se le pasa como argumento un carácter del String (líneas 6 y 7). En la línea 8 se
arranca el hilo.

Aunque el array de hilos no es necesario en el ejemplo, resulta adecuado


mostrar la manera de crear estas estructuras de hilos, puesto que en otras ocasiones
son muy útiles.

1 public class LetrasHilos {


2
3 public LetrasHilos (String Frase) {
4 Thread[] Hilo = new Thread[Frase.length()];
5 for (int i=0; i!=Frase.length();i++) {
6 Hilo[i] = new Thread
7 (new CHilo(Frase.substring(i,i+1)));
8 Hilo[i].start();
9 }
10 return;
11 }
12 }
© JESÚS BOBADILLA SANCHO 19

El ejemplo se completa proporcionando una clase que contenga el método


main e instancie a la clase LetrasHilos, pasando un String como argumento. Esto se
realiza en la línea 8 de la clase PruebaLetrasHilos.

1 public class PruebaLetrasHilos {


2
3 public static void main(String[] args) {
4 if (args.length!=1) {
5 System.out.println("Hay que introducir un argumento");
6 System.exit(0);
7 }
8 LetrasHilos Instancia = new LetrasHilos(args[0]);
9 }
10
11 }

En el siguiente gráfico se muestra la consola del sistema con varias


ejecuciones de la clase PruebaLetrasHilos; en cada una de ellas el orden de las
letras es diferente, como cabe esperar por los tiempos aleatorios en los que se
“despiertan” las distintas instancias de la clase CHilo.

2.5 EXCLUSIÓN MUTUA (MODIFICADOR


SYNCHRONIZED)
Cuando se programa haciendo uso de concurrencia (paralelismo) hay que
tener en cuenta la necesidad de establecer, en ocasiones, zonas de exclusión en el
acceso a los recursos. Pongamos un ejemplo de esta situación: para controlar el paso
a un túnel de un solo carril, en ambos lados del túnel se colocan semáforos. Cuando
un automóvil encuentra el semáforo en verde, se le da permiso de entrada e
inmediatamente se pone el semáforo en rojo, hasta que sale del túnel. El código
quedaría como:

1 .....
2 if (Semaforo.equals(”Verde”)) {
3 PermisoPaso();
20 © JESÚS BOBADILLA SANCHO

4 Semaforo=”Rojo”
5 }
6 .......................

Si dos o más threads (que se ejecutan en paralelo) leen simultáneamente el


valor de la propiedad Semaforo y determinan que “está” en verde, esos threads
ejecutarán al mismo tiempo el método PermisoPaso, produciendo una situación
errónea (y en este caso muy peligrosa).

Para evitar situaciones como la ilustrada podemos “proteger” el acceso a los


recursos (en nuestro ejemplo el recurso abstracto es el túnel y el recurso concreto en
la variable Semaforo). Java permite definir métodos de “exclusión mutua”, donde
sólo puede ejecutarse un hilo en cada momento.

Colocando el modificador synchronized en un método conseguimos que su


ejecución sea exclusiva, es decir, que no podrá haber dos o más hilos ejecutando
simultáneamente ese método.

El método PeticionEntradaATunel, al incluir el modificador synchronized,


resuelve el problema de exclusión mutua que hemos planteado.

1 public synchronized boolean PeticionEntradaATunel(){


2 .....
3 if (Semaforo.equals(”Verde”)) {
4 PermisoPaso();
5 Semaforo=”Rojo”
6 }
7 .......................
8 }

2.6 MÉTODOS MÁS IMPORTANTES DE LA CLASE


THREAD
Además de los dos constructores más utilizados de esta clase:
Thread() y Thread(Runnable destino)

…existe una amplia colección de métodos destinados, en su mayoría, a


controlar la ejecución de los hilos. La siguiente tabla muestra los métodos más útiles
y comúnmente utilizados:
© JESÚS BOBADILLA SANCHO 21

Método Acción
void start() Provoca el comienzo de la ejecución del hilo.
void run() Se ejecuta automáticamente tras start, cuando el hilo se ha
construido a partir del interfaz Runnable (o la clase
Thread).
static void sleep(long ms) Detiene la ejecución del hilo durante, al menos, los
milisegundos indicados
void destroy() Elimina el hilo sin liberara recursos
void setPriority(int Prioridad) Establece una prioridad para el hilo (MAX_PRIORITY,
MIN_PRIORITY, NORM_PRIORITY)
int getPriority() Indica la prioridad del hilo
final void join() Espera a que este hilo termine
static Thread currentThread() Devuelve un apuntador al hilo que se está ejecutando
boolean isAlive() Indica si el hilo ha sido arrancado y todavía no ha
terminado
void setDaemon(boolean on) Establece el hilo como demonio o como usuario. Los hilos
demonio están supeditados a los hilos que los han creado,
de tal manera que cuando el creador termina, sus hijos
“demonio” también finalizan
public final bolean isDaemon() Indica si el hilo es de tipo demonio o usuario
void setName(String name) Asigna un nombre al hilo
String getName() Devuelve el nombre del hilo
LECCIÓN 3

STREAMS Y FICHEROS

3.1 INTRODUCCIÓN
Los ficheros y “streams” se utilizan en Java haciendo uso del paquete
java.io , que contiene una gran cantidad de interfaces y clases. En este apartado
trataremos las clases más utilizadas, que nos permitirán realizar lecturas y escrituras
en ficheros secuenciales y aleatorios.

Las clases InputStream y OutputStream derivan de manera directa de


java.lang.Object y tienen un especial interés para nosotros, puesto que son las
superclases de FileInputStream y FileOutputStream, que nos permitirán trabajar con
ficheros secuenciales de datos. InputStream y OutputStream también se utilizan para
canalizar datos a través de sockets, por lo que juegan un papel importante en las
comunicaciones TCP que proporcionan alg unas clases del paquete java.net.

Al ser InputStream y OutputStream clases muy genéricas, nos proporcionan


únicamente la base para hacer uso de ficheros secuenciales. Sus métodos más
importantes son read() y write(int b); son métodos abstractos que definen la función
básica de lectura/escritura de un byte. Puesto que estas clases son abstractas, para
cada dispositivo específico de entrada/salida debemos crear o utilizar una clase que
derive de las mismas e implemente sus métodos read y write.

La clase RandomAccessFile permite trabajar con ficheros de acceso directo,


proporcionando numerosos métodos de lectura y escritura de los tipos primitivos de
datos. La clase File facilita la gestión de los ficheros en sí (renombrado, borrado,
atributos, etc.), sin centrarse en su contenido.
24 © JESÚS BOBADILLA SANCHO

3.2 FICHEROS DE ACCESO SECUENCIAL


Para programar acciones sobre ficheros secuenciales vamos a hacer uso de
las clases FileInputStream y FileOutputStream. Estas clases proporcionan una serie
de constructores que permiten indicar el fichero sobre el que se actúa; nosotros
utilizaremos los constructores:
FileInputStream(String FicheroOrigen)
FileOutputStream(String FicheroDestino)

Las clases FileInputStream y FileOutputStream implementan los métodos


read y write de las clases InputStream y OutputStream, por lo que no son clases
abstractas y podemos crear instancias de las mismas:

FileInputStream FicheroOrigen = new FileInputStream("LaFoto.jpg");


FileOutputStream FicheroDestino = new FileOutputStream("Salida.txt");

La clase FileInputStream proporciona tres métodos sobrecargados read y la


clase FileOutputStream otros tres métodos sobrecargados write. El método close
está accesible en ambas clases y su uso es aconsejable para liberar recursos
asociados con el stream.

Método Descripción
int read() Lee un byte del fichero
int read(byte[] b) Lee hasta b.length bytes del fichero, depositándolos en
la matriz b (devuelve el número de bytes leidos)
int read(byte[] b, int off, int len) Lee hasta len bytes del fichero, depositándolos en la
matriz b, a partir de off (devuelve el número de bytes
leidos)
void write(int b) Escribe el byte especificadoen el fichero
void write(byte[] b) Escribe b.length bytes en el fichero, tomándolos de la
matriz b
void write(byte[] b, int off, int len) Escribe len bytes en el fichero, obteniéndolos de la
matriz b, a partir de off
close() Cierra el fichero y libera los recursos del sistema
asociados con el stream.

En los ejemplos de este apartado utilizaremos FileInputStream y


FileOutputStream, debido a que estas clases son válidas para todo tipo de ficheros
secuenciales (imágenes, sonidos, etc.); sin embargo, cuando queramos trabajar con
ficheros de texto, también podemos utilizar las clases FileReader y FileWriter.
© JESÚS BOBADILLA SANCHO 25

3.3 LECTURA DE FICHEROS SECUENCIALES


Usando los conceptos explicados en los apartados anteriores, estamos en
disposición de poder implementar un programa que lea un fichero secuencial y lo
visualice por pantalla. El fichero que leeremos será el propio código de la clase:
LeeFichero.

En la línea 1 importamos los objetos del paquete java.io . En las líneas 6 y 7


definimos la matriz buffer donde recogemos los bytes del fichero que vamos
leyendo. El tamaño del buffer (64) lo asignamos arbitrariamente.

Las líneas 11 y 12 contienen la instanciación del objeto FicheroOrigen, de


tipo FileInputStream, asociado al fichero físico de nombre “LeeFichero.java”. La
instanciación puede levantar la excepción FileNotFoundException (línea 27), por lo
que se debe emplear la estructura try-catch.

En la línea 15 se comienza un bucle en el que se leen grupos de datos de


hasta 64 bytes (línea 16) y se imprimen por consola (línea 17 y 18). El bucle se
termina cuando en una lectura nos encontramos menos de TAMANIO_BUFFER
bytes. Finalmente se cierra el fichero (línea 20).

En la línea 21 se recoge la excepción IOException, que podría levantarse


debido a un problema en la lectura de datos (método read de la línea 16) o al cerrar
el fichero (método close de la línea 20).

1 import java.io.*;
2
3 public class LeeFichero {
4
5 public static void main(String[] argumentos) {
6 final int TAMANIO_BUFFER = 64;
7 byte buffer[] = new byte[TAMANIO_BUFFER];
8 int NumBytes;
9
10 try {
11 FileInputStream FicheroOrigen =
12 new FileInputStream("LeeFichero.java");
13
14 try {
15 do {
16 NumBytes = FicheroOrigen.read(buffer);
17 System.out.print(new
18 String(buffer,0,NumBytes));
19 } while (NumBytes == TAMANIO_BUFFER);
20 FicheroOrigen.close();
21 } catch (IOException e){
26 © JESÚS BOBADILLA SANCHO

22 System.out.println("Error leyendo los


23 datos o cerrando el fichero");
24 }
25
26
27 } catch (FileNotFoundException e) {
28 System.out.println("Fichero no encontrado");
29 }
30
31 }
32
33 }

3.4 ESCRITURA DE FICHEROS SECUENCIALES


La clase EscribeFichero lee el texto que el usuario introduce por teclado y lo
va escribiendo en un fichero. Su estructura es muy similar a la de la clase
LeeFichero, aunque varían algunos detalles.

En las líneas 11 y 12 se instancia el objeto FicheroDestino, de tipo


FileOutputStream. La línea 16 recoge las pulsaciones de teclado del usuario,
haciendo uso de System.in; en la línea 17 se emplea el método write para escribir
bloques de NumBytes bytes en el fichero de salida.

En la condición de terminación del bucle se comprueba si el usuario ha


pulsado la tecla “enter”, introduciendo una nueva línea
(Character.LINE_SEPARATOR), que se corresponde con el valor 13.

El tratamiento de excepciones es idéntico al del ejemplo anterior.


© JESÚS BOBADILLA SANCHO 27

1 import java.io.*;
2
3 public class EscribeFichero {
4
5 public static void main(String[] argumentos) {
6 final int TAMANIO_BUFFER = 64;
7 byte buffer[] = new byte[TAMANIO_BUFFER];
8 int NumBytes;
9
10 try {
11 FileOutputStream FicheroDestino = new
12 FileOutputStream("Salida.txt");
13
14 try {
15 do {
16 NumBytes = System.in.read(buffer);
17 FicheroDestino.write(buffer,0,NumBytes);
18 } while (buffer[0] !=
19 Character.LINE_SEPARATOR);
20 FicheroDestino.close();
21 } catch (IOException e){
22 System.out.println("Error escribiendo
23 los datos o cerrando el fichero");
24 }
25
26
27 } catch (FileNotFoundException e) {
28 System.out.println("Fichero no encontrado");
29 }
30
31 }
32
33 }
28 © JESÚS BOBADILLA SANCHO

3.5 EJEMPLO DE LECTURA/ESCRITURA DE FICHEROS


SECUENCIALES
Partiendo de los ejemplos LeeFichero y EscribeFichero utilizados en los
apartados anteriores, vamos a implementar una aplicación que lea un fichero,
permita establecer un procesamiento sobre su contenido y, posteriormente, imprima
el resultado. El procesamiento, en nuestro ejemplo, se limitará a pasar el texto a
mayúsculas. La estructura de la aplicación es la siguiente:

La clase CopiaFichero contiene un constructor que admite dos parámetros


de tipo String, para que podamos indicar el fichero origen y el fichero destino sobre
los que actuaremos; también incorpora un método abstracto Procesa, que deberemos
implementar para realizar el tratamiento deseado.

La clase CopiaMayusculas extiende CopiaFichero e implementa el método


Procesa, de manera que transforma el texto de entrada en mayúsculas.
CopiaMayusculasPrincipal crea una instancia de CopiaMayusculas, iniciando la
lectura, procesamiento y escritura programados en el constructor de la clase
CopiaFichero.

Veamos cada una de estas tres clases: CopiaFichero se encarga, ante todo,
de la lectura y escritura de los ficheros. Su constructor (línea 5) admite en sus
parámetros el nombre de los ficheros origen y destino, que serán utilizados en las
líneas 11 a 14 para crear instancias de FileOutputStream y FileInputStream.

Las líneas 18, 19 y 20 se encargan, respectivamente, de leer datos del


fichero de origen, procesarlos y escribir el resultado en el fichero de destino. Estas
instrucciones se repiten en un bucle (líneas 17 y 21) que procesa blo ques de bytes de
un tamaño máximo de TAMANIO_BUFFER.
© JESÚS BOBADILLA SANCHO 29

El método Procesa (línea 34) es abstracto; recibe el bloque de datos buffer y


la indicación de cuantos bytes han sido leídos (NumBytes). Las clases que
implementen este método tomarán ambos datos como información para realizar el
procesamiento deseado y devolverán un bloque de datos que se escribirá en el
fichero de salida.

1 import java.io.*;
2
3 abstract public class CopiaFichero {
4
5 CopiaFichero(String NombreOrigen, String NombreDestino) {
6 final int TAMANIO_BUFFER = 64;
7 byte buffer[] = new byte[TAMANIO_BUFFER];
8 int NumBytes;
9
10 try {
11 FileOutputStream FicheroDestino = new
12 FileOutputStream(NombreDestino);
13 FileInputStream FicheroOrigen = new
14 FileInputStream(NombreOrigen);
15
16 try {
17 do {
18 NumBytes = FicheroOrigen.read(buffer);
19 buffer = Procesa(buffer,NumBytes);
20 FicheroDestino.write(buffer,0,NumBytes);
21 } while (NumBytes == TAMANIO_BUFFER);
22 FicheroOrigen.close();
23 FicheroDestino.close();
24 } catch (IOException e){
25 System.out.println(e.toString());
26 }
27
28 } catch (FileNotFoundException e) {
29 System.out.println("Fichero no encontrado");
30 }
31
32 }
33
34 abstract public byte[] Procesa(byte[] buffer,
35 int NumBytes);
36
37 }

La clase CopiaMayusculas extiende a CopiaFichero (línea 1),


proporcionando un constructor análogo al de su superclase (línea 3). La parte más
interesante se corresponde con el procesamiento de los datos, que se realiza gracias a
la implementación del método abstracto Procesa (línea 8). En general se podrían
30 © JESÚS BOBADILLA SANCHO

implementar complejos algoritmos de encriptación, compresión, transformación, etc.


de los datos que nos llegan como bloques sucesivos de bytes, aunque por sencillez,
en nuestro ejemplo, únicamente pasaremos los caracteres a mayúsculas (líneas 9 a
11).

1 public class CopiaMayusculas extends CopiaFichero {


2
3 CopiaMayusculas(String NombreOrigen,
4 String NombreDestino) {
5 super(NombreOrigen, NombreDestino);
6 }
7
8 public byte[] Procesa(byte[] buffer, int NumBytes) {
9 String Linea = new String(buffer,0,NumBytes);
10 Linea = Linea.toUpperCase();
11 return Linea.getBytes();
12 }
13
14 }

En este momento, para probar el ejemplo, solo nos resta hacer uso del
método main, creando una instancia de la clase CopiaMayusculas. La clase
CopiaMayusculasPrincipal realiza este sencillo cometido:

1 public class CopiaMayusculasPrincipal {


2
3 public static void main(String[] Argumentos) {
4 CopiaMayusculas ProgramaAMayusculas = new
5 CopiaMayusculas("CopiaFichero.java",
6 "Salida.txt");
7 }
8
9 }
© JESÚS BOBADILLA SANCHO 31

3.6 FICHEROS DE ACCESO DIRECTO


El paquete java.io contiene una clase muy útil para el tratamiento de
ficheros de acceso directo, su nombre es RamdomAcessFile e implementa los
interfaces DataInput y DataOutput, por lo que contiene métodos de lectura y
escritura sobre este tipo de ficheros.

De sus dos constructores, vamos a emplear:


RandomAccessFile(String NombreFichero, String modo)

El parámetro modo puede tomar 4 valores diferentes, los más utilizados son:
“r” para lectura y “rw” para lectura y escritura.

Entre sus métodos vamos a destacar los siguientes:

Método Descripción
void close() Cierra el fichero y libera sus recursos asociados
long getFilePointer() Proporciona el desplazamiento actual de la posición de lectura
/escritura del fichero
long length() Devuelve la longitud del fichero (en bytes)
int read() Lee un byte
int read(byte[] b) Lee hasta b.length() bytes del fichero
int read(byte[] b, int off, Lee hasta len bytes del fichero, empezando a colocarlos a partir
int len) de off
boolean readBoolean() Lee un valor booleano
byte readByte() Lee un byte (con signo)
char readChar() Lee un carácter
double readDouble() Lee un double
float readFloat() Lee un float
int readInt() Lee un entero
long readLong() Lee un long
short readShort() Lee un short
String readLine() Lee un String
void write(int b) Escribe un byte
void write (byte[] b) Escribe b.length() bytes en el fichero
void write (byte[] b, int Escribe len bytes del fichero, empezando a tomarlos a partir de
off, int len) off
void Escribe un valor booleano
writeBoolean(boolean v)
void writeByte(int v) Escribe un byte (con signo)
void writeChar(int v) Escribe un carácter
void writeDouble(double Escribe un double
v)
void writeFloat(float v) Escribe un float
void writeInt(int v) Escribe un entero
32 © JESÚS BOBADILLA SANCHO

void writeLong(long v) Escribe un long


void writeShort(short v) Escribe un short
void writeChars(String v) Escribe un String
void seek (long pos) Sitúa el puntero en la posición pos
void setLength(long Establece un Nuevo tamaño de fichero
longitud)
int skipBytes(int v) Trata de saltar v bytes en el fichero

Para ilustrar el uso de la clase RandomAccessFile vamos a crear una clase


EscribeAleatorio, que escribe una secuencia de números primos en un fichero, y la
vamos a complementar con otra clase: LeeAleatorio, que lee la mitad superior de los
números escritos.

La clase EscribeAleatorio (línea 3) tiene un constructor (línea 8) que admite


como parámetro la cantidad de números primos que se van a calcular e imprimir.
Los primos se calculan mediante el método GeneraPrimos (línea 29) y se van
almacenando en la matriz Primos (líneas 6 y 45).

Una vez invocado el método GeneraPrimos (línea 11), se crea una instancia
de la clase RandomAccesFile (línea 13) con modo de lectura/escritura (línea 14); tras
ello, nos metemos en un bucle (línea 16) que itera tantas veces como primos se han
calculado. En cada iteración se invoca al método writeInt (línea 17).

Como se puede observar, el tratamiento de excepciones es idéntico al que


realizábamos con los ficheros de acceso secuencial.

1 import java.io.*;
2
3 public class EscribeAleatorio {
4
5 private int NumPrimos;
6 private int[] Primos;
7
8 EscribeAleatorio(int NumPrimos) {
9 this.NumPrimos = NumPrimos;
10 Primos = new int[NumPrimos];
11 GeneraPrimos(NumPrimos);
12 try {
13 RandomAccessFile FicheroPrimos = new
14 RandomAccessFile("Primos.txt", "rw");
15 try {
16 for (int i=0;i<NumPrimos;i++)
17 FicheroPrimos.writeInt(Primos[i]);
18 FicheroPrimos.close();
19 } catch (IOException e){
20 System.out.println("Error escribiendo los
21 datos o cerrando el fichero");
© JESÚS BOBADILLA SANCHO 33

22 }
23 } catch (FileNotFoundException e) {
24 System.out.println("Fichero no encontrado");
25 }
26 }
27
28
29 private void GeneraPrimos(int NumPrimos) {
30 Primos[0] = 2;
31 int PrimosHallados = 1;
32 int PosiblePrimo = 3;
33 int Indice=0;
34 boolean Primo = true;
35
36 do {
37 while (Primo && (Indice<PrimosHallados)) {
38 if (PosiblePrimo % Primos[Indice] == 0)
39 Primo = false;
40 else
41 Indice++;
42 }
43
44 if (Primo) {
45 Primos[PrimosHallados] = PosiblePrimo;
46 PrimosHallados++;
47 }
48
49 Primo = true;
50 PosiblePrimo++;
51 Indice = 0;
52
53 } while (PrimosHallados<NumPrimos ); // while exterior
54
55 } // GeneraPrimos
56
57 } //clase

Para conseguir que se realicen las acciones programadas en la clase


EscribeAleatorio debemos crear una instancia de la misma, indicando el número de
primos que deseamos obtener (línea 5 de la clase EscribeAleatorio100Primos):

1 public class EscribeAleatorio100Primos {


2
3 public static void main(String[] Argumentos) {
4 EscribeAleatorio CienPrimos = new
5 EscribeAleatorio(100);
6 }
7 }
34 © JESÚS BOBADILLA SANCHO

En la ventana que muestra la consola podemos apreciar como los valores


enteros no se escriben en formato UNICODE (tipo texto), sino que se graban los 4
bytes que codifican el valor entero.

Para poder leer los últimos 50 números primos que hemos hallado,
escribimos la clase LeeAleatorio :

1 import java.io.*;
2
3 public class LeeAleatorio {
4
5 public static void main(String[] args) {
6 int Primo;
7 long LongitudFichero, Puntero;
8
9 try {
10 RandomAccessFile FicheroPrimos = new
11 RandomAccessFile("Primos.txt", "r");
12 try {
13 LongitudFichero = FicheroPrimos.length();
14 FicheroPrimos.seek(LongitudFichero/2);
15 Puntero = FicheroPrimos.getFilePointer();
16 while (Puntero<LongitudFichero) {
17 Primo = FicheroPrimos.readInt();
18 System.out.print(Primo+" ");
19 Puntero = FicheroPrimos.getFilePointer();
20 }
21 FicheroPrimos.close();
22 } catch (IOException e){
23 System.out.println("Error leyendo los datos
24 o cerrando el fichero");
25 }
26 } catch (FileNotFoundException e) {
27 System.out.println("Fichero no encontrado");
28 }
29 }
30
31 } //clase
© JESÚS BOBADILLA SANCHO 35

En las líneas 10 y 11 se instancia el objeto FicheroPrimos, abierto en modo


lectura (“r”); posteriormente se halla su longitud, haciendo uso del método length
(línea 13). En la línea 14 nos situamos en la mitad del fichero, utilizando el método
seek.

La línea 16 marca el comienzo de un bucle que iterará hasta que hallamos


alcanzado la longitud del fichero. En cada iteración leemos un primo haciendo uso
del método readInt (línea 17), lo mostramos por la consola (línea 18) y actualizamos
el apuntador de posición del fichero utilizando el método getFilePointer (línea 19).

3.7 LA CLASE FILE


La clase File nos permite tener acceso a la información de los ficheros desde
el punto de vista de su gestión a nivel del sistema operativo (borrar, renombrar,
preguntar si existe, si es un directorio, si se puede escribir en el mismo, etc.),
mientras que las clases utilizadas en los apartados anteriores permitía n el tratamiento
del contenido de los ficheros.

El constructor más simple de la clase nos permite crear un objeto File a


partir de la ruta a un fichero/directorio:
File(String NombreFichero)

Los métodos más representativos de la clase son:

boolean canRead() boolean canWrite() boolean delete()


void deleteOnExit() boolean exists() String getAbsolutePath()
String getName() String getParent() File getParentFile()
String getPath() boolean isDirectory() boolean isFile()
boolean isHidden() long lastModified() long length()
String[] list() File[] listFiles() static File[] listRoots()
boolean mkdir() boolean renameTo(File f) boolean setLastModified(long time)
boolean setReadOnly() String toString() URL toURL()
36 © JESÚS BOBADILLA SANCHO

Para ilustrar parte de las posibilidades de la clase File se proporciona el


ejemplo SistemaDeFicheros, que instancia a la clase GUISistemaDeFicheros, donde
se realiza todo el procesamiento. GUISistemaDeFicheros permite “navegar” a través
de los directorios del sistema, ofreciendo la siguiente información de cada
fichero/directorio seleccionado:
• Existe permiso de lectura
• Existe permiso de escritura
• Es directorio
• Está oculto

1 public class SistemaDeFicheros {


2 public static void main(String[] argumentos) {
3 GUISistemaDeFicheros Interfaz = new
4 GUISistemaDeFicheros();
5 }
6 }

El interfaz de la aplicación presenta el siguiente aspecto:

En la parte izquierda de la ventana se sitúa una lista desplegable que


mostrará todos los ficheros/directorios que parten de cada directorio seleccionado.
Inicialmente nos situamos por programa en el directorio raíz del disco C. En la parte
derecha se sitúan 4 cajas de verificación deshabilitadas, que muestran la información
básica pedida de cada fichero o directorio seleccionado.

En la clase GUISistemaDeFicheros se declaran como atributos los


componentes que ofrecen información en este interfaz de usuario: la lista
desplegable y las cajas de verificación (líneas 7 y 9 a 12). También se declara el
objeto Fichero, de tipo File (línea 8).

En las líneas 15 a 35 se declaran y definen los elementos necesarios para


crear el interfaz de usuario mostrado. La línea 37, especialmente importante en este
ejemplo, instancia el objeto Fichero, de tipo File, a partir del camino “C:/”

La línea 38 contiene una llamada al método EstableceLista , que inserta en la


lista desplegable Ficheros los literales que se le proporcionan como argumento.
© JESÚS BOBADILLA SANCHO 37

En la línea 39 se añade a la lista desplegable una instancia de la clase de


tratamiento de eventos de elemento ElementoSeleccionado.

Las últimas líneas de GUISistemaDeFicheros (41 a 47) sirven para


visualizar adecuadamente en el marco los componentes que contiene.

El método EstableceLista (línea 50), en primer lugar elimina todos los


elementos de la lista desplegable, usando el método removeAll, que pertenece a la
clase Choice; después, para podernos desplazar hacia directorios superiores, inserta
el directorio padre (“../”) como una opción de la lista desplegable (línea 53); antes de
hacer esta acción, comprobamos que no nos encontramos en el directorio raíz (línea
52). Finalmente, se va añadiendo a la lista desplegable (línea 55) todos los elementos
de la matriz NombreFicheros.

La clase ElementoSeleccionado (línea 58) implementa el único método,


itemStateChanged, del interfaz ItemListener. Cuando el usuario selecciona un
elemento de la lista desplegable, las acciones que se realizan son: se construye la
ruta al fichero o directorio seleccionado (líneas 61 y 62), concatenando la ruta actual
(método getAbsolutePath ) con el literal del elemento que produjo el evento (línea
62). Posteriormente, en la línea 63, creamos una nueva instancia de la clase File para
acceder a los datos del nuevo fichero o directorio; si nos encontramos en un
directorio (método isDirectory en la línea 64) actualizamos la lista desplegable con
el nombre de todos los ficheros/directorios contenidos en el directorio actual
(método list en la línea 65).

Finalmente, en las líneas 71 a 74, actualizamos el contenido de las cajas de


verificación usando los métodos de la clase File: canRead, canWrite, isDirectory e
isHidden.

1 import java.awt.*;
2 import java.awt.event.*;
3 import java.io.*;
4
5 public class GUISistemaDeFicheros {
6
7 private Choice Ficheros;
8 private File Fichero;
9 private Checkbox PermisoEscritura;
10 private Checkbox PermisoLectura;
11 private Checkbox EsDirectorio;
12 private Checkbox EstaOculto;
13
14 GUISistemaDeFicheros() {
15 Frame MiMarco = new Frame();
16 Panel MiPanel = new Panel(new GridLayout(1,2));
17 Panel PanelInformacion = new
38 © JESÚS BOBADILLA SANCHO

18 Panel(new GridLayout(4,1));
19 PanelInformacion.setBackground(Color.green);
20 PermisoEscritura = new
21 Checkbox("Permiso de escritura");
22 PermisoEscritura.setEnabled(false);
23 PanelInformacion.add(PermisoEscritura);
24 PermisoLectura = new Checkbox("Permiso de lectura");
25 PermisoLectura.setEnabled(false);
26 PanelInformacion.add(PermisoLectura);
27 EsDirectorio = new Checkbox("Directorio");
28 EsDirectorio.setEnabled(false);
29 PanelInformacion.add(EsDirectorio);
30 EstaOculto = new Checkbox("Oculto");
31 EstaOculto.setEnabled(false);
32 PanelInformacion.add(EstaOculto );
33
34 Ficheros = new Choice();
35 Ficheros.setBackground(Color.green);
36
37 Fichero = new File("c:/");
38 EstableceLista(Fichero.list());
39 Ficheros.addItemListener(new ElementoSeleccionado());
40
41 MiPanel.add(Ficheros);
42 MiPanel.add(PanelInformacion);
43 MiMarco.add(MiPanel);
44 MiMarco.setSize(400,200);
45 MiMarco.setTitle("Navegación por el sistema
46 de ficheros");
47 MiMarco.setVisible(true);
48 }
49
50 private void EstableceLista(String[] NombreFicheros) {
51 Ficheros.removeAll();
52 if (Fichero.getParent()!=null)
53 Ficheros.add("../");
54 for (int i=0;i<NombreFicheros.length;i++)
55 Ficheros.add(NombreFicheros[i]);
56 }
57
58 private class ElementoSeleccionado implements
59 ItemListener {
60 public void itemStateChanged(ItemEvent Evento){
61 String Camino=Fichero.getAbsolutePath()+"/"+
62 Ficheros.getSelectedItem();
63 Fichero = new File(Camino);
64 if (Fichero.isDirectory()){
65 EstableceLista(Fichero.list());
66 Ficheros.setBackground(Color.green);
© JESÚS BOBADILLA SANCHO 39

67 }
68 else
69 Ficheros.setBackground(Color.orange);
70
71 PermisoEscritura.setState(Fichero.canWrite());
72 PermisoLectura.setState(Fichero.canRead());
73 EsDirectorio.setState(Fichero.isDirectory());
74 EstaOculto.setState(Fichero.isHidden());
75 }
76
77 }
78
79 }
LECCIÓN 4

COMUNICACIONES BASADAS EN EL
PROTOCOLO TCP

4.1 INTRODUCCIÓN
El protocolo TCP (Transmisión Control Protocol) funciona en el nivel de
transporte, basándose en el protocolo de red IP (Internet Protocol). IP proporciona
comunicaciones no fiables y no basadas en conexión, muy dependientes de
saturaciones en la red, caídas de nodos, etc. Por el contrario, TCP está orientado a
conexión y proporciona comunicaciones fiables basadas en mecanismos de red que
gestionan el control de flujo de paquetes y de congestión en los nodos.

El control de flujo aludido es una característica técnica importante en el


funcionamiento del protocolo TCP: evita que los nodos que envían información
puedan saturar a los que la reciben; para lograr este objetivo, el protocolo TCP
utiliza de manera interna un mecanismo de sincronización basado en tres
comunicaciones realizadas entre cliente y servidor.

Un aspecto que hay que tener en cuenta cuando se programan


comunicaciones con TCP es la eficiencia que conseguimos. Cuando vamos a
traspasar una gran cantidad de información (por ejemplo un fichero de vídeo), si no
necesitamos tiempo real, TCP es un protocolo adecuado, puesto que el tiempo
necesario para establecer la comunicación es despreciable respecto al utilizado para
transmitir los datos. En el otro extremo, si necesitamos una gran cantidad de
comunicaciones cortas en las que la fiabilidad no es muy importante, TCP no es un
protocolo adecuado; sería mucho mejor UDP, que se explicará más adelante. Un
ejemplo de esta situación es el caso de una serie de sensores de temperatura y
presión que mandan sus medidas por la red cada segundo y un aparato que recoge
las medidas y las visualiza en una central de control.
42 © JESÚS BOBADILLA SANCHO

En definitiva, el protocolo TCP posibilita la comunicación fiable de datos


entre nodos cliente y nodos servidores; resulta especialmente adecuado cuando el
tamaño de los datos que se transmiten no es pequeño.

En Java, las comunicaciones TCP se realizan utilizando la clásica


abstracción de socket. Los sockets nos permiten establecer y programar
comunicaciones sin tener que conocer los niveles inferiores sobre los que se
asientan.

Para identificar el destino de los paquetes de datos, los sockets utilizan los
conceptos de dirección y puerto. La dirección se refiere a la máquina a la que se
dirigen los datos; se determina gracias a la resolución de nombres que proporcionan
los DNS o simplemente aportando al socket, de manera directa, la dirección IP del
nodo destino.

Puesto que una misma máquina (nodo) puede hacerse cargo de recoger
varias comunicaciones diferentes de datos (habitualmente ligadas a distintos
servicios), existe la necesidad de proporcionar un mecanismo que nos permita
distinguir los paquetes que llegan relacionados con los distintos servicios ofrecidos
(correo, news, web, etc.): este mecanismo son los puertos. Los puertos se
representan con valores enteros, que no deben coincidir para diferentes servicios.

Los datos que enviemos a un nodo con dirección IP 138.100.57.45 y puerto


6000 acabarán siendo tratados por programas diferentes (servidores) que los
enviados al mismo nodo (138.100.57.45) y puerto 7000. Este mecanismo no es tan
diferente al que usamos en el correo ordinario: además de la dirección de la casa a la
que enviamos una carta, indicamos la persona destinataria (que es el equivalente al
puerto).

Los valores numéricos de puertos 1-1023 se reservan a servicios de interés


general, montados a menudo sobre protocolos de uso extendido: el 80 para web con
HTTP, el 25 para correo saliente con SMTP, el 110 para correo entrante con POP3,
el 119 para el servicio de noticias con NNTP, etc. Los valores de puertos entre 1024
y 49151 se usan para servicios específicos de uso no general, el resto (a partir de
49152) se emplean para designar servicios de uso esporádic o.

En los siguientes apartados se explicará el mecanismo general con el que se


utilizan los sockets TCP, la diferencia entre las clases Socket y ServerSocket, un
primer ejemplo de comunicaciones (Hola mundo), una aplicación “teletipo”, otra de
traspaso del contenido de un fichero, etc.
© JESÚS BOBADILLA SANCHO 43

4.2 ESTABLECIMIENTO DE COMUNICACIONES


Java proporciona dos clases de abstracción de comunicaciones TCP: una
para los procesos cliente (Socket) y otra para los procesos servidor (ServerSocket).
Antes de entrar en los detalles de programación vamos a mostrar gráficamente el
esquema básico de establecimiento de comunicaciones TCP.

1 El programa que proporciona el servicio (programa servidor) crea una


instancia de la clase ServerSocket, indicando el puerto asociado al servicio:
ServerSocket SocketServidor = new ServerSocket(Puerto);
2 El programa que proporciona el servicio invoca el método accept sobre el
socket de tipo ServerSocket. Este método bloquea el programa hasta que se
produce una conexión por parte de un cliente:
...SocketServidor.accept();
3 El método accept devuelve un socket de tipo Socket, con el que se realiza la
comunicación de datos del cliente al servidor: Socket
ComunicaConCliente = SocketServidor.accept();
4 El programa cliente crea una instancia de tipo Socket, a la que proporciona la
dirección del nodo destino y el puerto del servicio: Socket SocketCliente
= new Socket(Destino, Puerto);
5 Internamente, el socket del cliente trata de establecer comunicación con el
socket de tipo ServerSocket existente en el servidor; cuando la comunicación
se establece es cuando realmente (físicamente) se produce el paso 3 del
diagrama.
6 Con los pasos anteriores completados se puede empezar a comunicar datos
entre el cliente (o clientes) y el servidor.
44 © JESÚS BOBADILLA SANCHO

4.3 TRANSMISIÓN DE DATOS


TCP es un protocolo especialmente útil cuando se desea transmitir un flujo
de datos en lugar de pequeñas cantidades aisladas de información. Debido a esta
característica, los sockets de Java están diseñados para transmitir y recibir datos a
través de los Streams definidos en el paquete java.io.

La clase Socket contiene dos métodos importantes que se emplean en el


proceso de transmisión de flujos de datos:
InputStream getInputStream()
OutputStream getOutputStream()

Empleando el Stream de salida del socket del cliente y el Stream de entrada


del socket del servidor podemos establecer un flujo de datos continuo a través de la
conexión TCP establecida:

Socket cliente Socket servidor

GetOutputStream() GetInputStream()

OutputStream FlujoDeSalida = SocketCliente.getOutputStream();


InputStream FlujoDeEntrada = ComunicaConCliente.getInputStream();

Las clases OutputStream e InputStream son abstractas, por lo que no


podemos emplear directamente todos sus métodos. En general utilizaremos otras
clases más especializadas que nos permiten trabajar con flujos de datos:
DataOutputStream, DataInputStream, FileOutputStream, FileInputStream, etc.

DataInputStream Flujo = new DataInputStream(FlujoDeEntrada);


DataOutputStream Flujo = new DataOutputStream(FlujoDeSalida);

Una vez definidas instancias de este tipo de clases, nos basta con emplear
los métodos de escritura y lectura de datos que mejor se adapten a nuestras
necesidades:

Flujo.writeBytes("Línea de texto");
int BytesLeidos = Flujo.read(Mensaje);
© JESÚS BOBADILLA SANCHO 45

4.4 HOLA MUNDO


Para consolidar todas las ideas expuestas hasta el momento vamos a realizar
la aplicación más sencilla posible: el típico “Hola mundo” en versión TCP. En este
caso necesitamos:
• Un programa que se ejecute en el equipo cliente y envíe el texto “Hola
Mundo”: TCPClienteHolaMundo
• Un programa que se ejecute en el equipo servidor y reciba e imprima el
mensaje: TCPServidorHolaMundo

Los programas TCPClienteHolaMundo y TCPServidorHolaMundo han sido


implementados siguiendo los esquemas mostrados en los apartados anteriores, por lo
que resultarán muy fáciles de entender. Comencemos con TCPClienteHolaMundo:

1 import java.net.Socket;
2 import java.io.*;
3 import java.net.UnknownHostException;
4
5 public class TCPClienteHolaMundo {
6
7 public static void main(String[] args) {
8 OutputStream FlujoDeSalida;
9 DataOutputStream Flujo;
10 try {
11 Socket SocketCliente = new Socket("localhost",
12 8000);
13
14 FlujoDeSalida = SocketCliente.getOutputStream();
15 Flujo = new DataOutputStream(FlujoDeSalida);
16 Flujo.writeBytes("Hola Mundo");
17
18 SocketCliente.close();
19 } catch (UnknownHostException e) {
20 System.out.println("Referencia a host
21 no resuelta");
22 } catch (IOException e) {
23 System.out.println("Error en
24 las comunicaciones");
25 } catch (SecurityException e) {
26 System.out.println("Comunicacion no permitida
27 por razones de seguridad");
28 }
29 }
30
31 }
46 © JESÚS BOBADILLA SANCHO

En la línea 1 se indica que vamos a utilizar la clase Socket del paquete


java.net. Como ya sabemos, el cliente utiliza la clase Socket y el servidor utiliza,
además, la clase ServerSocket.

En la línea 11 se crea la instancia SocketCliente de la clase Socket, indicando


que el nodo destino es la máquina local (“localhost”) y el puerto es el 8000
(asignado arbitrariamente).

En la línea 14 se obtiene el objeto FlujoDeSalida, de tipo OutputStream


(línea 8). Utilizando este objeto creamos una instancia Flujo (línea 15) de tipo
DataOutputStream (línea 9).

Entre los métodos de la clase DataOutputStream se encuentra writeBytes,


que utilizamos para enviar el texto “Hola Mundo” (línea 16) a través de la red. Tras
enviar este único mensaje cerramos el socket haciendo uso del método close (línea
18) y de esta manera liberamos los recursos empleados.

Cuando utilizamos el método writeBytes debemos ser conscientes de que


estamos escribiendo un texto en un objeto de tipo DataOutputStream, que a su vez
se sustenta en un objeto de tipo OutputStream, que se encuentra asocia do a un socket
de tipo Socket; de manera que, finalmente, como esperamos, los datos escritos
acaban saliendo a la red a través del socket.

Las líneas 19 a 28 contienen los diferentes tratamientos de excepciones que


nos podemos encontrar al instanciar el socket y realizar el traspaso de información.

Para completar el ejemplo debemos escribir la clase


TCPServidorHolaMundo:

1 import java.net.ServerSocket;
2 import java.net.Socket;
3 import java.io.*;
4
5 public class TCPServidorHolaMundo {
6
7 public static void main(String[] args) {
8 byte[] Mensaje=new byte[80];
9 InputStream FlujoDeEntrada;
10 DataInputStream Flujo;
11 try {
12 ServerSocket SocketServidor = new
13 ServerSocket(8000);
14 Socket ComunicaConCliente = SocketServidor.accept();
15
16 System.out.println("Comunicacion establecida");
17 FlujoDeEntrada =
© JESÚS BOBADILLA SANCHO 47

18 ComunicaConCliente.getInputStream();
19 Flujo = new DataInputStream(FlujoDeEntrada);
20 int BytesLeidos = Flujo.read(Mensaje);
21 System.out.println(new String(Mensaje));
22
23 ComunicaConCliente.close();
24 SocketServidor.close();
25
26 } catch (IOException e) {
27 System.out.println("Error en las comunicaciones");
28 System.exit(0);
29 } catch (SecurityException e) {
30 System.out.println("Comunicacion no permitida por
31 razones de seguridad");
32 System.exit(0);
33 }
34
35 }
36
37 }

En las líneas 1 y 2 importamos las clase ServerSocket y Socket, que vamos a


utilizar. En la línea 3 nos aseguramos de que vamos a tener accesibles las distintas
clases del paquete java.io que usamos en el programa: InputStream,
DataInputStream e IOException.

En la línea 12 creamos la instancia SocketServidor, utilizando el puerto 8000


(que ha de ser el mismo que el definido en TCPClienteHolaMundo); a esta instancia
se le aplica el método accept (línea 14) para establecer la conexión y obtener el
objeto ComunicaConCliente, de tipo Socket.

En las líneas 17 y 18 se obtiene el Stream de entrada asociado al socket que


acabamos de conseguir. En la línea 19 utilizamos el objeto FlujoDeEntrada, de tipo
InputStream, para crear una instancia Flujo de tipo DataInputStream.

La lectura de los datos que nos llegan por la línea de comunicaciones se


hace, en nuestro ejemplo, empleando el método read de la clase DataInputStream
(línea 20). Mensaje es un vector de bytes (línea 8) de 80 posiciones (el tamaño del
vector ha sido elegido arbitrariamente).

Finalmente se imprime por consola (línea 21) el texto que nos llega
(esperamos “Hola Mundo”) y cerramos los sockets utilizados (líneas 23 y 24).

Tras ejecutar cada programa en una consola diferente, las siguientes figuras
muestran el correcto funcionamiento de nuestras clases. Nótese que debemos
ejecutar en primer lugar la clase servidor, o nos encontraremos con una excepción de
tipo IOException en el cliente, tal y como mostramos a continuación.
48 © JESÚS BOBADILLA SANCHO

4.5 APLICANDO LA PROGRAMACIÓN ORIENTADA A


OBJETOS
Partiendo del ejemplo “Hola Mundo” y cambiando muy pocas cosas,
podemos crear dos clases abstractas: TCPCliente y TCPServidor que nos servirán de
base para una gran cantidad de aplicaciones.

Las nuevas clases se encargarán de crear los sockets necesarios y gestionar


las excepciones que se puedan dar, dejando los detalles de envío y recepción de
datos a sendos métodos abstractos Comunicacion, que deberá implementar cada
aplicación concreta que deseemos realizar basándonos en las clases TCPCliente y
TCPServidor.

A continuación se muestra la clase TCPCliente, que consta de un constructor


(línea 8) que admite como entrada la dirección del host y el puerto destino de las
comunicaciones. La clase se encarga de crear el socket (línea 12), establecer el flujo
de salida (líneas 14 y 15) y tratar las excepciones (líneas 20 a 28).

El envío de datos de cada aplicación se debe implementar dentro del método


abstracto Comunicación (línea 32), que actúa sobre el flujo de salida establecido
(línea 17).

1 import java.net.Socket;
2 import java.io.*;
3 import java.net.UnknownHostException;
4
© JESÚS BOBADILLA SANCHO 49

5 public abstract class TCPCliente {


6 private DataOutputStream Flujo;
7
8 TCPCliente(String Host, int Puerto) {
9 OutputStream FlujoDeSalida;
10
11 try {
12 Socket SocketCliente = new Socket(Host, Puerto);
13
14 FlujoDeSalida = SocketCliente.getOutputStream();
15 Flujo = new DataOutputStream(FlujoDeSalida);
16
17 Comunicacion(Flujo);
18
19 SocketCliente.close();
20 } catch (UnknownHostException e) {
21 System.out.println("Referencia a host no
22 resuelta");
23 } catch (IOException e) {
24 System.out.println("Error en las comunicaciones");
25 } catch (SecurityException e) {
26 System.out.println("Comunicacion no permitida por
27 razones de seguridad");
28 }
29 }
30
31
32 public abstract void Comunicacion (DataOutputStream
33 Flujo);
34
35 }

La clase TCPServidor es similar a TCPServidorHolaMundo, pero aplicando


cambios similares a los explicados en TCPCliente .

1 import java.net.ServerSocket;
2 import java.net.Socket;
3 import java.io.*;
4
5 public abstract class TCPServidor {
6 private DataInputStream Flujo;
7
8 TCPServidor(int Puerto){
9 byte[] Mensaje=new byte[256];
10 InputStream FlujoDeEntrada;
11 try {
12 ServerSocket SocketServidor = new
13 ServerSocket(Puerto);
50 © JESÚS BOBADILLA SANCHO

14 Socket ComunicaConCliente = SocketServidor.accept();


15
16 System.out.println("Comunicacion establecida");
17 FlujoDeEntrada =
18 ComunicaConCliente.getInputStream();
19 Flujo = new DataInputStream(FlujoDeEntrada);
20
21 Comunicacion (Flujo);
22
23 ComunicaConCliente.close();
24 SocketServidor.close();
25 } catch (IOException e) {
26 System.out.println("Error en las comunicaciones");
27 System.exit(0);
28 } catch (SecurityException e) {
29 System.out.println("Comunicacion no permitida por
30 razones de seguridad");
31 System.exit(0);
32 }
33 }
34
35
36 public abstract void Comunicacion (DataInputStream
37 Flujo);
38
39 }

4.6 APLICACIÓN TELETIPO: TALK


Utilizando las clases desarrolladas hasta el momento vamos a crear una
aplicación que nos permita visualizar por consola, en un ordenador, las frases que
introducimos por teclado en otro (o el mismo) nodo. A esta aplicación se le
denomina talk ; es un servicio telemático síncrono bastante utilizado.

La clase TCPTeletipoCliente extiende a TCPCliente, por lo que heredamos


la capacidad de establecer la conexión TCP que hemos programado en esta última
clase.

Al heredar la clase abstracta TCPCliente estamos obligados a implementar


su método abstracto Comunicacion (línea 9); al menos si deseamos poder crear
instancias de TCPTeletipoCliente .
© JESÚS BOBADILLA SANCHO 51

En el método Comunicacion nos encargaremos de programar la lectura de


frases por teclado y el envío de estas frases por el flujo de salida Flujo, de tipo
DataOutputStream.

Para leer frases del teclado creamos un objeto Teclado de tipo InputStream,
asignándolo a System.in (entrada estándar del sistema), todo ello en la línea 12. En la
línea 16 utilizamos el método read para leer una secuencia de hasta 256 caracteres
(línea 10) introducidos por el teclado.

La siguiente línea de código (si no se levanta una excepción) es la 23, donde


se escribe (write) por el Stream de salida (Flujo) el array de NumBytesLeidos bytes
Valor.

Finalmente, las líneas 28 y 29 convierten la secuencia de bytes en un String


Mensaje, que utilizamos para codificar una condición de finalización del bucle de
escritura y envío de datos (escribir el texto “Fin”).

Cuando la ejecución del método Comunicación finaliza, se continúa con la


secuencia de instrucciones del constructor de la clase TCPCliente , cerrándose el
socket empleado en la comunicación.

1 import java.io.*;
2
3 public class TCPTeletipoCliente extends TCPCliente {
4
5 TCPTeletipoCliente(String Host, int Puerto) {
6 super(Host, Puerto);
7 }
8
9 public void Comunicacion (DataOutputStream Flujo) {
10 byte[] Valor = new byte[256];
11 int NumBytesLeidos = 0;
12 InputStream Teclado = System.in;
13 String Mensaje;
14 do {
15 try {
16 NumBytesLeidos = Teclado.read(Valor);
17 }
18 catch (IOException e){
19 System.out.println("Error en la entrada de datos
20 por consola");
21 }
22 try {
23 Flujo.write(Valor,0,NumBytesLeidos);
24 }catch (IOException e) {
25 System.out.println("Error en la escritura de
26 datos a la linea");
52 © JESÚS BOBADILLA SANCHO

27 }
28 Mensaje = new String(Valor);
29 Mensaje = Mensaje.substring(0,NumBytesLeidos-2);
30
31 } while (!Mensaje.equals("Fin"));
32 }
33
34 }

La clase TCPTeletipoServidor extiende la clase abstracta TCPServidor e


implementa su único método Comunicacion. En primer lugar se lee cada frase
proveniente del cliente (línea 15), utilizando el método read sobre el flujo de
entrada Flujo.

En las líneas 20 y 21 se convierte el array de bytes en un String; en la línea


22 se imprime el String por la consola y en la línea 24 se comprueba si llega la
condición de finalización del bucle.

1 import java.io.*;
2
3 public class TCPTeletipoServidor extends TCPServidor {
4
5 TCPTeletipoServidor(int Puerto) {
6 super(Puerto);
7 }
8
9 public void Comunicacion (DataInputStream Flujo) {
10 byte[] buffer = new byte[256];
11 int BytesLeidos=0;
12 String Mensaje;
13 do {
14 try {
15 BytesLeidos = Flujo.read(buffer);
16 } catch (IOException e) {
17 System.out.println("Error en la lectura de
18 datos por linea");
19 }
20 Mensaje = new String(buffer);
21 Mensaje = Mensaje.substring(0,BytesLeidos-2);
22 System.out.println(Mensaje);
23
24 } while (!Mensaje.equals("Fin"));
25 }
26
27 }
© JESÚS BOBADILLA SANCHO 53

Para poder ejecutar la aplicación “Teletipo (talk)” únicamente nos falta crear
las clases, cliente y servidor, que instancien a TCPTeletipoCliente y
TCPTeletipoServidor.

Los parámetros empleados en cliente: host destino y puerto destino, los


obtenemos de los argumentos con los que invocamos a
TCPTeletipoClientePrincipal. Lo mismo hacemos con el puerto local que requieren
las clases de tipo servidor.

1 public class TCPTeletipoClientePrincipal {


2
3 public static void main(String[] args) {
4 int Puerto = Integer.parseInt(args[1]);
5 String Host = args[0];
6 TCPTeletipoCliente InstanciaCliente = new
7 TCPTeletipoCliente(Host,Puerto);
8 }
9
10 }

1 public class TCPTeletipoServidorPrincipal {


2
3 public static void main(String[] args) {
4 int Puerto = Integer.parseInt(args[0]);
5 TCPTeletipoServidor InstanciaServidor = new
6 TCPTeletipoServidor(Puerto);
7 }
8 }

En los gráficos anteriores se observa como se comunica el cliente con el


servidor. En este caso estamos utilizando un mismo equipo para albergar a ambos
programas (cliente y servidor), por eso utilizamos el nombre “localhost”, que se
refiere a la dirección de la máquina local.
54 © JESÚS BOBADILLA SANCHO

4.7 UTILIZACIÓN DE HILOS


Cuando se programan servicios de comunicaciones normalmente surge la
necesidad de utilizar threads para que diversos programas se ejecuten en paralelo,
por ejemplo cuando existen distintos servidores y servicios en una misma máquina.

Imaginemos que deseamos establecer un servicio de comunicaciones en


cadena, de manera que una primera persona (el director) envía un mensaje a la
segunda persona (un jefe de proyecto), que a su vez envía sus peticiones a una
tercera persona (un analista) y así sucesivamente hasta llegar al programador. De
esta manera la petición del director se va traduciendo en las distintas peticiones
jerárquicas necesarias.

Con las clases que hemos implementado podemos resolver fácilmente el


problema: el director necesita una ventana de consola ejecutando la clase
TCPTeletipoClientePrincipal y el programador también necesita una sola ventana de
consola ejecutando la clase TCPTeletipoServidorPrincipal; todos los demás
implicados necesitan dos ventanas: una para recibir mensajes de su superior y otra
para enviar a su subordinado.

Para evitar el uso de varias consolas simultáneas, podemos utilizar el


mecanismo de programación concurrente que nos ofrece Java: los hilos. Veamos
como podemos variar nuestras clases para obtener una ventana que atienda
concurrentemente la entrada (servidor) y salida (cliente) de mensajes.

La clase cliente se programa en TCPTeletipoClienteConHilos, que


implementa a Thread, por lo que es un hilo (línea 6); su constructor admite, además
de las referencias Host y Puerto , un campo de texto AreaEntrada donde se escribe el
mensaje de salida (líneas 16 y 17).

El método run (línea 24) de la clase Thread implementa el establecimiento


de comunicaciones (línea 26) y la determinación del flujo de salida (líneas 27 y 28).
Este método se ejecuta cuando se arranca (start) cada hilo instanciado de esta clase.

En la línea 41 se define la clase que implementa el único método


(actionPerformed) del interfaz ActionListener. La línea 45 se encarga de recoger el
mensaje introducido en el campo de texto (getText), convertirlo en array de bytes
(getBytes) y escribirlo (write) en el Stream de salida (Flujo).
© JESÚS BOBADILLA SANCHO 55

El final de la aplicación (línea 50) se alcanza cuando se envía el mensaje


“Fin” (línea 49).

1 import java.io.*;
2 import java.awt.TextField;
3 import java.awt.event.*;
4 import java.net.*;
5
6 public class TCPTeletipoClienteConHilos extends Thread{
7
8 TextField AreaEntrada;
9 OutputStream FlujoDeSalida;
10 DataOutputStream Flujo;
11 Socket SocketCliente;
12 String Host;
13 int Puerto;
14
15
16 TCPTeletipoClienteConHilos(String Host, int Puerto,
17 TextField AreaEntrada) {
18 this.AreaEntrada = AreaEntrada;
19 this.Host = Host;
20 this.Puerto = Puerto;
21 }
22
23
24 public void run() {
25 try {
26 SocketCliente = new Socket(Host, Puerto);
27 FlujoDeSalida = SocketCliente.getOutputStream();
28 Flujo = new DataOutputStream(FlujoDeSalida);
29
30 } catch (UnknownHostException e) {
31 System.out.println("Referencia a host no
32 resuelta");
33 } catch (IOException e) {
34 System.out.println("Error en las
35 comunicaciones");
36 }
37 AreaEntrada.addActionListener(new TextoModificado());
38 }
39
40
41 private class TextoModificado implements
42 ActionListener {
43 public void actionPerformed(ActionEvent e) {
44 try {
45 Flujo.write(AreaEntrada.getText().getBytes());
46 } catch (IOException IOe) {
56 © JESÚS BOBADILLA SANCHO

47 System.out.println("Error al enviar los datos");


48 }
49 if (AreaEntrada.getText().equals("Fin"))
50 System.exit(0);
51 AreaEntrada.setText("");
52 }
53 }
54
55 }

La clase TCPTeletipoServidorConHilos se apoya en TCPServidorConHilos,


por lo que el establecimiento de comunicaciones lo tenemos resuelto y nos basta con
implementar el método abstracto Comunicacion.

El método Comunicacion recoge los mensajes que le llegan por la red (línea
20), los convierte en String (línea 25) y los visualiza en un campo de texto (línea
26). El objeto de tipo TextField se obtiene a través del constructor de la clase (línea
9). El método Comunicacion finaliza cuando llega el mensaje “Fin” (línea 28).

1 import java.io.*;
2 import java.awt.TextField;
3
4 public class TCPTeletipoServidorConHilos extends
5 TCPServidorConHilos {
6 TextField AreaSalida;
7
8 TCPTeletipoServidorConHilos(int Puerto,
9 TextField AreaSalida) {
10 super(Puerto);
11 this.AreaSalida = AreaSalida;
12 }
13
14 public void Comunicacion (DataInputStream Flujo) {
15 byte[] buffer = new byte[256];
16 int BytesLeidos=0;
17 String Mensaje;
18 do {
19 try {
20 BytesLeidos = Flujo.read(buffer);
21 } catch (IOException e) {
22 System.out.println("Error en la lectura de
23 datos por linea");
24 }
25 Mensaje = new String(buffer,0,BytesLeidos);
26 AreaSalida.setText(Mensaje);
27
28 } while (!Mensaje.equals("Fin"));
© JESÚS BOBADILLA SANCHO 57

29 }
30
31 }

La clase TCPServidorConHilos es idéntica a TCPServidor, salvo en la


definición de la clase, donde ahora se extiende Tread:

public abstract class TCPServidorConHilos extends Thread{

Finalmente necesitamos una clase, TCPTeletipoConHilos, que defina el


interfaz gráfico de usuario, instancie los hilos cliente y servidor y los arranque
(start).

El GUI de la aplicación se define entre las líneas 6 y 24. En las líneas 12 y


13 se instancian los campos de texto Entrada y Salida, que serán utilizados para
introducir mensajes (que recoge el hilo cliente) y visualizar los mensajes
provenientes de la red (que recoge el hilo servidor).

En las líneas 30 y 31 se intancia el hilo servidor, mientras que en las líneas


32 a 34 se hace lo propio con el hilo cliente. Una vez creados los hilos se arrancan
(método start) en las líneas 35 y 36.

En las líneas 27 a 29 se recogen los argumentos de llamada a la clase,


siguiendo el orden: dirección destino, puerto destino, puerto local.

1 import java.awt.*;
2
3 public class TCPTeletipoConHilos {
4
5 public static void main(String[] args) {
6 Frame MiMarco = new Frame();
7 Panel Visor = new Panel(new GridLayout(2,1));
8 Panel AreaEnviar = new Panel(new
9 FlowLayout(FlowLayout.LEFT));
10 Panel AreaRecibir = new Panel(new
11 FlowLayout(FlowLayout.LEFT));
12 TextField Entrada = new TextField(30);
13 TextField Salida = new TextField(30);
14
15 AreaEnviar.add(new Label("Enviado :"));
16 AreaEnviar.add(Entrada);
17 AreaRecibir.add(new Label("Recibido:"));
18 AreaRecibir.add(Salida);
19 Visor.add(AreaEnviar);
20 Visor.add(AreaRecibir);
58 © JESÚS BOBADILLA SANCHO

21 MiMarco.add(Visor);
22 MiMarco.setSize(400,90);
23 MiMarco.setTitle("Talk con TCP");
24 MiMarco.setVisible(true);
25
26 if (args.length==3) {
27 String HostRemoto = args[0];
28 int PuertoLocal = Integer.parseInt(args[2]);
29 int PuertoRemoto = Integer.parseInt(args[1]);
30 TCPTeletipoServidorConHilos InstanciaServidor = new
31 TCPTeletipoServidorConHilos(PuertoLocal,Salida);
32 TCPTeletipoClienteConHilos InstanciaCliente = new
33 TCPTeletipoClienteConHilos(HostRemoto,
34 PuertoRemoto,Entrada);
35 InstanciaServidor.start();
36 InstanciaCliente.start();
37 }
38 else {
39 System.out.println("Es necesario pasar tres
40 argumentos (host remoto, puerto remoto, puerto local)");
41 System.exit(0);
42 }
43
44 }
45
46 }

Ejecutando en tres máquinas diferentes las líneas de comando:


Java TCPTeletipoConHilos Host2, 8002, 8000
Java TCPTeletipoConHilos Host3, 8004, 8002
Java TCPTeletipoConHilos Host4, 8006, 8004

... Obtenemos
© JESÚS BOBADILLA SANCHO 59

4.8 TRASPASO DE FICHEROS


El protocolo TCP resulta especialmente útil para transmitir flujos de datos
que requieren una comunicación fiable; un ejemplo de esta situación es el envío de
un programa, que podría tener un tamaño considerable y que no admite errores en la
recepción.

En el siguiente ejemplo se proporcionan dos clases (TCPFicheroCliente y


TCPFicheroServidor) que se encargan de traspasar un fichero de un ordenador a
otro.

TCPFicheroCliente extiende la clase TCPCliente, por lo que únicamente


necesitamos implementar el método abstracto Comunicacion (línea 10). Para
acceder al fichero secuencial utilizamos la clase FileInputStream (líneas 16 y 17). Si
no existen problemas al leer el fichero (líneas 18 a 21) nos metemos en un bucle
(línea 24) donde se leen (método read en la línea 25) hasta 256 bytes (líneas 11 y
12).

Tras la lectura de datos en el fichero, se realiza la escritura (write) en el


Stream de salida a la red (línea 26); el bucle se termina cuando en alguna lectura del
fichero no hemos rellenado por completo el buffer de entrada (línea 27). La última
acción es cerrar el fichero de entrada (línea 28).

1 import java.io.*;
2
3 public class TCPFicheroCliente extends TCPCliente {
4 private FileInputStream FicheroOrigen;
5
6 TCPFicheroCliente(String Host, int Puerto) {
7 super(Host, Puerto);
8 }
9
10 public void Comunicacion (DataOutputStream Flujo) {
11 final int TAMANIO_BUFFER = 256;
12 byte buffer[] = new byte[TAMANIO_BUFFER];
13 int NumBytesLeidos = 0;
14
15 try {
16 FicheroOrigen = new
17 FileInputStream("TCPCliente.java");
18 } catch (FileNotFoundException e) {
19 System.out.println("Fichero no encontrado");
20 System.exit(0);
21 }
22
23 try {
24 do {
60 © JESÚS BOBADILLA SANCHO

25 NumBytesLeidos = FicheroOrigen.read(buffer);
26 Flujo.write(buffer,0,NumBytesLeidos);
27 } while (NumBytesLeidos == TAMANIO_BUFFER);
28 FicheroOrigen.close();
29 } catch (IOException e){
30 System.out.println(e.getMessage());
31 }
32 }
33 }

La clase TCPFicheroServidor se encarga de recoger los datos que llegan por


la red (línea 20) y de escribirlos en el fichero de salida (línea 21). Los detalles de
implementación son muy similares a los que existen en la clase TCPClienteServidor.

1 import java.io.*;
2
3 public class TCPFicheroServidor extends TCPServidor {
4
5 TCPFicheroServidor(int Puerto) {
6 super(Puerto);
7 }
8
9 public void Comunicacion (DataInputStream Flujo) {
10 final int TAMANIO_BUFFER = 256;
11 byte buffer[] = new byte[TAMANIO_BUFFER];
12 int NumBytes=0;
13
14 try {
15 FileOutputStream FicheroDestino = new
16 FileOutputStream("Salida.txt");
17
18 try {
19 do {
20 NumBytes = Flujo.read(buffer);
21 FicheroDestino.write(buffer,0,NumBytes);
22 } while (NumBytes==TAMANIO_BUFFER);
23 FicheroDestino.close();
24 } catch (IOException e){
25 System.out.println("Error de
26 entrada/salida");
27 }
28
29 } catch (FileNotFoundException e) {
30 System.out.println("Fichero no encontrado");
31 }
32
33 }
34 }
© JESÚS BOBADILLA SANCHO 61

Para poder ejecutar estas clases únicamente nos falta instanciarlas desde
sendos métodos main. En la línea 5 de la clase TCPFicheroClientePrincipal
utilizamos directamente la dirección IP del nodo destino de la comunicación.

1 public class TCPFicheroClientePrincipal {


2
3 public static void main(String[] args) {
4 TCPFicheroCliente InstanciaCliente = new
5 TCPFicheroCliente("138.100.155.17",20000);
6 }
7
8 }

1 public class TCPFicheroServidorPrincipal {


2
3 public static void main(String[] args) {
4 TCPFicheroServidor InstanciaServidor = new
5 TCPFicheroServidor(20000);
6 }
7
8 }

En los siguientes gráficos aparece el resultado de ejecutar la aplicación y


comprobar que el fichero ha sido traspasado correctamente:
62 © JESÚS BOBADILLA SANCHO

4.9 COMUNICACIÓN BIDIRECCIONAL


Cuando se establece una conexión TCP, existe la posibilidad de realizar
comunicaciones bidireccionales a través de los sockets existentes en el cliente y el
servidor. El modelo cliente-servidor se adapta de forma natural al enfoque de
comunicación semidúplex: se realiza la petición de servicio en un sentido y,
posteriormente, se envía la respuesta en sentido contrario; en este apartado se
presenta un ejemplo muy conciso en el que un programa cliente y un programa
servidor se envían información en los dos sentidos (de cliente a servidor y de
servidor a cliente).

El programa cliente es TCPClienteDuplexHolaMundo. En la línea 13 se


define el socket a través del cual se establecen las comunicaciones en la parte del
cliente. En las líneas 15 a 21 se definen los Streams que canalizarán los datos de
entrada y salida.

La línea 23 codifica un bucle dentro del cual se realiza, de manera


alternativa, la comunicación de salida (línea 25) y la de entrada (líneas 28 a 34). La
condición de finalizació n se ha establecido de manera que el bucle itere 20 veces.
Finalmente, en la línea 37 se cierra el socket.

1 import java.net.*;
2 import java.io.*;
3
4 public class TCPClienteDuplexHolaMundo {
5
6 public static void main(String[] args) {
7 DataInputStream FlujoEntrada;
8 DataOutputStream FlujoSalida;
9 byte[] Mensaje=new byte[80];
10 int BytesLeidos=0, Frases=0;
11
12 try {
13 Socket SocketCliente = new Socket("localhost", 8000);
14
15 OutputStream FlujoDeSalida =
16 SocketCliente.getOutputStream();
17 InputStream FlujoDeEntrada =
18 SocketCliente.getInputStream();
19
20 FlujoEntrada = new DataInputStream(FlujoDeEntrada);
21 FlujoSalida = new DataOutputStream(FlujoDeSalida);
22
23 do {
24 try {
25 FlujoSalida.writeBytes("Hola terricola\n");
26 Frases++;
© JESÚS BOBADILLA SANCHO 63

27
28 BytesLeidos = FlujoEntrada.read(Mensaje);
29 } catch (IOException e) {
30 System.out.println("Error en la lectura de
31 datos");
32 System.exit(0);
33 }
34 System.out.print(new String(Mensaje,0,BytesLeidos));
35 } while (Frases!=20);
36
37 SocketCliente.close();
38
39 } catch (UnknownHostException e) {
40 System.out.println("Referencia a host no resuelta");
41 } catch (IOException e) {
42 System.out.println("Error en las comunicaciones");
43 }
44 }
45
46 }

El programa servidor, TCPServidorDuplexHolaMundo, realiza acciones


muy similares a TCPClienteDuplexHolaMundo. En este caso se crea una instancia
de la clase ServerSocket (línea 14) y, con ella, se crea el socket (línea 15) que se
comunicará con el cliente.

Las comunicaciones se comienzan con la lectura de datos (líneas 28 a 32) y


se terminan con la escritura (línea 34).

1 import java.net.ServerSocket;
2 import java.net.Socket;
3 import java.io.*;
4
5 public class TCPServidorDuplexHolaMundo {
6
7 public static void main(String[] args) {
8 DataInputStream FlujoEntrada;
9 DataOutputStream FlujoSalida;
10 byte[] Mensaje=new byte[80];
11 int BytesLeidos=0, Frases=0;
12
13 try {
14 ServerSocket SocketServidor = new ServerSocket(8000);
15 Socket ComunicaConCliente = SocketServidor.accept();
16 System.out.println("Comunicacion establecida");
17
18 OutputStream FlujoDeSalida =
64 © JESÚS BOBADILLA SANCHO

19 ComunicaConCliente .getOutputStream();
20 InputStream FlujoDeEntrada =
21 ComunicaConCliente .getInputStream();
22
23 FlujoEntrada = new DataInputStream(FlujoDeEntrada);
24 FlujoSalida = new DataOutputStream(FlujoDeSalida);
25
26 do {
27 try {
28 BytesLeidos = FlujoEntrada.read(Mensaje);
29 } catch (IOException e) {
30 System.out.println("Error en la lectura de datos");
31 System.exit(0);
32 }
33 System.out.print(new String(Mensaje,0,BytesLeidos));
34 FlujoSalida.writeBytes("Hola humano\n");
35 Frases++;
36 } while (Frases!=20);
37
38
39 ComunicaConCliente.close();
40 SocketServidor.close();
41
42 } catch (IOException e) {
43 System.out.println("Error en las comunicaciones");
44 System.exit(0);
45 }
46
47 }
48
49
50 }
© JESÚS BOBADILLA SANCHO 65

4.10 CONFIGURACIÓN DE LAS COMUNICACIONES


Los ejemplos que hemos desarrollado utilizan los valores por defecto en el
comportamiento de los sockets, y por tanto en el desarrollo de la s comunicaciones.
Es posible variar por programa ciertos comportamientos de las comunicaciones, así
como obtener y establecer una gran cantidad de información relativa a las clases
Socket y ServerSocket.

ServerSocket
Métodos principales Acción
Socket accept() Espera a que se realice una conexión y devuelve un socket
para comunicarse con el cliente
void bind(SocketAddress a) Asigna la dirección establecida al socket creado con
accept, si no se utiliza este método se asigna
automáticamente una dirección temporal
void close() Cierra el socket
InetAddress getInetAddress() Devuelve la dirección a la que está conectada el socket
int getLocalPort() Devuelve el número de puerto asociado al socket
int getSoTimeout() Devuelve el valor en milisegundos que el socket espera al
establecimiento de comunicación tras la ejecución de
accept
void setSoTimeout(int ms) Asigna el número de milisegundos que el socket espera al
establecimiento de comunicación tras la ejecución de
accept

Socket
Métodos principales Acción
void bind(SocketAddress a) Asigna la dirección establecida al socket creado con
accept, si no se utiliza este método se asigna
automáticamente una dirección temporal
void close() Cierra el socket
void connect(SocketAddress a) Conecta el socket a la dirección de servidor establecida
void connect(SocketAddress a, Conecta el socket a la dirección de servidor establecida,
int ms) esperando un máximo de ms milisegundos
InetAddress getInetAddress() Devuelve la dirección a la que está conectada el socket
InputStream getInputStream() Devuelve el stream de entrada asociado al socket
int getLocalPort() Devuelve el número de puerto asociado al socket
OutputStream Devuelve el stream de salida asociado al socket
getOutputStream()
int getPort() Devuelve el valor del Puerto remoto al que está conectado
int getSoLinger() Devuelve el número de milisegundos que se espera a los
datos después de cerrar (close) el socket
int getSoTimeout() Devuelve el valor en milisegundos que el socket espera al
establecimiento de comunicación tras la ejecución de
accept
66 © JESÚS BOBADILLA SANCHO

boolean isBound() Indica si el socket está vinculado


boolean isClosed() Indica si el socket está cerrado
boolean isConnected() Indica si el socket está conectado
void setSoLinger(boolean Se establece si se esperan o no los datos después de cerrar
Activo, int ms) el socket (y cuanto tiempo se esperan)
void setSoTimeout(int ms) Asigna el número de milisegundos que el socket espera al
establecimiento de comunicación tras la ejecución de
accept
LECCIÓN 5

COMUNICACIONES BASADAS EN EL
PROTOCOLO UDP

5.1 INTRODUCCIÓN
En este apartado se explican las características básicas del protocolo UDP
(User Datagram Protocol), que están relacionadas con buena parte de los conceptos
de IP y TCP. Estos conceptos se explican en el primer apartado dedicado a TCP
(Transmission Control Protocol)

Al igual que TCP, el protocolo UDP funciona en el nivel de transporte,


basándose en el protocolo de red IP (Internet Protocol). IP proporciona
comunicaciones no fiables y no basadas en conexión, muy dependientes de
saturaciones en la red, caídas de nodos, etc. UDP no es un protocolo orientado a
conexión ni a comunicaciones basadas en flujos de datos, por lo que Java no asocia
los Streams a los datagramas.

UDP proporciona una serie de posibilidades que IP no contempla; la más


obvia es la utilización de puertos, que permite diferenciar servicios en una misma
dirección de red (IP). Puesto que en la cabecera de un paquete IP se incluye el
protocolo utilizado, los servicios (programas) asociados a cada protocolo se
diferencian entre sí, y por lo tanto pueden utilizar el mismo rango de puertos, es
decir, el puerto 8200 de un servicio TCP no se confundirá, por ejemplo, con el
puerto 8200 de un servicio UDP.

En la cabecera de cada paquete UDP se incluye un campo de redundancia,


que sirve para comprobar, en el nodo destino, la integridad de los datos que se
reciben. Esta es una característica que IP no contempla.

Puesto que UDP no es un protocolo orientado a conexión, cuando a un nodo


le llega un datagrama, necesita la dirección y puerto del nodo origen para saber
dónde debe contestar. La dirección del nodo origen la puede obtener de la cabecera
IP, mientras que el número de puerto lo obtiene de la cabecera UDP.
68 © JESÚS BOBADILLA SANCHO

Cada mensaje UDP puede albergar hasta 508 bytes, por lo que este
protocolo resulta especialmente adecuado para transmitir información que no ocupe
mucho tamaño; por otra parte, al no ser un protocolo fiable, las aplicaciones deben
tolerar la pérdida de datos, o bien hacerse cargo de las mismas.

En Java, las comunicaciones UDP se realizan utilizando la abstracción de


socket. Los sockets nos permiten establecer y programar comunicaciones sin tener
que conocer los niveles inferiores sobre los que se asientan.

En los siguientes apartados se explicará el mecanismo general con el que se


utilizan los sockets UDP, la diferencia entre las clases Socket y DatagramSocket, un
primer ejemplo de comunicaciones “Hola mundo”, una aplicación “talk”, etc.

5.2 ESTABLECIMIENTO DE COMUNICACIONES


Java proporciona dos clases de especial importancia en la implementación
de aplicaciones que hacen uso del protocolo UDP: una para realizar las
comunicaciones (DatagramSocket) y otra para albergar los datos y dirección de
destino (DatagramPacket), ambas en el paquete java.net. Antes de entrar en los
detalles de programación, vamos a mostrar gráficamente el esquema básico de
establecimiento de comunicaciones UDP.

Programa cliente Programa servidor

1. new DatagramSocket
5. new DatagramPacket (...) 2. new DatagramPacket
(...) (...)
4. new DatagramSocket
(...)
DatagramSocket DatagramSocket
6. send 3. receive
Puerto destino

Puerto destino

Nodo destino

1 El programa que proporciona el servicio (programa servidor) crea una


instancia de la clase DatagramSocket, indicando el puerto asociado al
servicio: DatagramSocket MiSocket=new DatagramSocket(4000);
 JESÚS BOBADILLA SANCHO 69

2 El programa servidor crea una instancia de la clase DatagramPacket, donde se


guardarán los datos recibidos: DatagramPacket Paquete = new
DatagramPacket(buffer, buffer.length);
3 El programa servidor invoca el método receive sobre el socket de tipo
DatagramSocket. Este método, por defecto, bloquea el programa hasta que
llegan los datos: MiSocket.receive(Paquete);
4 El programa cliente crea una instancia de tipo DatagramSocket:
DatagramSocket MiSocket = new DatagramSocket();
5 El programa cliente crea una instancia de tipo DatagramPacket,
proporcionándole los datos, además de la dirección y puerto de destino.
DatagramPacket Paquete = new DatagramPacket(buffer,
Mensaje.length(),InetAddress.getByName("localhost"),4000)
6 El programa que utiliza el servicio (programa cliente) invoca el método send
sobre el socket de tipo DatagramSocket: MiSocket.send(Paquete);

5.3 HOLA MUNDO


Como es habitual, vamos a implementar el programa más sencillo posible
que muestre el funcionamiento básico de las comunicaciones UDP: crearemos una
clase UDPEnvia y otra UDPRecibe que se encarguen de mandar y recibir,
respectivamente, la frase “Hola mundo”.

La clase UDPEnvia utiliza el paquete java.net (línea 1) para poder crear


instancias de las clases DatagramSocket y DatagramPacket. En la línea 7 se crea la
instancia MiSocket de la clase DatagramSocket.

En las líneas 8 a 10 se prepara el array de bytes que contiene el mensaje a


enviar: “Hola Mundo”. En las líneas 11 a 13 se crea la instancia Paquete de la clase
DatagramPacket; el constructor utilizado admite como parámetros:
• Un array de bytes donde se encuentra la información a transmitir.
• Un entero, indicando el número de bytes del array que contienen la
información que se desea transmitir.
• Un objeto de tipo InetAddress, que representa la dirección del nodo donde se
desea transmitir. Podemos convertir un String (como “localhost”) en un
objeto de tipo InetAddress utilizando el método estático getByName.
• Un entero que determina el puerto destino donde se envía la información.
70 © JESÚS BOBADILLA SANCHO

El datagrama Paquete lo podemos comparar con un sobre que contiene la


dirección de destino y la carta (los datos). El socket MiSocket también lo podemos
comparar con un objeto muy común en nuestra vida: un buzón de correos.

Una vez definidos los datos a enviar (buffer), el sobre con la dirección de
destino de la carta (Paquete) y el buzón de correos (MiSocket), estamos listos para
depositar el sobre en el buzón de correos: método send utilizado en la línea 14.

En la línea 15 se cierra MiSocket, con el método close, liberando de esta


manera los recursos utilizados. Las líneas 16 y 17 recogen cualquier tipo de
excepción que se pueda levantar en la clase.

1 import java.net.*;
2
3 public class UDPEnvia {
4
5 public static void main(String args[]) {
6 try {
7 DatagramSocket MiSocket = new DatagramSocket();
8 byte[] buffer = new byte[15];
9 String Mensaje = "Hola Mundo";
10 buffer=Mensaje.getBytes();
11 DatagramPacket Paquete = new DatagramPacket(buffer,
12 Mensaje.length(),
13 InetAddress.getByName("localhost"),14000);
14 MiSocket.send(Paquete);
15 MiSocket.close();
16 } catch (Exception exc){
17 System.out.println ("Error");
18 } //try
19 }
20
21 } // UDPEnvia

La clase UDPRecibe, al igual que UDPEnvía , también define un


DatagramSocket, un array de bytes donde almacenar la información que llega y un
DatagramPacket, donde recoger los datos recibidos.

La instancia de la clase DatagramSocket se define en la línea 7; como se


puede observar, en este caso, se le asocia el puerto de comunicaciones en la propia
instanciación.

La instancia del objeto DatagramPacket se define en la línea 9; basta con


asociarle el array de bytes donde se almacenará la información que nos llega, así
como una indicación de su longitud máxima en bytes.
 JESÚS BOBADILLA SANCHO 71

Una vez definidos los objetos DatagramSocket y DatagramPacket, estamos


en condiciones de recibir (método receive en la línea 11) la información.
Posteriormente, en la línea 12, se imprime por consola el mensaje que nos llega;
para ello recuperamos los datos (método getData ) que se encuentran en el
DatagramPacket; esta acción es similar a “abrir el sobre y sacar la carta”.

1 import java.net.*;
2
3 public class UDPRecibe {
4
5 public static void main(String[] args) {
6 try {
7 DatagramSocket MiSocket = new DatagramSocket(14000);
8 byte[] buffer = new byte[15];
9 DatagramPacket Paquete = new DatagramPacket(buffer,
10 buffer.length);
11 MiSocket.receive(Paquete);
12 System.out.println(new String(Paquete.getData()));
13 MiSocket.close();
14 } catch (Exception e){
15 System.out.println ("Error");
16 }
17 } //main
18
19 } // UDPRecibe

Ejecutando los programas UDPRecibe y UDPEnvia (en ese orden) en


consolas diferentes del mismo ordenador, o en máquinas diferentes (variando la
dirección destino) obtenemos el resultado esperado:
72 © JESÚS BOBADILLA SANCHO

5.4 MÉTODOS DE UTILIDAD


Utilizando programación orientada a objetos resulta adecuado crear clases
que nos proporcionen métodos de utilidad general. En nuestro caso, vamos a crear
dos métodos que nos encapsulen una comunicación UDP: establecimiento, envío o
recepción de Strings y cierre de la comunicación.

Nuestros métodos van a estar basados, casi por completo, en las clases
desarrolladas en el apartado anterior (“Hola Mundo”), por lo que resultará muy fácil
su seguimiento. Gráficamente, los métodos Envia y Recibe adoptan el siguiente
esquema:

Mensaje
Mensaje
Tamaño Mensaje
Puerto
Host Destino
Tamaño Mensaje
Puerto Destino

Envia Recibe

El método Envia lo vamos a situar en la clase TEnviaUDP (línea 3). Sus


parámetros de entrada (líneas 5 y 6) se corresponden con los que hemos señalado en
el gráfico anterior. Las instrucciones del método Envia (líneas 8 a 19) se obtienen de
forma directa de la clase UDPEnvia , explicada en el apartado “Hola Mundo”.

1 import java.net.*;
2
3 public class TEnviaUDP {
4
5 public void Envia(String Mensaje, int TamanioMensaje,
6 String HostDestino,int Puerto) {
7 try {
8 DatagramSocket MiSocket = new DatagramSocket();
9 byte[] buffer = new byte[TamanioMensaje];
10 DatagramPacket Paquete;
11 buffer=Mensaje.getBytes();
12 Paquete = new DatagramPacket(buffer, Mensaje.length(),
13 InetAddress.getByName(HostDestino),Puerto);
14 MiSocket.send(Paquete);
15 MiSocket.close();
16 } catch (Exception exc){
17 System.out.println ("Error");
18 }
 JESÚS BOBADILLA SANCHO 73

19 } // Envia
20
21 } // TEnviaUDP

El método Recibe se incluye en la clase TRecibeUDP (línea 3). Se le pasa


como argumentos (líneas 9 y 10) el valor del puerto de “escucha” y el tamaño
máximo del mensaje. El String que se recibe por la línea se devuelve (línea 20) al
programa llamante; obsérvese el uso de los métodos getData y getLength (líneas 20
y 21), pertenecientes a la clase DatagramPacket.

1 import java.net.*;
2
3 public class TRecibeUDP {
4
5 DatagramSocket MiSocket;
6 DatagramPacket Paquete;
7 byte[] buffer;
8
9 public String Recibe(int Puerto,
10 int TamanioMaximoMensaje) {
11 try {
12 MiSocket = new DatagramSocket(Puerto);
13 buffer = new byte[TamanioMaximoMensaje];
14 Paquete = new DatagramPacket(buffer, buffer.length);
15 MiSocket.receive(Paquete);
16 MiSocket.close();
17 } catch (Exception e){
18 System.out.println ("Error");
19 } //try
20 return new String(Paquete.getData()).substring
21 (0,Paquete.getLength());
22 } // Recibe
23 } // TRecibeUDP

Utilizando los métodos Envia y Recibe resulta muy sencillo programar


cierto tipo de aplicaciones y servicios: aquellos en los que el tránsito de datagramas
es lo suficientemente esporádico como para definir y liberar un DatagramPacket y
un DatagramSocket por mensaje. A continuación vamos a implementar un servicio
que puede encajar con este esquema: el servicio talk (conversación escrita).
74 © JESÚS BOBADILLA SANCHO

5.5 SERVICIO TALK (CONVERSACIÓN ESCRITA)


Vamos a implementar un servicio muy útil de comunicaciones telemáticas:
la conversación escrita. Este servicio nos permite dialogar por escrito, con otra
persona o personas, de manera síncrona, a través de Internet.

El interfaz de usuario que se implementa en este ejemplo (clase Talk ) es el


siguiente (una ventana por usuario):

A diferencia de lo que hemos hecho en ejemplos anteriores, vamos a


implementar todo el servicio talk utilizando un solo fichero, además de las clases de
utilidad TEnviaUDP y TRecibeUDP.

La clase Talk (línea 11) define el interfaz gráfico de usuario dentro de su


único constructor (línea 24). Al instanciar esta clase hay que proporcionar como
argumentos el puerto origen de la comunicación y el puerto destino; la dirección del
nodo destino se obtiene a través de una caja de texto (línea 38). Obsérvese como el
valor inicial de la dirección destino es la secuencia IP “127.0.0.1”, que se
corresponde con el bucle local y representa el valor numérico de la variable de
entorno “localhost”.

En las líneas 27 y 28 se crean instancias de las clases TEnviaUDP y


TRecibeUDP, que utilizaremos para invocar a sus métodos Envia y Recibe.

En las líneas 53 a 57 se crean y arrancan dos hilos de ejecución, uno para


enviar mensajes y otro para recibir los que nos llegan; de esta manera, cada instancia
de la clase Talk crea una ventana de las mostradas en el gráfico anterior y se
encarga, en paralelo, de enviar los mensajes que escribimos en un área de texto,
mostrando, en el otro área de texto, los mensajes que nos llegan.
 JESÚS BOBADILLA SANCHO 75

El método main, que se encuentra en la línea 62, se encarga de crear una


instancia de la clase Talk , pasando como argumentos del constructor los puertos que
recoge de la línea de comandos (a través del parámetro args).

Para completar la aplicación, únicamente nos queda implementar los hilos


(threads) TalkRecibir y TalkEnviar, instanciados en las líneas 53 y 54. Estas clases
van a resultar muy sencillas, puesto que se apoyan en el uso de TRecibeUDP y
TEnviaUDP (líneas 21 y 22).

La clase TalkRecibir (línea 76) es un hilo (extiende Thread), y su único


método, run, invoca al método Recibe (línea 80) usando la instancia
InstanciaRecibeUDP (línea 21). El mensaje recibido (líneas 18 y 80) se utiliza para
establecer el contenido del área de texto AreaRecibir (líneas 14 y 82). Este proceso
se repite hasta que enviamos un mensaje sin contenido (línea 83), que es la
condición de finalización que hemos programado.

El método run (línea 94) de la clase TalkEnviar (línea 92) establece una
clase de tratamiento de eventos de acción RespuestaAEnviar (línea 95) asociada al
botón de envío BotonEnviar.

La clase RespuestaAEnviar (línea 98) implementa el único método


actionPerformed (línea 100) de la clase ActionListener (línea 99). En primer lugar se
obtiene el mensaje a enviar, tomándolo del área de texto AreaEnviar (línea 101);
posteriormente se hace uso del método Envia, invocándolo a través de la instancia
InstanciaEnviaUDP (líneas 28 y 102). Después del envío se limpia el área de salida
(línea 104) y se comprueba si hay que terminar el programa (líneas 105 y 106).

Ejecutando dos instancias de la aplicación en diferentes JVM y “cruzando”


los puertos conseguiremos tener el servicio listo para su uso.
Ejemplo: Java Talk 14000 14002 y Java Talk 14002 14000

1 import java.awt.*;
2 import java.awt.event.*;
3
4 // ********************************************************
5 // * Servicio Talk *
6 // * nota: los puertos origen y destino se deben cruzar
7 // * en las dos instancias que se creen del *
8 // * talk, usar: java Talk PuertoOrigen PuertoDestino *
9 // ********************************************************
10
11 public class Talk {
12 static final int TamanioMaximoMensaje = 90;
13
14 TextArea AreaRecibir;
76 © JESÚS BOBADILLA SANCHO

15 TextArea AreaEnviar;
16 TextField HostDestino;
17 Button BotonEnviar;
18 String MensajeRecibido;
19 int PuertoOrigen, PuertoDestino;
20
21 TRecibeUDP InstanciaRecibeUDP;
22 TEnviaUDP InstanciaEnviaUDP;
23
24 public Talk(int PuertoOrigen, int PuertoDestino) {
25 this.PuertoOrigen = PuertoOrigen;
26 this.PuertoDestino = PuertoDestino;
27 InstanciaRecibeUDP = new TRecibeUDP();
28 InstanciaEnviaUDP = new TEnviaUDP();
29 Frame Marco = new Frame("Talk");
30 Panel panel = new Panel();
31 Label EtiquetaMensajeSaliente = new Label ("Mensaje
32 Saliente:");
33 Label EtiquetaMensajeEntrante = new Label ("Mensaje
34 Entrante:");
35 Label EtiquetaHostDestino = new Label ("Host destino");
36 AreaRecibir = new TextArea(3,24);
37 AreaEnviar = new TextArea(3,24);
38 HostDestino = new TextField("127.0.0.1");
39 BotonEnviar = new Button("Enviar");
40
41 Marco.setSize(250,280);
42 Marco.setLayout(new BorderLayout());
43 Marco.add("Center",panel);
44 panel.add(EtiquetaHostDestino);
45 panel.add(HostDestino);
46 panel.add(BotonEnviar);
47 panel.add(EtiquetaMensajeSaliente);
48 panel.add(AreaEnviar);
49 panel.add(EtiquetaMensajeEntrante);
50 panel.add(AreaRecibir);
51 Marco.show();
52
53 TalkRecibir HiloRecibir = new TalkRecibir();
54 TalkEnviar HiloEnviar = new TalkEnviar();
55
56 HiloRecibir.start();
57 HiloEnviar.start();
58
59 } // Constructor
60
61
62 public static void main(String[] args) {
63 if (args.length!=2) {
 JESÚS BOBADILLA SANCHO 77

64 System.out.println("usar: java Talk PuertoOrigen


65 PuertoDestino, ejemplo: java Talk 5000 5002");
66 System.exit(1);
67 }
68 Talk Instancia = new Talk(Integer.parseInt(args[0]),
69 Integer.parseInt(args[1]));
70 }
71
72
73 // ********************************************************
74 // * Thread para recibir datos *
75 // ********************************************************
76 private class TalkRecibir extends Thread {
77
78 public void run() {
79 do {
80 MensajeRecibido = InstanciaRecibeUDP.Recibe(
81 PuertoOrigen,TamanioMaximoMensaje);
82 AreaRecibir.setText(MensajeRecibido);
83 } while (MensajeRecibido.length()!=0);
84 System.exit(1);
85 }
86 } //class TalkRecibir
87
88
89 // *******************************************************
90 // * Thread de envio *
91 // *******************************************************
92 private class TalkEnviar extends Thread {
93
94 public void run() {
95 BotonEnviar.addActionListener(new RespuestaAEnviar());
96 }
97
98 private class RespuestaAEnviar extends Object
99 implements ActionListener {
100 public void actionPerformed(ActionEvent e) {
101 String Mensaje = AreaEnviar.getText();
102 InstanciaEnviaUDP.Envia(Mensaje,Mensaje.length(),
103 HostDestino.getText(),PuertoDestino);
104 AreaEnviar.setText("");
105 if (Mensaje.length() ==0)
106 System.exit(1); // Al mandar un mensaje vacio
107 // se abandona el programa
108 } // actionPerformed
109 } // RespuestaAEnviar
110
111 } //class TalkEnviar
112 } // clase
78 © JESÚS BOBADILLA SANCHO

5.6 RECEPCIÓN DE DISTINTOS TIPOS DE MENSAJES


En este ejemplo de comunicaciones UDP creamos una clase PruebaEnvia
que genera mensajes de dos tipos:

Tipo del mensaje Formato del mensaje


Coordenada x, y del cursor * ValorX ValorY, siendo “ValorX” y “ValorY” números
enteros
Mensaje de texto Mensaje: sssssss siendo “sssssss” un String

De la misma manera podríamos enviar otro tipo de mensajes, seguramente


con un formato más regular; por ejemplo: comenzando cada mensaje con un byte
que indique el tipo de mensaje que viene en los siguientes bytes.

El objetivo es disponer de una ventana “cliente” donde podamos enviar


mensajes de texto y transmitir todos los cambios de coordenadas del cursor
producidos por los movimientos del ratón. La ventana “servidor” reflejará los
mensajes recibidos, tanto de texto como de movimientos de ratón. A continuación se
muestra el aspecto de ambos GUI:

En el constructor de la clase PruebaEnvia se define un GUI con una etiqueta


(“Mensaje:”) y tres campos de texto (líneas 18 a 21) que nos sirven para definir el
mensaje a enviar, el nodo destino y el puerto destino.

En la línea 29 se define una instancia de la clase TEnviaUDP, a través de la


que podremos hacer uso del método Envia (líneas 53 a 55). En las líneas 31 y 32 se
añaden las clases de tratamiento de eventos: RespuestaAEnviar (asociada a la
introducción de una frase en CampoMensaje) y MovimientoDelRaton (asociada a los
movimientos de ratón en panel).

En la clase RespuestaAEnviar (línea 36) se implementa el método


actionPerformed (línea 37), que recoge el String introducido en CampoMensaje
(línea 38) y lo envía al nodo y puerto de destino en las comunicaciones (líneas 39 a
41). Si el mensaje es vacío, terminamos la aplicación (líneas 42 y 43).
 JESÚS BOBADILLA SANCHO 79

La clase MovimientoDelRaton (línea 48) se encarga de recoger los eventos


producidos por el movimiento del ratón (método mouseMoved en la línea 50). A
través del parámetro MouseEvent (línea 50) se accede a las coordenadas “x” e “y”
del puntero del ratón (métodos getX y getY en las líneas 51 y 52) y se compone el
mensaje (con el asterisco en su cabecera); posteriormente, el mensaje se envía
(líneas 53 a 55), y se comprueba si deseamos salir de la aplicación (líneas 56 y 57).

En las líneas 64 a 66 implementamos el método main, que instancia a


PruebaEnvia , arrancando la aplicación.

1 import java.awt.*;
2 import java.awt.event.*;
3
4 public class PruebaEnvia extends Object {
5
6 static final int TamanioMaximoMensaje = 20;
7 Frame Marco;
8 Panel panel;
9 Label EtiquetaMensaje;
10 TextField CampoMensaje;
11 TextField HostDestino;
12 TextField Puerto;
13 TEnviaUDP EnviaFrase;
14
15 public PruebaEnvia() {
16 Marco = new Frame("EnvioUDP");
17 panel = new Panel();
18 EtiquetaMensaje = new Label ("Mensaje: ");
19 CampoMensaje = new TextField(TamanioMaximoMensaje);
20 HostDestino = new TextField("127.0.0.1",15);
21 Puerto = new TextField("5000",4);
22 Marco.setSize(500,200);
23 Marco.add (panel);
24 panel.add (HostDestino);
25 panel.add (Puerto);
26 panel.add (EtiquetaMensaje);
27 panel.add (CampoMensaje);
28 Marco.show();
29 EnviaFrase = new TEnviaUDP();
30
31 CampoMensaje.addActionListener(new RespuestaAEnviar());
32 panel.addMouseMotionListener(new MovimientoDelRaton());
33 } // constructor
34
35
36 private class RespuestaAEnviar implements ActionListener {
37 public void actionPerformed(ActionEvent e) {
38 String Mensaje = CampoMensaje.getText();
80 © JESÚS BOBADILLA SANCHO

39 EnviaFrase.Envia(Mensaje,Mensaje.length(),
40 HostDestino.getText(),
41 Integer.parseInt(Puerto.getText()));
42 if (Mensaje.length() ==0)
43 System.exit(1); // Al mandar un mensaje vacio se
44 // abandona el programa
45 } // actionPerformed
46 } // RespuestaAEnviar
47
48 private class MovimientoDelRaton implements
49 MouseMotionListener {
50 public void mouseMoved(MouseEvent e) {
51 String Mensaje="*"+String.valueOf(e.getX())+
52 " "+String.valueOf(e.getY());
53 EnviaFrase.Envia(Mensaje,Mensaje.length(),
54 HostDestino.getText(),
55 Integer.parseInt(Puerto.getText()));
56
57 } // mouseMoved
58
59 public void mouseDragged(MouseEvent e) {
60 }
61
62 } // MovimientoDelRaton
63
64 public static void main(String[] args) {
65 PruebaEnvia MiClaseEnvia = new PruebaEnvia();
66 } //main
67
68 } // clase

La clase PruebaRecibe es muy sencilla: el método main (línea 43) instancia


el constructor de la clase (línea 5), que realiza todo el trabajo. En primer lugar se
crea un interfaz gráfico de usuario con dos etiquetas (líneas 13 y 14):
EtiquetaMensaje (para visualizar los mensajes que llegan) y PosicionXY, para
mostrar la situación del cursor que existe en el equipo origen de la comunicación.

A continuación entramos en un bucle (línea 23) de recepción-visualización


de mensajes hasta que recibimos un mensaje vacío (línea 38). Dentro del bucle,
esperamos hasta que nos llegue un mensaje (líneas 24 y 25), determinamos de qué
tipo es (líneas 27 y 28) y lo mostramos.

Si el mensaje es de tipo “Texto” lo mostramos en la etiqueta


EtiquetaMensaje (línea 35); si por el contrario es de tipo “coordenadas”, obtenemos
la “x” y la “y” (líneas 29 y 30) y las mostramos entre paréntesis (línea 32).
 JESÚS BOBADILLA SANCHO 81

1 import java.awt.*;
2
3 public class PruebaRecibe extends Object {
4
5 public PruebaRecibe() {
6 final int Puerto = 5000;
7 final int TamanioMaximoMensaje = 20;
8
9 String MensajeRecibido;
10 TRecibeUDP InstanciaRecibeUDP = new TRecibeUDP();
11 Frame Marco = new Frame("RecepcionUDP");
12 Panel panel = new Panel();
13 Label EtiquetaMensaje = new Label ("Mensaje:");
14 Label PosicionXY = new Label("");
15
16 Marco.setSize(200,80);
17 panel.setLayout(new GridLayout(2,1));
18 Marco.add(panel);
19 panel.add(EtiquetaMensaje);
20 panel.add(PosicionXY);
21 Marco.show();
22
23 do {
24 MensajeRecibido = InstanciaRecibeUDP.Recibe
25 (Puerto,TamanioMaximoMensaje);
26 try {
27 int i = MensajeRecibido.indexOf(" ");
28 if (i!=-1&&MensajeRecibido.indexOf("*")==0) {
29 String X = MensajeRecibido.substring(1,i);
30 String Y = MensajeRecibido.substring
31 (i+1,MensajeRecibido.length());
32 PosicionXY.setText("("+X+", "+Y+")");
33 }
34 else
35 EtiquetaMensaje.setText("Mensaje: "+MensajeRecibido);
36 } catch (Exception e){ }
37
38 } while (MensajeRecibido.length()!=0);
39
40 System.exit(1);
41 } // Constructor
42
43 public static void main(String[] args) {
44 PruebaRecibe MiClaseRecibe = new PruebaRecibe();
45 } //main
46
47 } // clase
82 © JESÚS BOBADILLA SANCHO

5.7 SERVICIO CHAT


El protocolo UDP puede resultar muy adecuado para soportar un servicio de
chat, suponiendo una fiabilidad razonable en las líneas de comunicaciones. Si la
probabilidad de que los mensajes se reciban adecuadamente es alta, UDP
proporciona un nivel de comunicaciones sencillo y poco exigente en recursos.

En este apartado se desarrolla un servicio de chat, proporcionándose tanto la


parte del servidor como la del cliente. El servidor se encarga de recibir los mensajes
de los clientes, almacenarlos en una lista FIFO (primero en entrar, primero en salir)
y transmitirlos a los clientes cuando éstos los solicitan. Los clientes envían mensajes
y reciben, periódicamente, la lista de mensajes que almacena el servidor.

Gráficamente, la aplicación se comporta del siguiente modo:


1

3
2
5 Servidor
Cliente 1
Lista FIFO
4 Mensaje ‘n'

Mensaje n-1

Mensaje 1
Cliente 2

Cuando un usuario escribe un mensaje en el programa cliente del chat, el


texto se empaqueta en un datagrama y se envía al servidor (1); el servidor recibe el
mensaje y lo guarda en la primera posición del buffer FIFO (2). Esta operación se
repite cada vez que un cliente envía un mensaje al servidor del chat.

Los programas cliente hacen peticiones periódicas al servidor (3) con el fin
de recibir todos los mensajes almacenados en la lista FIFO (4 y 5); de esta forma, los
usuarios pueden ver de manera automática los últimos mensajes recibidos en el
servidor del chat.

A continuación se presenta el diseño empleado en la aplicación. La clase


TRecibeUDP ha sido ampliada para proporcionar métodos adicionales. Los nuevos
ficheros creados son ChatCliente.java y ChatServidor.java, cada uno de los cuales
contiene varias clases.
 JESÚS BOBADILLA SANCHO 83

ChatServidor ChatCliente

Conversación ChatEnviar

ChatRecibir

TRecibeUDP TEnviaUDP

TRecibeUDP

La clase TRecibeUDP ha sido modificada para incorporar el método


DameIPRemota (línea 30); este método devuelve el contenido de la propiedad
IPRemota (declarada en las línea 9). La dirección IP remota se consigue en la línea
20, a través del método getAddress, perteneciente a la clase DatagramPacket, y del
método getHostAddress, perteneciente a la clase InetAddress.

1 import java.net.*;
2
3 public class TRecibeUDP extends Object {
4
5 DatagramSocket MiSocket;
6 DatagramPacket Paquete;
7 byte[] buffer;
8
9 String IPRemota="";
10
11 public synchronized String Recibe(int Puerto, int
12 TamanioMaximoMensaje,int TimeOut) {
13 try {
14 MiSocket = new DatagramSocket(Puerto);
15 MiSocket.setSoTimeout(TimeOut);
16 buffer = new byte[TamanioMaximoMensaje];
17 Paquete = new DatagramPacket(buffer, buffer.length);
18 MiSocket.receive(Paquete);
19
20 IPRemota = Paquete.getAddress().getHostAddress();
21 MiSocket.close();
22 } catch (Exception e){
84 © JESÚS BOBADILLA SANCHO

23 System.out.println ("Error");
24 } //try
25 return new String(Paquete.getData()).
26 substring(0,Paquete.getLength());
27 } // Recibe
28
29
30 public synchronized String DameIPRemota(){
31 return IPRemota;
32 }
33
34 } // TRecibeUDP

ChatServidor

La clase ChatServidor define la clase privada Conversacion (línea 52), que


se encarga de crear y mantener la lista FIFO y de proporcionar los métodos de
acceso necesarios: InsertarMensaje (línea 63) y DameMensajes (línea 69);
ImprimeMensajes es un método útil para hacer seguimientos de funcionamiento de
la clase.

La aplicación servidora del chat se instancia en el método main, situado en


la línea 46. Toda la funcionalidad del servidor de chat se implementa en su
constructor (línea 3); en primer lugar se definen sus propiedades:
PUERTO_ORIGEN (línea 4) define el puerto que utiliza el servidor del chat,
mientras que PUERTO_DESTINO (línea 5) indica el puerto que utilizan los clientes
del servicio. El resto de constantes definen el tamaño máximo de los mensajes, el
número de mensajes que se almacenan, dos códigos que se utilizan para distinguir el
tipo de los mensajes entrantes y, por último, el tiempo que se espera la llegada de un
mensaje (un timeout de cero significa tiempo de espera indefinido).

En las líneas 17 y 18 se crean instancias de las clases TRecibeUDP y


TEnviaUDP, con el fin de utilizarlas en la recepción y envío de mensajes de texto.
En la línea 19 se crea una instancia de la clase Conversacion, indicando que se van a
almacenar 10 mensajes.

El núcleo del servidor se encuentra en el interior del bucle do while infinito,


situado en las líneas 21 y 42. En primer lugar se espera indefinidamente (timeout a
 JESÚS BOBADILLA SANCHO 85

cero) a que se reciba un mensaje (líneas 22 y 23); los mensajes que le llegan al
servidor pueden ser de 2 tipos diferentes:
• Proporcionan texto a insertar en la lista FIFO (caracterizados, en este
ejemplo, por contener un cero en el primer carácter del mensaje)
• Solicitan el envío del contenido de la lista FIFO (caracterizados, en este
ejemplo, por contener un uno en el primer carácter del mensaje)
0Hola Pedro

1xxxx

Servidor
11 Cliente 1
Mensaje1\nMensaje2\n... Lista FIFO
Mensaje ‘n'

Mensaje n-1

Mensaje 1

En la línea 24 se aísla el primer carácter del mensaje; en la línea 25 se


consulta su contenido, si nos encontramos ante una petición de inserción de texto en
la lista FIFO, se elimina el primer carácter (el cero) del mensaje (líneas 26 y 27), se
inserta el texto en la lista FIFO (línea 28) y se imprime el contenido por la consola
del servidor del chat (líneas 29 y 30).

Si el mensaje proveniente del cliente es del tipo “solicitud de envío de la


lista FIFO” (línea 31), se realizan las siguientes acciones:
• Inicialización de la variable MensajeAEnviar (línea 32)
• Obtención de los mensajes contenidos en la lista FIFO (línea 33), utilizando
el método DameMensajes de la clase Conversacion.
• Concatenación de todos los mensajes en la variable MensajeAEnviar,
separados por un salto de línea “\n” (líneas 35 y 36).
• Determinación de la dirección IP del cliente (línea 37)
• Envío del texto contenido en MensajeAEnviar al cliente que realizó la
petición.

1 public class ChatServidor {


2
3 ChatServidor() {
4 final int PUERTO_ORIGEN = 5002;
5 final int PUERTO_DESTINO = 5000;
6 final int TAMANIO_MENSAJE=40;
7 final int NUM_MENSAJES=10;
8 final char INSERTAR_FRASE='0';
86 © JESÚS BOBADILLA SANCHO

9 final char DEVOLVER_FRASES='1';


10 final int TIMEOUT=0;
11
12 String MensajeRecibido, MensajeAEnviar,IPRemota;
13 String[] MensajesRecibidos;
14 char TipoMensaje;
15 int PuertoRemoto;
16
17 TRecibeUDP InstanciaRecibeUDP = new TRecibeUDP();
18 TEnviaUDP InstanciaEnviaUDP = new TEnviaUDP();
19 Conversacion ConversacionChat = new
20 Conversacion(NUM_MENSAJES);
21 do {
22 MensajeRecibido = InstanciaRecibeUDP.
23 Recibe(PUERTO_ORIGEN,TAMANIO_MENSAJE,TIMEOUT);
24 TipoMensaje = MensajeRecibido.charAt(0);
25 if (TipoMensaje==INSERTAR_FRASE) {
26 MensajeRecibido=MensajeRecibido.substring(1,
27 MensajeRecibido.length());
28 ConversacionChat.InsertarMensaje(MensajeRecibido);
29 System.out.println("Se ha recibido el
30 mensaje: "+MensajeRecibido);
31 } else { // (TipoMensaje==DEVOLVER_FRASES)
32 MensajeAEnviar="";
33 MensajesRecibidos=ConversacionChat.DameMensajes();
34 for (int i=0;i<NUM_MENSAJES;i++)
35 MensajeAEnviar = MensajeAEnviar+
36 MensajesRecibidos[i]+"\n";
37 IPRemota=InstanciaRecibeUDP.DameIPRemota();
38 InstanciaEnviaUDP.Envia(MensajeAEnviar,
39 TAMANIO_MENSAJE*NUM_MENSAJES,IPRemota,
40 PUERTO_DESTINO);
41 }
42 } while (true);
43
44 }
45
46 public static void main(String[] args){
47 ChatServidor InstanciaChat = new ChatServidor();
48 }
49
50
51
52 private class Conversacion {
53 private String[] Mensajes;
54 private int NumMensajes;
55
56 Conversacion(int NumMensajes) {
57 Mensajes = new String[NumMensajes];
 JESÚS BOBADILLA SANCHO 87

58 this.NumMensajes = NumMensajes;
59 for (int i=0;i<NumMensajes;i++)
60 Mensajes[i]="";
61 }
62
63 public void InsertarMensaje(String Mensaje) {
64 for (int i=NumMensajes-2;i!=-1;i--)
65 Mensajes[i+1]=Mensajes[i];
66 Mensajes[0] = Mensaje;
67 }
68
69 public String[] DameMensajes() {
70 return Mensajes;
71 }
72
73 public void ImprimeMensajes() {
74 for (int i=0;i<NumMensajes;i++)
75 System.out.println(Mensajes[i]);
76 System.out.println();
77 }
78
79 }
80
81
82 } // ChatServidor

ChatCliente

La aplicación cliente del chat debe proporcionar un GUI (Graphic User


Interfaz) que permita, en todo momento (en paralelo), enviar un mensaje y recibir
los mensajes del servidor:
0Hola Pedro
Hilo que envía
1xxxx
Hilo que recibe
Cliente
Mensaje1\nMensaje2\n...

La clase ChatCliente define, en primer lugar, sus constantes y variables


globales. El tamaño máximo del mensaje se establece como 40 bytes (igual que en el
servidor), el timeout para recibir mensajes en 200 milisegundos, y el puerto origen y
destino de la comunicación cruzados respecto al servidor (línea 11).
88 © JESÚS BOBADILLA SANCHO

En las líneas 19 y 20 se declaran la s variables de las clases TRecibeUDP y


TEnviaUDP, con el fin de poder realizar comunicaciones en modo texto. El
constructor de la clase instancia los objetos de comunicaciones (líneas 23 y 24), crea
el GUI (líneas 25 a 43), instancia los hilos que reciben y envían mensajes (líneas 45
y 46) y los arranca (líneas 48 y 49).

El hilo encargado de recibir y visualizar la lista FIFO del servidor es


ChatRecibir (línea 61). El método run de esta clase (línea 63) se ejecuta cuando
invocamos el método start (línea 48); en primer lugar se envía al servidor
(PUERTO_DESTINO) un mensaje de un byte con el contenido “1” (líneas 65 y 66).
El servidor responde a este mensaje con el envío de toda la lista FIFO, que es
recogida en las líneas 67 y 68 y visualizada en la línea 69; posteriormente se para la
ejecución del hilo durante 5 segundos, utilizando el método sleep (línea 71) con el
parámetro 5000l (valor longword 5000).

Las acciones explicadas en el párrafo anterior se sitúan en un bucle infinito


(líneas 64 y 73), con lo que se consigue visualizar en el área de texto AreaRecibir, el
contenido de la lista FIFO de manera periódica cada 5 segundos. Si el mensaje del
servidor no llega en TIMEOUT milisegundos (200), se intenta de nuevo a los 5
segundos.

El hilo encargado de enviar los mensajes del usuario es ChatEnviar (línea


82). En su método run (línea 84) simplemente se añade el ActionListener (línea 85)
MensajeAEnviar (línea 88), asociado al botón BotonEnviar. El método
actionPerformed (línea 90), en primer lugar prepara el texto a enviar tomando el
contenido de la caja de texto correspondiente, precedido de un cero (línea 91);
después envía el mensaje (líneas 92 y 93) e inicializa la caja de texto donde el
usuario escribe los mensajes (línea 94).

1 import java.awt.*;
2 import java.awt.event.*;
3 import java.util.*;
4
5 // *******************************************************
6 // * Cliente Chat *
7 // *******************************************************
8 public class ChatCliente {
9 final int TAMANIO_MAX_MENSAJE = 40;
10 final int TIMEOUT=200;
11 final int PUERTO_ORIGEN=5000, PUERTO_DESTINO=5002;
12
13 TextArea AreaRecibir;
14 TextField TextoEnviar;
15 TextField HostDestino;
16 Button BotonEnviar;
17 String MensajeRecibido;
18
 JESÚS BOBADILLA SANCHO 89

19 TRecibeUDP InstanciaRecibeUDP;
20 TEnviaUDP InstanciaEnviaUDP;
21
22 public ChatCliente(String Host) {
23 InstanciaRecibeUDP = new TRecibeUDP();
24 InstanciaEnviaUDP = new TEnviaUDP();
25 Frame Marco = new Frame("Chat");
26 Panel PanelSur = new Panel();
27 Label EtiquetaMensajeSaliente =new Label ("Mensaje:");
28 Label EtiquetaHostDestino =new Label ("Host destino");
29 TextoEnviar = new TextField(25);
30 AreaRecibir = new TextArea();
31 HostDestino = new TextField(Host);
32 BotonEnviar = new Button("Enviar");
33
34 Marco.setSize(600,250);
35 Marco.setLayout(new BorderLayout());
36 Marco.add("South",PanelSur);
37 PanelSur.add(EtiquetaHostDestino);
38 PanelSur.add(HostDestino);
39 PanelSur.add(EtiquetaMensajeSaliente);
40 PanelSur.add(BotonEnviar);
41 PanelSur.add(TextoEnviar);
42 Marco.add("Center",AreaRecibir);
43 Marco.show();
44
45 ChatRecibir HiloRecibir = new ChatRecibir();
46 ChatEnviar HiloEnviar = new ChatEnviar();
47
48 HiloRecibir.start();
49 HiloEnviar.start();
50
51 } // Constructor
52
53 public static void main(String[] args) {
54 ChatCliente Instancia = new ChatCliente(args[0]);
55 }
56
57 // *******************************************************
58 // * Thread para recibir datos *
59 // *******************************************************
60
61 private class ChatRecibir extends Thread {
62
63 public void run() {
64 do {
65 InstanciaEnviaUDP.Envia("1",1,HostDestino.getText(),
66 PUERTO_DESTINO);
67 MensajeRecibido = InstanciaRecibeUDP.Recibe(
90 © JESÚS BOBADILLA SANCHO

68 PUERTO_ORIGEN,400,TIMEOUT);
69 AreaRecibir.setText(MensajeRecibido);
70 try {
71 Thread.sleep(5000l);
72 } catch (InterruptedException e) {}
73 } while (true);
74 }
75 } //class ChatRecibir
76
77
78 // *******************************************************
79 // * Thread de envio *
80 // *******************************************************
81
82 private class ChatEnviar extends Thread {
83
84 public void run() {
85 BotonEnviar.addActionListener(new MensajeAEnviar());
86 }
87
88 private class MensajeAEnviar extends Object implements
89 ActionListener {
90 public void actionPerformed(ActionEvent e) {
91 String Mensaje = "0"+TextoEnviar.getText();
92 InstanciaEnviaUDP.Envia(Mensaje,Mensaje.length(),
93 HostDestino.getText(),PUERTO_DESTINO);
94 TextoEnviar.setText("");
95 } // actionPerformed
96 } // MensajeAEnviar
97
98 } //class ChatCliente
99
100 } // clase

Ejecución del programa


 JESÚS BOBADILLA SANCHO 91

5.8 CONFIGURACIÓN DE LAS COMUNICACIONES


A continuación se muestran los métodos más utilizados de las clases
DatagramPacket y DatagramSocket:

DatagramPacket
Métodos principales Acción
InetAddress getAddress() Dirección del nodo remoto en la comunicación
byte[] getData() Devuelve el mensaje que contiene el datagrama
int getLength() Devuelve la longitud del mensaje del datagama
int getOffset() Devuelve el desplazamiento que indica el
comienzo del mensaje (dentro del array de bytes)
int getPort() Devuelve el valor del puerto remoto
void setAddress(InetAddress d) Establece el nodo remoto en la comunicación
void setData(byte[] Mensaje) Establece el mensaje que contiene el datagrama
void setData(byte[] Mensaje, int Establece el mensaje que contiene el datagrama,
Desplazamiento, int Longitud) indicando su desplazamiento en el array de bytes
y su longitud
92 © JESÚS BOBADILLA SANCHO

void setLength(int Longitud) Establece la longitud del mensaje del datagama


void setPort() Establece el valor del puerto remoto
void setSocketAddress(SocketAddress d) Establece la dirección (nodo+puerto) remota en la
comunicación

DatagramSocket
Métodos principales Acción
void bind(SocketAddress a) Asigna la dirección establecida al socket
void close() Cierra el socket
void connect(SocketAddress a) Conecta el socket a la dirección remota
establecida
void connect(InetAddress a, int puerto) Conecta el socket a la dirección establecida y el
puerto especificado
void disconnect() Desconecta el socket
InetAddress getInetAddress() Devuelve la dirección a la que está conectada el
socket
InetAddress getLocalAddress() Devuelve la dirección del socket
int getLocalPort() Devuelve el número de puerto asociado al socket
OutputStream getOutputStream() Devuelve el stream de salida asociado al socket
int getPort() Devuelve el valor del Puerto remoto al que está
conectado
int getSoTimeout() Devuelve el valor en milisegundos que el socket
espera al establecimiento de comunicación
boolean isBound() Indica si el socket está vinculado
boolean isClosed() Indica si el socket está cerrado
boolean isConnected() Indica si el socket está conectado
void setSoTimeout(int ms) Indica el valor en milisegundos que el socket
espera al establecimiento de comu nicación
LECCIÓN 6

APLICACIONES WEB EN SERVIDOR:


SERVLETS

6.1 INTRODUCCIÓN
Los servlets de Java proporcionan un mecanismo para ejecutar programas en
equipos servidores, en función de las peticiones que los clientes realicen haciendo
uso de navegadores web. Por lo tanto, los servlets nos permiten crear páginas web
activas, en el sentido de que la respuesta que ofrecen puede variar en función de los
datos que proporcione el cliente y la situación de contexto existente. Por ejemplo,
utilizando servlets, podemos informar a un cliente de los vuelos disponibles en un
momento dado (situación de contexto), para un origen y destino seleccionados
(datos que proporciona el cliente).

Gráficamente, el funcionamiento de los servlets es el siguiente:

6 2
Servidor
Cliente 1 3
Servidor Web
(Apache, IIS,
5 Tomcat)
Contenedor de
servlets (Tomcat)
4
Cliente 2

1 El cliente realiza una petición http haciendo uso de un navegador (Explorer,


Netscape, Opera, etc.). En esta petición pueden existir parámetros con sus
valores (por ejemplo aeropuerto de origen y aeropuerto de destino del vuelo
solicitado).
94 © JESÚS BOBADILLA SANCHO

2 La petición y sus datos asocia dos, que le llegan al equipo servidor a través de
la red, los recoge el programa servidor de web utilizado.
3 El servidor web detecta que hay que ejecutar un servlet y delega esta acción
en el programa “contenedor de servlets”, que se encargará de llevarla a cabo.
Puede darse la circunstancia de que el contenedor de servlets también haga la
función de servidor web.
4 Una vez ejecutado el servlet, se traspasan los resultados (en forma de página
web) al servidor web.
5 El servidor web envía la página web de resultados a través del ordenador
servidor.
6 La página web de respuesta le llega al cliente, que la visualizará haciendo uso
del navegador (cliente web) utilizado.

6.2 PAQUETES Y CLASES UTILIZADOS


Los paquetes que intervienen directamente en la creación de servlets son:
javax.servlet y javax.servlet.http , ambos correspondientes a J2EE. El primero de
ellos contiene los interfaces y clases más genéricos que soportan el funcionamiento
de los servlets. Al programar servlets, lo más habitual es hacer uso del segundo
paquete (javax.servlet.http ), que resulta más especializado, útil y fácil de utilizar.

Los servlets de Java tienen un ciclo de vida marcado por tres métodos del
interfaz javax.servlet.Servlet: init(...), service(...) y destroy(). El método init lo
invoca el contenedor de servlets, una sola vez; resulta útil para inicializar recursos
que serán necesarios en la ejecución del servlet (abrir ficheros, conectar bases de
datos, establecer comunicaciones, etc.). El método destroy se invoca cuando el
servlet va a ser descargado del servidor; en su interior se debería programar la
liberación de recursos utilizados en el método init.

El método service realiza el trabajo “cotidiano” del servlet: dar respuesta a


las distintas peticiones de los clientes. Cada vez que un cliente realiza una petición
(GET, POST, etc.) el servidor ejecuta el método service. Siguiendo nuestro ejemplo
de acceso a información de vuelos, cuando el contenedor de servlets carga el servlet
que hemos programado, invoca al método init; cada vez que un usuario hace una
consulta de vuelos, se invoca al método service; finalmente, cuando descargamos el
servlet del servidor, se invoca el método destroy.
 JESÚS BOBADILLA SANCHO 95

init(...) service(...) destroy()

La clase javax.servlet.http.HttpServlet es la que se utiliza en casi todas las


implementaciones de servlets y la que vamos a emplear en nuestros ejemplos.
HttpServlet conserva el método service(...), pero además proporciona los métodos
doGet, doPost, y los de menor utilización doPut, doDelete, doTrace, doOptions y
doHead. Cuando al servlet le llega una petición de tipo GET, se ejecuta
automáticamente el método doGet(...), lo mismo ocurre con las peticiones POST,
PUT, DELETE, TRACE, OPTIONS y HEAD.

Cuando creamos un sitio web que contiene páginas activas, escribimos las
páginas de consulta y también los programas que responden, desde el servidor, a las
peticiones que los clientes realizan rellenando las páginas web de consulta. En el
ejemplo de peticiones de vuelo, cuando el usuario selecciona los aeropuertos de
origen y de destino, podemos enviar esta información con formato GET o POST. Si
elegimos la primera opción, se ejecutará el método doGet en el servlet del equipo
servidor; si elegimos la segunda opción se ejecutará el método doPost.

<form action=”URL del servlet” method=”GET”>


1
<select name=”AeropuertoOrigen”>
<option>……….. 6 2
</select> Servidor
Servidor Web 3
<select name=”AeropuertoDestino”>
(Apache, IIS,
<option>……….. 5 Tomcat)
Contenedor
</select> de servlets
…………. 4 (Tomcat)

</form>
Servlet

doGet(...) { ...... }

El método service de la clase javax.servlet.Servlet tiene como parámetros un


objeto de tipo ServletRequest y otro de tipo ServletResponse. ServletRequest
proporciona la información que llega del servidor web, mientras que
ServletResponse envía al cliente la información que genera el servlet. Los métodos
doXXX, como por ejemplo doGet, tienen como parámetros un objeto de tipo
HttpServletRequest y otro de tipo HttpServletResponse, que especializan a sus
correspondientes superclases ServletRequest y ServletResponse.
96 © JESÚS BOBADILLA SANCHO

6 2
Servidor

Servidor Web 3
(Apache, IIS,
5 Tomcat)
Contenedor
de servlets
4 (Tomcat)
ServletRequest ServletResponse

HttpServletRequest HttpServletResponse

Servlet

doGet(HttpServletRequest rq, HttpServletResponse rp) { ... }

6.3 HOLA MUNDO


Nuestro primer ejemplo de servlet, como es habitual, va a implementar el
caso más sencillo posible. El primer servlet HolaMundo mostrará el texto “Hola
mundo” en el navegador del cliente.

En las primeras líneas del fichero HolaMundo.java se colocan las sentencias


import de los paquetes comentados en el apartado anterior (javax.servlet y
javax.servlet.http ). El paquete java.io se necesita para las operaciones de entrada -
salida de datos y resultados a través de los objetos HttpServletRequest y
HttpServletResponse (líneas 7 y 8).

La forma más sencilla de definir un servlet es la especialización de la clase


HttpServlet, lo que realizamos en la línea 5.

El método doGet (línea 7) es el único que implementamos de todos los que


nos ofrece la clase HttpServlet. Este método puede levantar las excepciones
IOException y ServletException (línea 9), que por sencillez no tratamos en el
ejemplo.
 JESÚS BOBADILLA SANCHO 97

La línea 11 sirve para establecer, a través del método setContentType, el tipo


de datos que utilizamos en nuestra respuesta (en este caso texto html). La línea 12 es
muy importante: utiliza el método getWriter del interfaz HttpServletResponse
(método heredado del interfaz ServletResponse); getWriter devuelve un stream de
tipo PrintWriter, capaz de canalizar el flujo de salida de datos hacia el cliente.

Utilizaremos el método getWriter cuando deseamos mandar datos con


formato de texto hacia el cliente. Si necesitamos enviar streams de bytes, usaremos
el método getOutputStream. No es posible combinar ambos métodos para escribir el
cuerpo de la página web que enviamos al usuario.

Una vez definido el objeto de respuesta de tipo PrintWriter, basta con


canalizar por él las distintas sentencias html dirigidas hacia el navegador del cliente
(líneas 13 a 17).

1 import java.io.*;
2 import javax.servlet.*;
3 import javax.servlet.http.*;
4
5 public class HolaMundo extends HttpServlet {
6
7 public void doGet(HttpServletRequest request,
8 HttpServletResponse response)
9 throws IOException, ServletException {
10
11 response.setContentType("text/html");
12 PrintWriter HaciaCliente = response.getWriter();
13 HaciaCliente.println("<html>");
14 HaciaCliente.println("<body>");
15 HaciaCliente.println("<h1>Hola Mundo</h1>");
16 HaciaCliente.println("</body>");
17 HaciaCliente.println("</html>");
18 }
19 }

6.4 PUESTA EN FUNCIONAMIENTO


Desarrollar y ejecutar por primera vez un servlet no resulta tan sencillo
como un programa Java basado en el SDK estándar (J2SE); para empezar, resulta
necesario instalar una versión de Java 2 Enterprise Edition (J2EE), donde se
encuentran los paquetes javax.servlet y javax.servlet.http, que empleamos para
definir y utilizar los servlets.
98 © JESÚS BOBADILLA SANCHO

Siguiendo las explicaciones de los apartados anteriores, para ejecutar un


servlet necesitamos un “contenedor de servlets”. Este programa servidor pasa el
control al servlet que se ha invocado desde el cliente, además de establecer los
objetos ServletRequest y ServletResponse, para establecer los flujos de información
entre cliente-servidor y servidor-cliente.

En resumen, para poder compilar y ejecutar nuestro servlet HolaMundo


necesitamos realizar las siguientes acciones:

1 Instalar y configurar una versión de Java 2 Enterprise Edition (J2EE)


2 Instalar y configurar algún “contenedor de servlets”

La primera acción se detalla en el apéndice titulado “Instalación de J2EE”,


mientras que la segunda acción se explica en el apéndice “Instalación de Tomcat”.
Tomcat es un contenedor de servlets que también se puede utilizar como servidor
web.

Siguiendo los pasos explic ados en el apéndice dedicado a la instalación de


Tomcat, en el directorio c:\Tomcat\jakarta -tomcat-4.0-b7\webapps\examples\Web-
inf\classes hemos situado el fichero HolaMundo.class. Arrancamos Tomcat,
ejecutando el fichero startup.bat situado en c:\Tomcat\jakarta-tomcat-4.0-b7\bin y
probamos su funcionamiento desde el navegador, escribiendo la URL:
http://localhost:8080/examples/servlet/HolaMundo.
 JESÚS BOBADILLA SANCHO 99

Tomcat lleva control de las diferentes versiones que creamos de un mismo


servlet. El siguiente ejemplo, Tabla, se ha compilado dos veces, de manera que el
nuevo fichero objeto Tabla.class podría ser diferente del primer fichero objeto.
Tomcat, automáticamente lo toma como un servlet que hay que inicializar (llamando
al objeto init) y cargar desde el disco del servidor. En la siguiente ventana se
muestra el aviso del cargador de clases que utiliza Tomcat, informando de la nueva
situación:

A continuación se presenta el código de la clase Tabla y el resultado que


recibe el cliente que realiza la petición de ejecución del servlet.

1 import java.io.*;
2 import javax.servlet.*;
3 import javax.servlet.http.*;
4
5 public class Tabla extends HttpServlet {
6
7 public void doGet(HttpServletRequest request,
8 HttpServletResponse response)
9 throws IOException, ServletException {
10
11 response.setContentType("text/html");
12 PrintWriter out = response.getWriter();
13 out.println("<html>");
14 out.println("<body>");
15 out.println("<table border=1 bgcolor='Salmon'>");
16 for (int Fila=1;Fila<=7;Fila++) {
17 out.println("<tr><td>");
18 out.println("<font size="+Fila+">Tamaño de
19 letra: "+Fila+"</font>");
20 out.println("</td></tr>");
21 }
100 © JESÚS BOBADILLA SANCHO

22 out.println("</table>");
23 out.println("</body>");
24 out.println("</html>");
25 }
26 }

Lo más interesante es invocar a los servlets desde páginas web. Podemos


solicitar la ejecución de un servlet de igual manera que pedimos un recurso
cualquiera (página web, fichero de sonido, imagen, etc.); basta con definir la URL
del servlet en el punto deseado en la página web.

La página Tabla.htm define un hiperenlace de texto que nos permite ejecutar


el servlet Tabla :

1 <html>
2 <body>
3 <a href="http://localhost:8080/examples/servlet/Tabla">
4 Obtener la Tabla
5 </a>
6 </body>
7 </html>
 JESÚS BOBADILLA SANCHO 101

6.5 EMPLEO DE RECURSOS DEL SERVIDOR


Una gran ventaja de los servlets es que, además de generar páginas web,
pueden utilizar toda la potencia de Java para realizar los procesamientos que se
necesiten. Como los servlets se ejecutan en el servidor, no tienen las restricciones de
seguridad que, por defecto, presentan los applets. Además, la ejecución de los
servlets no está limitada por el tipo de navegador que utilicen los usuarios; los
navegadores de los clientes únicamente recogen la respuesta generada en el servidor.
Finalmente, los servlets se benefician de la independencia de la plataforma que
presentan las aplicaciones realizadas con el lenguaje Java.

A continuación se presenta el servlet DirectorioRaiz, que muestra el


contenido del directorio raíz del servidor.

La clase DirectorioRaiz extiende HttpServlet (línea 5) e implementa el


método doGet (línea 7). En la línea 12 creamos la instancia ACliente, de tipo
PrintWriter, que utilizaremos para enviar la respuesta HTML al cliente (líneas
15,16, etc.).

En la línea 13 se crea una instancia, Fichero, de la clase File, a partir del


directorio raíz “c:\”. Utilizando el método list sobre la instancia Fichero (línea 14),
obtenemos un array de literales, Contenido, con los nombres de los ficheros
contenidos en el directorio raíz.

El bucle situado en la línea 19 se encarga de mostrar, tabularmente, cada


String del vector Contenido (línea 22).

1 import java.io.*;
2 import javax.servlet.*;
3 import javax.servlet.http.*;
4
5 public class DirectorioRaiz extends HttpServlet {
6
7 public void doGet(HttpServletRequest request,
8 HttpServletResponse response)
9 throws IOException, ServletException
10 {
11 response.setContentType("text/html");
12 PrintWriter ACliente = response.getWriter();
13 File Fichero = new File("c:/");
14 String[] Contenido = Fichero.list();
15 ACliente.println("<html>");
16 ACliente.println("<body>");
17 ACliente.println("<table border=1
18 bgcolor='Salmon'>");
19 for (int Fich=0;Fich<Contenido.length;Fich++) {
102 © JESÚS BOBADILLA SANCHO

20 ACliente.println("<tr><td>");
21 ACliente.println("<font size=1>");
22 ACliente.println(Contenido[Fich]);
23 ACliente.println("</font>");
24 ACliente.println("</td></tr>");
25 }
26 ACliente.println("</table>");
27 ACliente.println("</body>");
28 ACliente.println("</html>");
29 }
30
31 }

6.6 UTILIZACIÓN DE PARÁMETROS


Los servlets explicados en los apartados anteriores podían crear páginas web
de respuesta tan complejas como deseáramos, utilizando tantas instrucciones println
como fueran precisas, sin embargo, no tenían la posibilidad de adaptar la respuesta a
las necesidades de los usuarios.
 JESÚS BOBADILLA SANCHO 103

En este apartado se muestra la manera de recibir información del usuario.


Esta información puede ser utilizada para personalizar las respuestas ofrecidas por
los servlets.

El objeto HttpServletRequest contiene una serie de métodos de gran utilidad


para recibir información proveniente del usuario. En concreto, el método
getParameter nos permite obtener el valor asociado a un nombre en la lista de pares
(nombre, valor) que puede proporcionar una URL.

6.6.1 Ejemplo: Nombre y edad

En la siguiente URL invocamos la ejecución del servlet Parametros,


aportando la lista de pares (Nombre,Ana)-(Edad,22), en formato GET. Obsérvese la
manera de colocar los datos tras el símbolo “?” y de separarlos con el símbolo “&”:
http://localhost:8080/examples/servlet/Parametros?Nombre=Ana&Edad=22

Podemos introducir la URL directamente en el navegador o bien generarla


mediante una página web que utilizará el usuario:

1 <html>
2 <body>
3 <h1> Cuestionario </h1>
4 <form action="http://localhost:8080/examples
5 /servlet/Parametros" method="get">
6 Nombre: <input type="text" name=Nombre> <br>
7 Edad: <input type="text" name=Edad> <br><br>
8 <input type="submit" value="Enviar datos"> <br>
9 </form>
10 </body>
11 </html>

Los formularios en HTML permiten enviar datos al servidor web. En la línea


4 de la página web mostrada se encuentra la etiqueta form, que en el ejemplo
contiene el atributo action y el atributo method: action indica la acción que debe
realizarse en el servidor (ejecutar el servlet Parametros), mientras que method indica
la manera en la que se envían los datos (en este caso en el interior de la URL).
104 © JESÚS BOBADILLA SANCHO

En nuestro primer ejemplo con parámetros, únicamente recogemos los


valores (“Ana” y “22”) asociados a los nombres (Nombre y Edad), mostrándolos en
la página web de respuesta. Las líneas clave en la clase Parametros son la 14 y la
15, donde obtenemos los valores que nos llegan asociados a los nombres: Nombre
(línea 14) y Edad (línea 15).

1 import java.io.*;
2 import javax.servlet.*;
3 import javax.servlet.http.*;
4
5 public class Parametros extends HttpServlet {
6
7 public void doGet(HttpServletRequest request,
8 HttpServletResponse response)
9 throws IOException, ServletException {
10 response.setContentType("text/html");
11 PrintWriter out = response.getWriter();
12 out.println("<html>");
13 out.println("<body>");
14 String NombreRecibido=request.getParameter("Nombre");
15 String EdadRecibida = request.getParameter("Edad");
16 out.println("<h1> Hola " + NombreRecibido + "</h1>");
17 out.println("<h1> Tienes " + EdadRecibida +
18 " años </h1>");
19 out.println("</body>");
20 out.println("</html>");
21 }
22 }

6.6.2 Ejemplo. Cálculo de la hipotenusa

En el segundo ejemplo de este apartado, Hipotenusa, realizamos el cálculo


del valor de la hipotenusa en un triángulo rectángulo definido por los tamaños de los
catetos que introducen los usuarios.
 JESÚS BOBADILLA SANCHO 105

En las líneas 11 y 12 se recogen los valores correspondientes a los nombres


CatetoA y CatetoB; en las líneas 13 y 14 se convierten los Strings al tipo double,
mientras que en la línea 15 se calcula el valor de la hipotenusa del triángulo.

1 import java.io.*;
2 import javax.servlet.*;
3 import javax.servlet.http.*;
4
5 public class Hipotenusa extends HttpServlet {
6
7 public void doGet(HttpServletRequest request,
8 HttpServletResponse response)
9 throws IOException, ServletException
10 {
11 String Cateto1 = request.getParameter("CatetoA");
12 String Cateto2 = request.getParameter("CatetoB");
13 double Cat1 = Double.parseDouble(Cateto1);
14 double Cat2 = Double.parseDouble(Cateto2);
15 double Hipotenusa = Math.sqrt(Cat1*Cat1+Cat2*Cat2);
16
17 response.setContentType("text/html");
18 PrintWriter out = response.getWriter();
19 out.println("<html>");
20 out.println("<body>");
21 out.println("Hipotenusa: " + Hipotenusa);
22 out.println("</body>");
23 out.println("</html>");
24 }
25 }

Para ejecutar el servlet Hipotenusa podemos crear manualmente la URL con


los datos deseados o bien utilizar una página web que realice dicho cometido:
http://localhost:8080/examples/servlet/Hipotenusa?CatetoA=4&CatetoB=3

1 <html>
2 <body>
3 <h3> Cálculo de la hipotenusa </h3>
4 <form action="http://localhost:8080
5 /examples/servlet/Hipotenusa" method="get">
6 Cateto 1: <input type="text" name=CatetoA> <br>
7 Cateto 2: <input type="text" name=CatetoB> <br><br>
8 <input type="submit" value="Enviar datos"> <br>
9 </form>
10 </body>
11 </html>
106 © JESÚS BOBADILLA SANCHO

6.7 CHAT
En este apartado se desarrolla una aplicación que implementa el servicio
chat. Los usuarios podrán acceder al chat haciendo uso de sus programas
navegadores (Explorer, Netscape, Opera, etc.).

La arquitectura de nuestra aplicación chat presenta el siguiente esquema:

..../chat.htm

< html>...
Servidor
Cliente 1
Servidor Web
(Apache, IIS,
Tomcat)
Contenedor de
servlets (Tomcat)

Chat.htm

ChatEnvia.htm ChatVisualiza.htm

ChatInserta ChatVisualiza

ChatConversacion
 JESÚS BOBADILLA SANCHO 107

Chat.htm es la página web que utiliza el usuario para tener acceso al chat;
incluye dos marcos, que contienen las páginas ChatEnvia.htm y ChatVisualiza.htm.
ChatEnvia.htm provoca la ejecución del servlet ChatInserta , mientras que
ChatVisualiza.htm provoca la ejecución periódica del servlet ChatVisualiza.
ChatConversacion es una clase que encapsula la estructura de datos que contiene los
mensajes enviados y la forma de acceder a ellos.

Vamos a analizar cada uno de los ficheros que componen la aplicación,


empezando por ChatConversacion y avanzando hacia Chat.htm.

6.7.1 ChatConversacion

La clase ChatConversacion alberga una matriz Conversacion que contiene


10 Strings (líneas 3 y 4). Estos Strings se corresponden con los últimos 10 textos
escritos por los usuarios en el chat.

Los métodos de acceso a la estructura de datos son InsertaMensaje (línea 7)


y DameConversacion (línea 14), ambos con el atributo de acceso synchronized,
utilizado para conseguir exclusión mutua en el acceso a los métodos, y por tanto a la
estructura de datos. No debemos olvidar que diversos usuarios pueden utilizar
simultáneamente el chat, y por lo tanto tratar de acceder concurrentemente al método
InsertaMensaje, que es en el que realmente pueden existir problemas de
actualización simultánea de datos (condiciones de carrera).

El método DameConversacion es realmente simple, únicamente devuelve la


propiedad Conversacion (línea 15). El método InsertaMensaje implementa una lista
FIFO (primero en entrar, primero en salir); el bucle de la línea 9 se encarga de
desplazar una posición todos los mensajes almacenados, perdiéndose el último. En la
línea 11 se introduce, en la primera posición, el mensaje que nos llega.

Los métodos se han definido como estáticos para facilitar su uso desde los
servlets, que no necesitarán crear una instancia de la clase ChatConversacion.

1 public class ChatConversacion {


2
3 private static final int NUM_LINEAS=10;
4 private static String[]Conversacion = new
5 String[NUM_LINEAS];
6
7 public synchronized static void InsertaMensaje(String
8 Mensaje) {
9 for(int i=NUM_LINEAS-2;i>=0;i--)
10 Conversacion[i+1] = Conversacion[i];
108 © JESÚS BOBADILLA SANCHO

11 Conversacion[0]=Mensaje;
12 }
13
14 public synchronized static String[] DameConversacion() {
15 return Conversacion;
16 }
17
18 }

6.7.2 ChatInserta

El servlet ChatInserta recoge dos textos provenientes de la página web


ChatEnvia.htm; los nombres asociados a los textos son: Apodo y Mensaje (líneas 10
y 11). En la estructura de datos del chat almacenamos como literal el contenido de la
variable Apodo seguida de dos puntos y el contenido de la variable Mensaje, para
ello utilizamos el método estático InsertaMensaje, perteneciente a la clase
ChatConversacion (línea 13).

En la línea 15 se indica que se va a enviar texto en formato “html”, mientras


que en la línea 16 se define el objeto out, de tipo PrintWriter, preparado para enviar
texto a través del objeto rp, de tipo HttpServletResponse.

En las líneas 18 a 31 se crea la página web que se encarga de la inserción de


datos en el chat. Esta página es idéntica a ChatEnvia.htm, que se explicará un poco
más adelante.

1 import java.io.*;
2 import javax.servlet.*;
3 import javax.servlet.http.*;
4
5 public class ChatInserta extends HttpServlet {
6
7 synchronized public void doGet(HttpServletRequest rq,
8 HttpServletResponse rp)
9 throws IOException, ServletException {
10 String Apodo = rq.getParameter("Apodo");
11 String Mensaje = rq.getParameter("Mensaje");
12
13 ChatConversacion.InsertaMensaje(Apodo+": "+Mensaje);
14
15 rp.setContentType("text/html");
 JESÚS BOBADILLA SANCHO 109

16 PrintWriter out = rp.getWriter();


17
18 out.println("<html>");
19 out.println("<body>");
20 out.println("<form method='GET'
21 action='http://e-ducacion.eui.upm.es:8080/
22 examples/servlet/ChatInserta'>");
23 out.println("<input type='text' size=10
24 name='Apodo'>");
25 out.println("<input type='text' size=70
26 name='Mensaje'>");
27 out.println("<input type='submit' value='Enviar'>");
28 out.println("</form>");
29 out.println("</body>");
30
31 out.println("</html>");
32
33 }
34 }

6.7.3 ChatVisualiza

El servlet ChatVisualiza, en su método doGet (línea 7), obtiene las últimas


frases escritas por los usuarios y almacenadas en la aplicación (líneas 10 y 11); para
ello invoca al método DameConversacion, perteneciente a la clase
ChatConversacion.

La única instrucción compleja de este servlet es la que abarca las líneas 17,
18 y 19. Su significado se explica un poco más adelante, cuando se detalla el
funcionamiento de la página ChatVisualiza.htm.

En las líneas 22 a 27 se imprimen, de forma tabular, los últimos 10 mensajes


enviados por los usuarios. Estos mensajes se encuentran en la matriz Frases,
obtenida en las líneas 10 y 11.

1 import java.io.*;
2 import javax.servlet.*;
3 import javax.servlet.http.*;
4
5 public class ChatVisualiza extends HttpServlet {
6
7 public synchronized void doGet(HttpServletRequest rq,
8 HttpServletResponse rp)
110 © JESÚS BOBADILLA SANCHO

9 throws IOException, ServletException {


10 String[] Frases =
11 ChatConversacion.DameConversacion();
12
13 rp.setContentType("text/html");
14 PrintWriter out = rp.getWriter();
15 out.println("<html>");
16 out.println("<head>");
17 out.println("<meta http-equiv='refresh'
18 content='5;url=http://e-ducacion.eui.upm.es:8080/
19 examples/servlet/ChatVisualiza'>");
20 out.println("</head>");
21 out.println("<body>");
22 out.println("<table border=0>");
23 for (int i=0;i<Frases.length;i++) {
24 out.println("<tr><td>" + Frases[i] +
25 "</td></tr>");
26 }
27 out.println("</table>");
28 out.println("</body>");
29 out.println("</html>");
30 } }

6.7.4 ChatEnvia.htm

La página web ChatEnvia.htm define dos cajas de texto con los nombres
asociados Apodo y Mensaje (líneas 5 y 6). Cuando el usuario pulsa el botón
“Enviar” (línea 7), los datos se envían con formato get al servlet ChatInserta,
situado en el host “e-ducacion.eui.upm.es” (líneas 3 y 4).

1 <html>
2 <body>
3 <form method="GET" action="http://e-ducacion.eui.upm.es:
4 8080/examples/servlet/ChatInserta">
5 <input type="text" size=10 name="Apodo">
6 <input type="text" size=70 name="Mensaje">
7 <input type="submit" value="Enviar">
8 </form>
9 </body>
10 </html>
 JESÚS BOBADILLA SANCHO 111

6.7.5 ChatVisualiza.htm

ChatVisualiza.htm contiene una etiqueta meta que indica hacia donde se


dirige la página web; en este caso hacia el servlet ChatVisualiza, situado en el
equipo “e-ducacion.eui.upm.es” (líneas 3 y 4). El parámetro content también indica
la periodicidad en segundos con la que se actualiza la página; en este ejemplo se
ejecuta el servlet ChatVisualiza cada cinco segundos.

1 <html>
2 <head>
3 <META HTTP-EQUIV="REFRESH" CONTENT="5;url=http://e-ducacion
4 .eui.upm.es:8080/examples/servlet/ChatVisualiza">
5 </head>
6 <body>
7 </body>
8 </html>

6.7.6 Chat.htm

Chat.htm es la página web principal de nuestra aplicación de chat; define un


marco que contiene la página ChatEnvia.htm (línea 7) con una altura de 100 pixels
(línea 5). También define un marco que contiene la página web ChatVisualiza.htm
(línea 6), ocupando el resto del área visible en el navegador del cliente.

1 <html>
2 <head>
3 <title>Servicio de Chat</title>
4 </head>
5 <FRAMESET ROWS="*,100">
6 <FRAME SRC="ChatVisualiza.htm">
7 <FRAME SRC="ChatEnvia.htm">
8 </FRAMESET>
9 </html>

6.7.7 Resultados

Para acceder a la aplicación, bastaría con introducir la URL http://e-


ducacion.eui.upm.es:8080/examples/servlets/Chat.htm, en el navegador. Como
usamos el propio equipo para hacer las pruebas, en lugar de e-ducacion.eui.upm.es
pondremos localhost (en la URL y en el código de los ficheros .java y .htm)
112 © JESÚS BOBADILLA SANCHO

6.8 APLICACIÓN DE CONTROL TELEMÁTICO DE


DATOS
En esta aplicación se simula un sistema de envío, recepción y visualización
de medidas físicas (presión y temperatura). El sistema telemático combina los
protocolos UDP y HTTP; el primero se emplea para enviar las muestras recogidas
por los sensores (simulados) y el segundo protocolo (HTTP) sirve para visualizar a
lo largo del tiempo los datos que se van recibiendo.

Sensor de
Temperatura
y presión UDP

Recibe las
Sensor de UDP muestras de
Temperatura los diferentes HTTP
y pres ión lugares
Servidor web
que visualiza
Sensor de UDP periódicamente HTTP
Temperatura las muestras
y presión recogidas
 JESÚS BOBADILLA SANCHO 113

El diseño de la aplicación en clases sigue el mismo esquema que el gráfico


anterior:

TemperaturaYPresion
UDP

TemperaturaYPresion RecibeMedidas

UDP
TemperaturaYPresion MedidasSensores HTTP

PanelDeMedidas
HTTP

Una misma máquina


JVM virtual Java

Geográficamente, las instancias de las clases se pueden encontrar en


diferentes lugares, siguiendo esta disposición:

TemperaturaYPresion
UDP
HTTP
JVM
RecibeMedidas
TemperaturaYPresion UDP

JVM Internet/intranet
MedidasSensores

UDP HTTP
PanelDeMedidas
TemperaturaYPresion HTTP
JVM JVM

A continuación se explica cada una de las clases que intervienen en la


aplicación:
114 © JESÚS BOBADILLA SANCHO

6.8.1 TemperaturaYPresion

La clase TemperaturaYPresion (línea 4) simula la obtención periódica de


muestras y realiza su envío, mediante el protocolo UDP, al programa encargado de
recibir y almacenar dichas muestras. El método main de esta clase (línea 6) requiere
tres parámetros, que se utilizan para crear la instancia de la clase (líneas 7 y 8).

El constructor (línea 11) realiza todo el trabajo de la clase. Su primer


parámetro indica el número (identificador) del sensor que se está instanciando; los
parámetros segundo y tercero establecen el destino (Host y puerto) al que se envían
los datos.

La obtención de datos se simula haciendo uso de una secuencia aleatoria,


por lo que, en la línea 14, definimos el objeto SecuenciaAleatoria, de tipo Random.

El núcleo de la clase se encuentra en el interior del bucle infinito definido en


las líneas 19 y 45. En primer lugar se obtienen la temperatura y presión simulados,
en un rango de 0-60 y 0-20, respectivamente (líneas 20 a 23). En las líneas 24 y 25
se crea un valor aleatorio, SegundosEspera, que varía en un rango de 0 a 5 segundos
y que se utiliza para determinar el tiempo de espera (línea 42) hasta el cálculo de las
siguientes muestras.

Una vez calculadas las muestras de presión y temperatura, se prepara un


mensaje en el que se introducen las mismas, junto con una indicación del
identificador del sensor (líneas 29 a 31). Finalmente, el mensaje se introduce en un
DatagramPacket (línea 33) y se envía (línea 37) al host y puerto de destino (líneas
34 y 35).

1 import java.util.Random;
2 import java.net.*;
3
4 public class TemperaturaYPresion extends Thread{
5
6 public static void main(String[] args) {
7 TemperaturaYPresion Sensor = new
8 TemperaturaYPresion(args[0],args[1],args[2]);
9 }
10
11 TemperaturaYPresion(String Sensor, String HostDestino,
12 String PuertoDestino) {
13 Float Temperatura, Presion;
14 Random SecuenciaAleatoria = new Random();
15 DatagramSocket SocketSensor;
16 DatagramPacket MedidasSensor;
17 byte[] Buffer = new byte[3];
 JESÚS BOBADILLA SANCHO 115

18
19 do {
20 Temperatura = new
21 Float(SecuenciaAleatoria.nextFloat()*60);
22 Presion = new
23 Float(SecuenciaAleatoria.nextFloat()*20);
24 Float SegundosEspera = new
25 Float(SecuenciaAleatoria.nextFloat()*5000);
26 System.out.println(Temperatura);
27 System.out.println(Presion);
28
29 Buffer[0] = new Integer(Sensor).byteValue();
30 Buffer[1] = Temperatura.byteValue();
31 Buffer[2] = Presion.byteValue();
32 try {
33 MedidasSensor = new DatagramPacket(Buffer,
34 Buffer.length,InetAddress.getByName(HostDestino),
35 Integer.parseInt(PuertoDestino));
36 SocketSensor = new DatagramSocket();
37 SocketSensor.send(MedidasSensor);
38 SocketSensor.close();
39 } catch (Exception e) {}
40
41 try {
42 this.sleep(SegundosEspera.longValue());
43 } catch (InterruptedException e) {}
44
45 } while (true);
46 }
47
48 }

6.8.2 MedidasSensores

La clase MedidasSensores (línea 1) actúa como almacén de muestras entre la


clase que recoge por UDP los datos generados por los sensores (RecibeMedidas) y la
clase que envía por HTTP los datos almacenados (PanelDeMedidas).

En la línea 3 se crea el array bidimensional Medidas, que permite almacenar


los últimos datos de temperatura y presión proporcionados por un número máximo
de 5 sensores.

El método Inserta (línea 5) permite almacenar una muestra de temperatura y


presión perteneciente a un sensor dado. El método DameMedidas (línea 11)
116 © JESÚS BOBADILLA SANCHO

devuelve los últimos datos proporcionados por todos los sensores del sistema.
Debido a que las medidas se almacenan en memoria, es necesario que esta clase se
sitúe en la misma máquina virtual que RecibeMedidas y PanelDeMedidas; de esta
forma, los datos son accesibles a través de memoria.

1 public class MedidasSensores {


2
3 private static int[][] Medidas = new int[5][2];
4
5 public synchronized static void Inserta(int Sensor,
6 int Temperatura, int Presion) {
7 Medidas[Sensor][0] = Temperatura;
8 Medidas[Sensor][1] = Presion;
9 }
10
11 public synchronized static int[][] DameMedidas() {
12 return Medidas;
13 }
14 }

6.8.3 RecibeMedidas

La clase RecibeMedidas (línea 6) es muy interesante, puesto que combina el


uso de los protocolos HTTP y UDP. Esta clase extiende HttpServlet, por lo que se
comporta como un servlet; utilizamos su método doGet (línea 8) para implementar
toda la funcionalidad.

Después de la declaración de constantes y variable s (líneas 11 a 15), se


prepara una página web que informe del comienzo del servicio de recogida de
muestras (líneas 17 a 23).

Recoger las muestras supone implementar un bucle infinito (líneas 25 y 40)


en cuyo interior se reciba un paquete UDP, que se ha establecido en el puerto 6000,
(líneas 27 a 31), extraer los datos individuales del mensaje (líneas 34 a 36) e insertar
dichos valores en la estructura de datos contenida en la clase MedidasSensores (línea
38).

1 import java.net.*;
2 import javax.servlet.*;
3 import javax.servlet.http.*;
4 import java.io.*;
5
 JESÚS BOBADILLA SANCHO 117

6 public class RecibeMedidas extends HttpServlet {


7
8 public void doGet(HttpServletRequest rq,
9 HttpServletResponse rp)
10 throws IOException, ServletException {
11 final int Puerto=6000;
12 DatagramSocket SocketSensor;
13 DatagramPacket MedidasSensor;
14 byte[] Buffer = new byte[3];
15 int Sensor,Temperatura,Presion;
16
17 rp.setContentType("text/html");
18 PrintWriter out = rp.getWriter();
19 out.println("<html>");
20 out.println("<body>");
21 out.println("El servicio va a comenzar");
22 out.println("</body>");
23 out.println("</html>");
24
25 do {
26 try {
27 MedidasSensor = new
28 DatagramPacket(Buffer,Buffer.length);
29 SocketSensor = new DatagramSocket(Puerto);
30 SocketSensor.receive(MedidasSensor);
31 SocketSensor.close();
32 } catch (Exception e) {}
33
34 Sensor = new Byte(Buffer[0]).intValue();
35 Temperatura = new Byte(Buffer[1]).intValue();
36 Presion = new Byte(Buffer[2]).intValue();
37
38 MedidasSensores.Inserta(Sensor,Temperatura,Presion);
39
40 } while (true);
41 }
42
43 }

6.8.4 PanelDeMedidas

La clase PanelDeMedidas permite visualizar periódicamente las medidas


que suministran los sensores. Puesto que esta clase hereda de HttpServlet (línea 5),
podremos utilizarla desde cualquier lugar de la red haciendo uso de un navegador.
118 © JESÚS BOBADILLA SANCHO

En su método doGet (línea 7) recogemos las medidas almacenadas en la


clase MedidasSensores (línea 11) y escribimos una página web con dichos
contenidos (líneas 23 a 28) de manera tabular.

Si deseamos que los usuarios puedan ver automáticamente cómo se


actualizan los valores proporcionados por los sensores, es necesario que este servlet
se ejecute periódicamente cada cierto tiempo. Para conseguirlo incluimos una
directiva META en la cabecera de la página web generada por el servlet (líneas 17 a
19); esta directiva indica que la página se refresque cada 2 segundos, ejecutando el
servlet PanelDeMedidas.

1 import java.io.*;
2 import javax.servlet.*;
3 import javax.servlet.http.*;
4
5 public class PanelDeMedidas extends HttpServlet {
6
7 public void doGet(HttpServletRequest rq,
8 HttpServletResponse rp)
9 throws IOException, ServletException {
10
11 int[][] Medidas = MedidasSensores.DameMedidas();
12
13 rp.setContentType("text/html");
14 PrintWriter out = rp.getWriter();
15 out.println("<html>");
16 out.println("<head>");
17 out.println("<meta http-equiv='refresh' content='2;
18 url=http://localhost:8080/examples/
19 servlet/PanelDeMedidas'>");
20 out.println("</head>");
21 out.println("<body>");
22 out.println("<table border=1 bgcolor=salmon>");
23 for (int i=0;i<5;i++) {
24 out.println("<tr><td> Sensor " + i + "</td>");
25 out.println("<td>" + Medidas[i][0] + "</td>");
26 out.println("<td>" + Medidas[i][1] +
27 "</td></tr>");
28 }
29 out.println("</table>");
30 out.println("</body>");
31 out.println("</html>");
32 }
33 }
 JESÚS BOBADILLA SANCHO 119

6.8.5 PanelDeMedidas.htm

Esta página web nos facilita la primera ejecución del servlet


PanelDeMedidas.

1 <html>
2 <head>
3 <META HTTP-EQUIV="REFRESH" CONTENT="2;
4 url=http://localhost:8080/examples/servlet/PanelDeMedidas">
5 </head>
6 <body>
7 </body>
8 </html>

6.8.6 Ejecución de la aplicación

Para que actúe cada sensor hay que utilizar el comando: java
TemperaturaYPresion ‘n’ localhost 6000, siendo ‘n’ el número de sensor (en nuestro
ejemplo, de 0 a 4). Para recoger los datos en el servidor usamos el navegador,
escribiendo la URL: http://localhost:8080/examples/servlet/RecibeMedidas. Para
visualizar los datos de los sensores:

http://localhost:8080/examples/servlets/PanelDeMedidas.htm.

El resultado, a lo largo del tiempo, en una ejecución de prueba en la que se


han empleando 5 sensores es:
LECCIÓN 7

BASES DE DATOS

7.1 BASES DE DATOS RELACIONALES


La mayor parte de las bases de datos que se utilizan son relacionales, por
tanto los conceptos y ejemplos que se desarrollan en este capítulo se refieren en su
totalidad a este tipo de bases de datos. Una base de datos relacional puede contener
un conjunto de tablas relacionadas entre sí; cada tabla está definida por una serie de
campos y conformada por una lista de tuplas. En el gráfico siguiente se muestra un
ejemplo explicativo de estos conceptos.

Los campos forman las columnas de las tablas; definen el tipo y variedad de
sus datos. Las filas de datos se denominan tuplas o registros; una tabla puede estar
vacía (sin ninguna tupla) o contener un número variable de las mismas. En el
ejemplo, cada tupla de la tabla Clientes define ciertos datos básicos de una persona
(cliente), mientras que cada tupla de la tabla Productos define los datos necesarios
de cada producto disponible. Por supuesto, cada tabla de una base de datos puede
contener un número de tuplas diferente al de las demás tablas.

A cada valor de un campo definido en una tupla, se le denomina atributo . En


el gráfico, todos los textos y valores numéricos representan atributos de las tablas.

Las tablas pertenecientes a una base de datos relacional pueden


“relacionarse” entre sí utilizando campos clave comunes entre las tablas. En el
ejemplo, el campo DNI de la tabla Clientes y el campo DNI de la tabla Pedidos
permiten determinar el Nombre, Apellido y Edad de las personas que han realizado
pedidos. Así mismo, los campos Producto de las tablas Productos y Pedidos,
permiten conocer el Nombre y Valor de los productos de los que se ha realizado
algún pedido. Relacionando las tres tablas del ejemplo, podemos determinar las
cantidades de cada producto que hay que proporcionar a cada cliente (indicando su
nombre y apellidos), así como conocer la cantidad de dinero que se le debe cobrar.
122 © JESÚS BOBADILLA SANCHO

Tienda Base de datos

Clientes Tabla

DNI Nombre Apellido Edad Campos


5084 Luis Rodrígue 47
5790 Elena Martín 14 Tuplas

1413 Jorge Sojo 24

Atributos
Productos Pe didos
Producto Nombre Valor DNI Producto Unidades

A01 Patatas 3.10 5084 A07 12

A02 Tomates 1.75 5790 R03 1

R015 Camiseta 8.40 1413 A14 6

Además de los datos (explícitos) de una base de datos (los atributos), existe
una información importante: los metadatos. Los metadatos se encuentran
recopilados en un conjunto de tablas del sistema, denominado catálogo. Los
catálogos almacenan información acerca de las bases de datos, las tablas y la
composición de dichas tablas, es decir, de toda la información presentada en el
gráfico anterior, exceptuando los atributos.

A lo largo de este capítulo se mostrará la manera de consultar, modificar,


insertar y borrar datos en las tablas de una base de datos relacional. Parte de estas
manipulaciones se realizará utilizando métodos de Java, aunque existe un lenguaje
estándar de acceso a bases de datos relacionales que nos simplifica mucho esta tarea,
especialmente cuando manejamos varias tablas de manera simultánea. Este lenguaje
se denomina SQL (Structured Query Language).
 JESÚS BOBADILLA SANCHO 123

7.2 STRUCTURED QUERY LANGUAGE (SQL)


SQL es un lenguaje de manipulación de bases de datos relacionales
ampliamente utilizado en el desarrollo de aplicaciones informáticas. Resulta muy
legible y potente, por lo que es recomendable usarlo lo máximo posible cuando se
desarrollan aplicaciones en Java que emplean bases de datos.

En este apartado se describirá muy brevemente alguna de las posibilidades


del lenguaje SQL, que se empleará en diversos ejemplos de este mismo capítulo. Las
sentencias de SQL se dividen en dos grupos: Data Definition Language (DDL) y
Data Manipulation Language (DML). Nosotros prestaremos especial atención al
segundo grupo.

La sentencia CREATE pertenece al tipo DDL y nos permite crear tablas en


una base de datos. En general podremos crear las bases de datos y sus tablas de
manera estática, utilizando el entorno de gestión correspondiente (Oracle, SQL,
Access, etc.), aunque en otras ocasiones resulta necesario crearlas de manera
dinámica, por ejemplo en un foro, cuando se desea incluir un nuevo tema de
discusión.

La sentencia CREATE que genera la tabla Clientes presenta el siguiente


aspecto:

CREATE TABLE Clientes (


DNI CHAR(10) NOT NULL PRIMARY KEY,
Nombre CHAR (10) NOT NULL,
Apellido CHAR(25) NOT NULL,
Edad INT NOT NULL,
);

Se ha definido el campo DNI como clave primaria, lo que provoca la


creación de un índice y nos asegura que no podrán existir dos atributos iguales (dos
DNI’s iguales) en dicho campo. Por ser NOT NULL, no se permite la inserción de
un atributo vacío. El resto de los campos, de tipo alfanumérico y entero, también ha
sido definido como NOT NULL.

Una vez definidas (estática o dinámicamente) las tablas de una base de


datos, estamos en condiciones de insertar, borrar, modificar y seleccionar los
atributos que irán conformando el contenido de cada tabla.
124 © JESÚS BOBADILLA SANCHO

7.2.1 Trabajando con una sola tabla

Para añadir una tupla en la tabla Clientes, utilizamos la sentencia INSERT:


INSERT INTO Clientes (DNI, Nombre, Apellido, Edad) VALUES (‘5084’, ‘Luis’,
Rodríguez’, 47)

Puesto que estamos añadiendo todos los campos, podemos prescindir de su


enumeración:
INSERT INTO Clientes VALUES (‘5084’, ‘Luis’, Rodríguez’, 47)

En general, tenemos la posibilidad de incluir únicamente un subconjunto de


atributos de la tupla, aunque en este caso no nos está permitido dejar campos sin
definir, es decir con valor NULL.
INSERT INTO Clientes (DNI, Apellido) VALUES (‘5084’, Rodríguez’)

La sentencia SQL que vamos a utilizar de manera más habitual es la de


selección (SELECT). En la siguiente sentencia se seleccionan todos los atributos
correspondientes a los campos Nombre y Apellido en todas las tuplas de la tabla
Clientes.

SELECT Nombre, Apellido FROM Clientes

Análogamente, para seleccionar únicamente las edades:

SELECT Edad FROM Clientes

Si deseamos seleccionar todos los campos, podemos incluir de manera


explícita cada uno de ellos, o bien hacer uso del carácter ‘*’ , que significa: “todos
los campos de la tabla”. De esta manera, las siguientes sentencias SELECT
producen el mismo resultado: devuelven todos los datos de la tabla Clientes.
SELECT DNI, Nombre, Apellido, Edad FROM Clientes
SELECT * FROM Clientes

Hasta ahora hemos seleccionado todas las tuplas de una tabla, restringiendo
únicamente los campos que obtenemos. Si deseamos, además, poder restringir las
tuplas que nos devuelve una sentencia SELECT, debemos emplear la palabra
WHERE. La siguiente sentencia nos proporciona los números de DNI de los clientes
menores de 30 años:
SELECT DNI FROM Clientes WHERE Edad<30
 JESÚS BOBADILLA SANCHO 125

Para obtener el apellido de los clientes menores de edad que se llaman


‘Jorge’ podemos escribir la sentencia:
SELECT Apellido FROM Clientes WHERE Edad<18 AND Nombre=’Jorge’

SQL permite incluir en sus condiciones los operadores <, <=, >, >=, <>,
AND, OR, NOT, IS NULL, IS NOT NULL, LIKE, BETWEEN AND, IN, ALL,
ANY, EXISTS, etc. La palabra LIKE funciona en combinación con el carácter ‘%’,
que hace de “comodín”, por ejemplo, la siguiente sentencia selecciona todos los
atributos de la tabla Clientes en los que el campo Nombre comienza con la letra ‘J’.
SELECT * FROM Clientes WHERE Nombre LIKE ‘J%’

Si deseamos seleccionar todos los campos pertenecientes a las tuplas cuyos


atributos DNI se encuentran entre el 2000 y el 5000:
SELECT * FROM Clientes WHERE DNI BETWEEN 2000 AND 5000

El operador IN nos permite trabajar con un conjunto de valores. Por


ejemplo, para seleccionar los apellidos de los clientes que se llamen Jorge, Jesús o
Elena, podemos usar las sentencias:

SELECT Apellido FROM Clientes WHERE Nombre=’Jorge’ OR Nombre=’Jesús’


OR Nombre=’Elena’

SELECT Apellido FROM Clientes WHERE Nombre IN (‘Jorge’, ‘Jesús’, ‘Elena’)

Para borrar tuplas utilizamos la sentencia DELETE, estableciendo las


condiciones que deseemos:
DELETE FROM Clientes WHERE Nombre =’Jorge’ AND Apellido=’Tejedor’
DELETE FROM Clientes WHERE Edad >= 65
DELETE FROM Clientes WHERE Nombre NOT IN (‘Jorge’)

La modificación de datos se hace a través de la sentencia UPDATE,


indicando con SET las modificaciones deseadas:
UPDATE Clientes SET Edad=15 WHERE DNI=’5790’
UPDATE Clientes SET Nombre=’Ana’ WHERE Nombre=’Elena’

A continuación se explica la manera de realizar selecciones utilizando varias


tablas relacionadas en una base de datos.
126 © JESÚS BOBADILLA SANCHO

7.2.2 Trabajando con varias tablas

Con Sql podemos realizar consultas sobre varias tablas a la vez, para
unificar el contenido de las mismas; esto nos permite no repetir datos en varias
tablas. En nuestra base de datos no es necesario que en la tabla de pedidos esté el
apellido del cliente. Simplemente bastará con que esté el código del cliente. De este
modo, podremos encontrar el resto de los datos buscando en la tabla de clientes el
código correspondiente al pedido.

Pero trabajar con varias tablas nos origina un problema. ¿Cómo


diferenciamos atributos con el mismo nombre en las distintas tablas? La solución es
utilizar el nombre de la tabla antes de referenciar el campo. Por ejemplo, tenemos la
tabla Clientes y la tabla Pedidos y coinciden en el campo ‘DNI’: utilizaremos
Clientes.Codigo para referirnos a la tabla Clientes y Pedidos.Codigo para hacerlo a
la de Pedidos.

Antes de empezar a explicar cómo realizar accesos sobre varias tablas,


vamos a definir unas nuevas tablas que utilizaremos en los ejemplos de este
apartado. Tendremos tres tablas: la primera será la de Clientes, que contendrá los
campos Código (código de cliente), Nombre y Dirección. La tabla Productos
contendrá los datos de los productos que existen en una tienda, tendrá los campos
Referencia (referencia del producto), Nombre, Precio y Concepto (ej: kg, unidad,
par, etc...). Por últ imo, la tabla Pedidos contendrá los pedidos de cada producto
realizados por los clientes, tendrá los campos Código (código de cliente), Referencia
(referencia del producto) y Cantidad.

CLIENTES
Código Nombre Dirección
1 Luis Miguel Guzmán el Bueno, 90
2 Beatriz Zurriaga, 25
3 Jonás Federico Puertas, 3
4 Joaquín Vinateros, 121
5 Pedro Virgen del Cerro, 154
6 Sandra Pablo Neruda, 15
7 Miguel Armadores, 1
8 Sergio Avenida del Ejercito,
9 Alejandro Leonor de76Cortinas, 7
10 Álvaro Fuencarral, 33
11 Rocío Cervantes, 22
12 Joaquín Buenos Aires, 31
13 Jesús Gaztambique, 32
 JESÚS BOBADILLA SANCHO 127

PRODUCTOS
Referencia Nombre Precio Concepto
1 Patatas 200 Pta Kilo
2 Melones 500 Pta Kilo
3 Sandías 120 Pta Kilo
4 Zapatos 5.000 Pta Par
5 Chandal 12.000 Pta Unidad
6 Pantalones 2.000 Pta Unidad
7 Camisa 2.500 Pta Unidad
8 Corbata 950 Pta Unidad
9 Aceite 695 Pta Litro

PEDIDOS
Código Referencia Cantidad
1 1 5
9 2 10
4 5 6
12 8 1
7 2 9
8 3 7
7 9 3
2 7 1
6 4 3
3 1 4

Un ejemplo sencillo consiste en seleccionar a partir de la tabla de pedidos


una lista con las direcciones a las que hay que enviar cada pedido y el nombre del
destinatario. Utilizaremos el campo Código (código de cliente) para unir las tablas
de Pedidos y Clientes. De este modo, cuando leamos un registro de la tabla de
pedidos, buscaremos en Clientes el registro cuyo campo Código coincida con el de
Pedidos. Buscamos los pares de registros que coincidan por el campo Código, para
ello utilizamos la siguiente sentencia:
SELECT Nombre, Dirección, Referencia, Cantidad FROM Clientes, Pedidos
WHERE Clientes.Código = Pedidos.Código
128 © JESÚS BOBADILLA SANCHO

Seleccionamos los campos Nombre, Dirección, Referencia y Cantidad de


las tablas Clientes y Pedidos, donde el campo Código del registro de clientes sea
igual al campo Código del registro de Pedidos.

Adviértase cómo utilizamos el nombre de cada tabla para diferenciar el


campo Código de Clientes y Pedidos; sin embargo, no es necesario utilizarlo cuando
hacemos referencia a los campos Dirección, Nombre o Cantidad, ya que esos
campos no se repiten en las dos tablas.

Nombre Dirección Referencia Cantidad


Luis Miguel Guzmán el Bueno, 90 1 5
Alejandro Leonor de Cortinas, 7 2 10
Joaquín Vinateros, 121 5 6
Joaquín Buenos Aires, 31 8 1
Miguel Armadores, 1 2 9
Sergio Avenida del Ejercito, 76 3 7
Miguel Armadores, 1 9 3
Beatriz Zurriaga, 25 7 1
Sandra Pablo Neruda, 15 4 3
Jonás Federico Puertas, 3 1 4

La selección la hemos hecho sobre dos tablas, pero Sql nos permite utilizar
más tablas con los operadores booleanos. De este modo podremos seleccionar sobre
varias tablas utilizando distintas condiciones para unirlas entre ellas de dos en dos.
Por ejemplo, si además de los datos que hemos visto en el ejemplo anterior
queremos mostrar el nombre del producto que se pide, tendremos que utilizar una
tercera tabla, la de productos.

Ahora buscaremos tres registros, uno de cada tabla, en los que coincidan por
un lado el campo Código de las tablas Pedidos y Clientes, y por otro el campo
Referencia de las tablas Pedidos y Productos. Como se puede ver, la tabla Pedidos
nos sirve de enlace entre las tablas Productos y Clientes, que no tienen ningún
campo en común.

A continuación vemos el resultado de esta selección, que nos muestra los


mismos registros que en el ejemplo anterior, pero sustituyendo el campo Referencia
por el del nombre del producto. Utilizaremos la siguiente sentencia Sql:
SELECT Clientes.Nombre, Dirección, Productos.Nombre, Cantidad FROM
Clientes, Productos, Pedidos WHERE Clientes.Código = Pedidos.Código AND
Pedidos.Referencia = Productos.Referencia
 JESÚS BOBADILLA SANCHO 129

Nombre Dirección Producto Cantidad


Luis Miguel Guzmán el Bueno, 90 Patatas 5
Alejandro Leonor de Cortinas, 7 Melones 10
Joaquín Vinateros, 121 Chándal 6
Joaquín Buenos Aires, 31 Corbata 1
Miguel Armadores, 1 Melones 9
Sergio Avenida del Ejercito, 76 Sandías 7
Miguel Armadores, 1 Aceite 3
Beatriz Zurriaga, 25 Camisa 1
Sandra Pablo Neruda, 15 Zapatos 3
Jonás Federico Puertas, 3 Patatas 4

En este caso sí tenemos que utilizar el nombre de la tabla en el campo


Nombre, ya que lo tienen tanto la tabla Clientes como Productos, y no hacen
referencia a la misma cosa.

Utilizamos el operador booleano para que se cumplan las dos condiciones,


uniendo de este modo las tres tablas. Seleccionamos tres registros, uno de cada tabla.
Los registros de las tablas Clientes y Pedidos coinciden en el campo Código, y los
de las tablas Pedidos y Productos coinciden en el campo Referencia .

A continuación vamos a seleccionar los clientes que han comprado algo. No


es lo mismo que lo que hemos hecho en ejemplos anteriores, ya que en éstos
mostrábamos todas las coincidencias que había entre las dos tablas en el campo
Código; esto generaba la repetición de cada cliente tantas veces como pedidos tenía.

En este nuevo ejemplo no deseamos que existan repeticiones. Cada cliente


que haya realizado una compra sólo aparecerá una vez. De este modo tendremos la
lista de los clientes que han comprado algo, sin ninguna repetición. Para lograrlo
utilizaremos la cláusula DISTINCT. Esta cláusula hace que todos los registros
seleccionados sean distintos; de este modo, si aparece un cliente con dos pedidos,
solo se mostrará una vez, ya que los campos seleccionados (Nombre y Dirección),
coinciden en los dos pedidos.
SELECT DISTINCT Nombre, Dirección FROM Clientes, Pedidos WHERE
Clientes.Código = Pedidos.Código

Esta sentencia buscará las coincidencias de las tablas Pedidos y Clientes por
el campo Código. Cuando encuentre varias veces el mismo cliente lo mostrará solo
una vez.
130 © JESÚS BOBADILLA SANCHO

Una cosa importante que hay que tener en cuenta es que todos los campos
seleccionados deberán ser iguales para todos los registros que unamos. Es decir, si
tenemos en dos registros dos nombres de cliente iguales pero direcciones diferentes,
se mostrarán los dos registros. Hay que tener cuidado con los campos que
seleccionemos, para que aparezcan los registros que deseemos.

Si queremos hacer más restrictiva la selección y buscar solamente los


clientes que han comprado un producto, añadiremos una nueva condición a la
sentencia:
SELECT DISTINCT Nombre, Dirección FROM Clientes, Pedidos WHERE
Clientes.Código = Pedidos.Código AND Referencia = 5

Podemos añadir todas las condiciones que deseemos a través de los


operadores booleanos AND y OR. Estas condiciones podrán hacer referencia a
cualquiera de las tablas o, como en este ejemplo, mezclar campos de tablas.

Otra forma de seleccionar los clientes que han comprado un cierto producto
es a través del operador EXISTS, de la siguiente forma:
SELECT Nombre, Dirección FROM Clientes WHERE EXIST (SELECT Código
FROM Pedidos WHERE Clie ntes.Código=Pedidos.Código AND Referencia=2)

El operador EXISTS nos devolverá los registros que se encuentren dentro de


la primera selección.

Nombre Dirección
Alejandro Leonor de Cortinas, 7
Beatriz Zurriaga, 25
Joaquín Buenos Aires, 31
Joaquín Vinateros, 121
Jonás Federico Puertas, 3
Luis Miguel Guzmán el Bueno, 90
Miguel Armadores, 1
Sandra Pablo Neruda, 15
Sergio Avenida del Ejercito, 76

Seleccionar los clientes que todavía no han comprado: vamos a hacer ahora
lo contrario que en el ejemplo anterior. Vamos a buscar los elementos de una tabla
que no se encuentran en otra. Para esto utilizaremos el operador IN que habíamos
visto anteriormente, y más concretamente NOT IN.
 JESÚS BOBADILLA SANCHO 131

Buscamos los clientes cuyo código no se encuentre en la tabla Pedidos. Para


esto, primero necesitaremos saber qué códigos de cliente contiene la tabla de
pedidos. Como ya sabemos, lo conseguimos con la sentencia:
SELECT Código FROM Pedidos

Una vez que hemos conseguido los códigos, debemos comparar los registros
de la tabla Clientes, viendo en cada momento si el código de cada registro está o no
en la selección previa que hemos realizado. Es decir,
SELECT Nombre, Dirección FROM Clientes WHERE Código NOT IN
(Lista_de_códigos)

Unido con lo anterior, tenemos que la sentencia Sql que utilizaremos será la
siguiente:
SELECT Nombre, Dirección FROM Clientes WHERE Código NOT IN (SELECT
Código FROM Pedidos)

Nótese que en la selección que se hace sobre la tabla Pedidos sólo se emplea
el campo Código, ya que es el que queremos comparar.

Aplicando lo que acabamos de ver, podemos realizar la selección de los


clientes que han comprado alguna vez, utilizando el operador IN de la siguiente
forma:
SELECT Nombre, Dirección FROM Clientes WHERE Código IN (SELECT
Código FROM Pedidos)

Si quisiéramos seleccionar los clientes que han comprado un producto


concreto, simplemente tendríamos que hacer más restrictiva la selección sobre
Pedidos:
SELECT Nombre, Dirección FROM Clientes WHERE Código IN (SELECT
Código FROM Pedidos WHERE Referencia = 5)

Nombre Dirección
Pedro Virgen del Cerro, 154
Álvaro Fuencarral, 33
Rocío Cervantes, 22
Jesús Gaztambique, 32

Obtener el pedido con mayor número de unidades: vamos a ver cómo


utilizar los cuantificadores ALL y ANY. Éstos van siempre acompañados por un
132 © JESÚS BOBADILLA SANCHO

operador de comparación (<, >, =, <>, ...). Utilizaremos ALL y ANY para
seleccionar valores que sean mayor, menor, igual, etc.. que todos o alguno de los
valores de una selección previa.

Queremos seleccionar el pedido de mayor número de unidades, es decir, el


que tenga el campo Cantidad mayor. Para conseguirlo buscamos el registro cuyo
campo Cantidad sea mayor que el de todos los pedidos. Utilizamos, por tanto, el
cuantificador > ALL de la siguiente forma:
SELECT Nombre, Dirección, Referencia, Cantidad FROM Clientes, Pedidos
WHERE Cantidad >= ALL (SELECT Cantidad FROM Pedidos) AND
Clientes.Código=Pedidos.Código

Obsérvese que tenemos que utilizar también la condición de que coincidan


los códigos, ya que estamos utilizando dos tablas. Si no la utilizásemos, aparecerían
todos los registros de la tabla de clientes con el valor del mayor pedido.

De la misma forma que hemos empleado ALL, podemos usar ANY. Este
segundo cuantificador lo utilizaremos para seleccionar valores que se encuentren en
algún campo. Por ejemplo, otra forma de seleccionar los clientes que han comprado
algo sería la siguiente:
SELECT Nombre, Dirección FROM Clientes WHERE Código = ANY (SELECT
Código FROM Pedidos)

Así mismo, para seleccionar los clientes que no tienen ningún pedido
utilizaríamos:
SELECT Nombre, Dirección FROM Clientes WHERE Código <> ALL (SELECT
Código FROM Pedidos)

Los operadores de comparación no sólo sirven para comparar campos


numéricos, los podemos utilizar para comparar cualquier tipo de campo. Por
ejemplo, podemos utilizarlos para saber cuál es el nombre menor, alfabéticamente
hablando:
SELECT Nombre FROM Clientes WHERE Nombre <= ALL (SELECT Nombre
FROM Clientes)

Nombre Dirección Referencia Cantidad


Alejandro Leonor de Cortinas, 7 2 10
 JESÚS BOBADILLA SANCHO 133

7.3 ARQUITECTURA DE UNA APLICACIÓN


Una aplicación Java que realiza accesos a bases de datos funciona según una
arquitectura que permite escribir los programas abstrayéndose de los detalles de los
niveles inferiores (discos, drivers, sistema operativo, etc.).

En la siguiente figura se muestran los nive les más importantes y conocidos


del esquema de funcionamiento con bases de datos: en el nivel superior se
encuentran las aplicaciones que realizamos; estas aplicaciones son interpretadas por
la máquina virtual Java (JVM).

Aplicación

JVM

Sistema Operativo

ODBC

Oracle Access MSQL

El sistema operativo proporciona el nivel de entrada/salida, que interactúa


con los dispositivos físicos donde se encuentran las bases de datos (que pueden ser
de diferentes tipos). El sistema operativo también gestiona el nivel de ODBC (Open
Data Base Conectivity); ODBC nos permite utilizar un interfaz único para los
distintos tipos de bases de datos, además de proporcionar nombres lógicos que se
relacionan con los nombres físicos que tendrán los ficheros. En nuestros programas
utilizaremos siempre nombres lógicos, abstrayéndonos de esta manera de futuros
cambios en los niveles inferiores. En el siguiente apartado se muestra la manera de
configurar ODBC en Windows.

El siguiente gráfico incide en la arquitectura del sistema vista desde el nivel


de aplicación, usando Java. El “tubo” que se ha dibujado representa a la clase
Connection, que nos proporciona el “medio” para comunicarnos con las bases de
datos. Según cual sea el tipo de la base de datos, los drivers serán diferentes, por lo
que en primer lugar creamos un objeto de tipo DriverManager y, a través de él, el
objeto Connection.
134 © JESÚS BOBADILLA SANCHO

ResultSet
Statement

SELECT * FROM ...

ODBC

Oracle Access MSQL


Connection

DriverManager

Una vez que disponemos de la conexión adecuada, el funcionamiento es


muy simple: creamos una instancia de la clase Statement y la utilizamos para definir
las sentencias SQL adecuadas en nuestra aplicación. Estas sentencias SQL
proporcionarán, en general, una serie de datos provenientes de la base de datos, que
se almacenan en una instancia del objeto ResultSet.

Resumimos la forma de actuar:


• La programación se realiza siguiendo las líneas discontinuas del gráfico:
creación de un objeto de tipo DriverManager; a través de él se crea un
objeto de tipo Connection, que a su vez usamos para crear un objeto
Statement; finalmente, usando el objeto Statement obtenemos un objeto
ResultSet, donde se encuentran los resultados deseados.

• En tiempo de ejecución, la sentencia SQL se ejecuta “descendiendo” a


través del objeto Connection y el resultado “asciende” al nivel de aplicación,
almacenándose en un objeto de tipo ResultSet. Más adelante detallaremos la
manera de acceder a los datos (atributos) almacenados en el objeto
ResultSet.
 JESÚS BOBADILLA SANCHO 135

7.4 CONEXIÓN A UNA BASE DE DATOS


Para poder acceder desde el nivel de aplicación a una base de datos ya
existente, debemos administrar los orígenes de datos ODBC.

En este primer ejemplo vamos a utilizar una base de datos de tipo Access,
cuyo nombre físico es EjemploBD.mdb, y está situada en el directorio
C:\Jesus\ProyectosJava\Libro\BD. Hemos decidido asignarle el nombre lógico
NombreLogico, que aunque no identifica su función, tiene el interés didáctico de
remarcar en los programas el hecho de que no emplearemos nombres físicos en el
nivel de aplicación al trabajar con bases de datos.

Los dos siguientes gráficos muestran la configuración que hemos empleado


en ODBC. Si la base de datos se utiliza en un sistema multiusuario (como por
ejemplo en una aplicación web), debemos utilizar la solapa DSN de Sistema.
136 © JESÚS BOBADILLA SANCHO

A continuación se presenta la clase PruebaConexion, en la que


estableceremos una conexión a la base de datos que previamente hemos preparado
en el administrador de ODBC. También se crea el objeto Statement y el ResultSet,
aunque, en este primer ejemplo, no actuaremos sobre los atributos obtenidos en el
objeto ResultSet.

La clase PruebaConexion (línea 3) nos va a servir de plantilla para la


realización de las futuras aplicaciones Java con acceso a bases de datos. Las clases e
interfaces que utilizaremos se encuentran en el paquete java.sql (línea 1).

Primero establecemos el driver que vamos a utilizar, llamando al método


estático forName, perteneciente a la clase Class (línea 7). El driver que empleamos
es el ODBC que nos proporciona la biblioteca JDBC.

Para crear la conexión, en primer lugar establecemos la fuente de donde


provienen los datos (en nuestro caso la base de datos). Utilizaremos el nombre
lógico que habíamos preparado en el administrador de ODBC (línea 8).
Posteriormente, en las líneas 9 y 10, hacemos uso del método getConnection
perteneciente a la clase DriverManager, aplicado al nombre lógico de la fuente de
datos. Si se desea, se puede hacer uso del método getConnection de la clase
DataSource, en lugar del de la clase DriverManager.

Utilizando el método createStatement, perteneciente al objeto de tipo


Connection, creamos el objeto de tipo Statement (línea 11). Ya estamos en
condiciones de obtener los datos deseados de la base de datos: nos basta con crear un
objeto de tipo ResultSet (líneas 12 y 13) ejecutando el método executeQuery,
perteneciente al interfaz Statement. Como argumento del método executeQuery
podemos colocar la sentencia SELECT, de tipo SQL, que nos convenga. Obsérvese
que la sentencia SELECT que hemos utilizado recoge todos los campos de la tabla
DatosPersonales.

En los ejemplos del siguiente apartado veremos como extraer los datos
(atributos) del objeto Personas, de tipo ResultSet.

1 import java.sql.*;
2
3 public class PruebaConexion {
4
5 public static void main(String[] args){
6 try{
7 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
8 String BaseDeDatos = "jdbc:odbc:NombreLogico";
9 Connection Conexion =
10 DriverManager.getConnection(BaseDeDatos);
11 Statement SentenciaSQL = Conexion.createStatement();
 JESÚS BOBADILLA SANCHO 137

12 ResultSet Personas = SentenciaSQL.executeQuery("SELECT


13 * FROM DatosPersonales");
14
15 Personas.close();
16 Conexion.close();
17 SentenciaSQL.close();
18 }
19 catch (ClassNotFoundException e) {
20 System.out.println("Clase no encontrada");
21 }
22 catch (SQLException e) {
23 System.out.println(e);
24 }
25
26 System.out.println("Sin error al conectar");
27 }
28
29 }

En las líneas 15 a 17 se realiza una liberación explícita de los recursos


empleados, utilizando los métodos close que proporcionan los interfaces ResultSet,
Connection y Statement.

En las líneas 19 a 24 se recogen las excepciones que podrían levantarse


debido a las instanciaciones realizadas y a los métodos invocados.

7.5 OBTENCIÓN DE LOS DATOS CONTENIDOS EN UN


OBJETO DE TIPO RESULTSET
En este apartado se explica, en primer lugar, el significado y uso de la clase
ResultSet, incidiendo en los métodos que se utilizan habitualmente en las
aplicaciones Java con acceso a bases de datos; posteriormente se desarrolla un
ejemplo que completa a la clase PruebaConexion, proporcionando un listado de
todos los datos contenidos en una tabla.

Los ejemplos de este capítulo se van a basar en las posibilidades que


proporciona el interfaz ResultSet, empleando un modelo de funcionamiento muy
similar al del código proporcionado.
138 © JESÚS BOBADILLA SANCHO

7.5.1 El interfaz ResultSet

Los objetos ResultSet permiten recoger los resultados de la ejecución de


sentencias SQL; estos resultados proporcionan un número variable de columnas y de
filas. En definitiva, un ResultSet es un contenedor tabular de tamaño variable.

Cada objeto de tipo ResultSet contiene un cursor que inicialmente se


encuentra situado en la posición anterior a la primera fila de la tabla. Existe una serie
de métodos que nos permiten mover el cursor a lo largo de la tabla. La siguiente
figura muestra gráficamente dichos métodos:
beforeFirst()
DNI Nombre Apellido Edad
1 53023767A Pedro Rubio 18
first()
2 84604568K Ana Moreno 35 absolute(2)
previous()
82607936G Carmen Delgado 12
relative(-1)
Cursor 77459822P Luis Tejedor 47
2376856S Jorge Gil 21 last()
next()
afterLast()

Todos los métodos señalados devuelven un valor de tipo boolean, que nos
indica si el movimiento del cursor ha sido posible. Hay que tener en cuenta que, por
defecto, los objetos RecordSet únicamente pueden ser recorridos incrementalmente,
y que además no son actualizables (no se pueden modificar sus atributos). En los
siguientes apartados realizaremos una leve modificación a nuestros programas para
solventar estas restricciones.

Una vez situado el cursor en la posición deseada, disponemos de una gran


cantidad de métodos para conocer su posición (isXxxxx), consultar el valor de los
atributos (getXxxxx) o modificar los mismos (updateXxxxx):

DNI Nombre Apellido Edad


isBeforeFirst()
1 53023767A Pedro Rubio 18 isFirst()
2 84604568K Ana Moreno 35
82607936G Carmen Delgado 12
Cursor 77459822P Luis Tejedor 47
2376856S Jorge Gil 21
isLast()
isAfterLast()
getString(...) getInt(...)
updateString(...) updateInt(...)
 JESÚS BOBADILLA SANCHO 139

Tanto los métodos “update” como los métodos “get” están sobrecargados
para admitir dos tipos de argumentos: el nombre de la columna o la posición de la
columna; de esta manera disponemos, por ejemplo, de un método getInt(String
NombreColumna), y de otro método getInt(int IndiceColumna). Las columnas se
numeran empezando por 1:

En el RecordSet de ejemplo, los siguientes dos métodos producen el mismo


resultado (“Luis”): getString(“Nombre”) y getString(2).

Los métodos “get” se aplican a una gran variedad de tipos de datos: Array,
AsciiStream, BigDecimal, BinaryStream, Boolean, Byte, Bytes, Date, Double, Float,
Int, Long, Object, Ref, Short, String, Time, Timestamp, URL, etc. formando
getArray(...), getByte(...), etc. Los métodos “update” existen para la mayor parte de
estos tipos de datos.

7.5.2 Hola mundo

Vamos a completar el ejemplo anterior (PruebaConexion), para obtener los


listados deseados de los datos contenidos en el objeto Personas, de tipo ResultSet.
Partiremos de la tabla DatosPersonales, que contiene la siguiente información:

DNI Nombre Apellido Edad


53023767A Pedro Rubio 18
84604568K Ana Moreno 35
82607936G Carmen Delgado 12
77459822P Luis Tejedor 47
22376856S Jorge Gil 21

La clase Listado (línea 3) realiza una conexión con la base de datos


NombreLogico y extrae todos sus atributos, copiándolos en el objeto Personas
(líneas 9 a 15), tal y como se realizó en el ejemplo anterior.

Para iterar por todas las tuplas (filas) del RecordSet Personas, empleamos
un bucle while (línea 16) que finaliza cuando el método next devuelve el valor false,
indicando el final del RecordSet.

Por cada tupla se obtienen los atributos alfanuméricos de los campos DNI,
Nombre y Apellido (líneas 17 a 19), empleando el método getString. También se
obtiene el atributo del campo Edad, de tipo entero, por lo que se utiliza el método
getInt (línea 20).
140 © JESÚS BOBADILLA SANCHO

En las líneas 21 y 22 se imprime por consola los resultados obtenidos en la


tupla en la que se encuentra el puntero del objeto personas. La clase Listado se
puede considerar como el programa HolaMundo de las bases de datos.

1 import java.sql.*;
2
3 public class Listado {
4
5 public static void main(String[] args){
6 String Nombre,Apellido,DNI;
7 int Edad;
8 try{
9 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
10 String BaseDeDatos = "jdbc:odbc:NombreLogico";
11 Connection Conexion =
12 DriverManager.getConnection(BaseDeDatos);
13 Statement SentenciaSQL = Conexion.createStatement();
14 ResultSet Personas = SentenciaSQL.executeQuery("SELECT
15 * FROM DatosPersonales");
16 while (Personas.next()) {
17 DNI = Personas.getString("DNI");
18 Nombre = Personas.getString("Nombre");
19 Apellido = Personas.getString("Apellido");
20 Edad = Personas.getInt("Edad");
21 System.out.println(Nombre+" "+Apellido+", "+
22 Edad+", "+DNI);
23 }
24 Personas.close();
25 Conexion.close();
26 SentenciaSQL.close();
27 }
28 catch (ClassNotFoundException e) {
29 System.out.println("Clase no encontrada");
30 }
31 catch (SQLException e) {
32 System.out.println(e);
33 }
34 }
35
36 }
 JESÚS BOBADILLA SANCHO 141

Si cambiamos la sentencia SQL a: “SELECT * FROM DatosPersonales


WHERE Edad<23”, obtenemos el resultado:

Poniendo la siguiente sentencia SQL: “SELECT * FROM


DatosPersonales WHERE Edad<23 ORDER BY Apellido”, obtenemos el
resultado:

7.6 MODIFICACIÓN, INSERCIÓN Y BORRADO


Cuando deseamos recorrer un objeto de tipo ResultSet en orden no
ascendente o bien modificar el contenido del mismo, debemos proporcionar ciertos
argumentos al método createStatement, perteneciente al interfaz Connection.

El interfaz ResultSet proporciona, entre otros campos, los valores estáticos


de tipo entero: TYPE_FORWARD_ONLY, TYPE_SCROLL_INSENSITIVE,
TYPE_SCROLL_SENSITIVE, CONCUR_READ_ONLY y CONCUR_UPDATABLE.
Si deseamos poder mover el cursor hacia delante y hacia atrás, evitaremos el uso de
TYPE_FORWARD_ONLY. Si queremos poder modificar el RecordSet,
seleccionaremos CONCUR_UPDATABLE.

Entre las tres posibilidades que ofrece el método createStatement, se


encuentra la ya utilizada (sin parámetros) y la opción de dos parámetros enteros: el
primero para indicar el “TYPE_SCROLL” y el segundo para indicar el “CONCUR”.
A partir de ahora haremos uso del método createStatement de la siguiente manera:

Statement SentenciaSQL = Conexion.createStatement(


ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE);
142 © JESÚS BOBADILLA SANCHO

7.6.1 Modificación

La clase Modificacion (línea 3) sigue el esquema explicado para establecer


una conexión con la base de datos (líneas 9 a 17); en este caso con capacidad para
mover el cursor en cualquier dirección y para modificar los datos del RecordSet
(líneas 13 a 15).

En la línea 19 iteramos secuencialmente por el RecordSet Personas,


obteniendo los atributos del Nombre y Apellido en cada tupla (líneas 20 y 21). Si el
nombre y apellidos hallados en una iteración coincide con el de “Ana Moreno”,
modificamos este valor por: “47645876F, Luis, Reverte, 45”.

Obsérvese el uso que realizamos de los métodos updateString(...) y


updateInt(...) en las líneas 26 a 29, que debemos combinar con el método updateRo w
(línea 30), para que las modificaciones se realicen físicamente en la base de datos.

Como se avisa en las líneas 24 y 25, debemos obligatoriamente invocar a un


método “update” por cada método “get” que utilicemos; es decir, debemos actualizar
cada campo (columna) que consultemos.

1 import java.sql.*;
2
3 public class Modificacion {
4
5 public static void main(String[] args){
6 String Nombre,Apellido,DNI;
7 int Edad;
8 try{
9 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
10 String BaseDeDatos = "jdbc:odbc:NombreLogico";
11 Connection Conexion =
12 DriverManager.getConnection(BaseDeDatos);
13 Statement SentenciaSQL = Conexion.createStatement(
14 ResultSet.TYPE_SCROLL_INSENSITIVE,
15 ResultSet.CONCUR_UPDATABLE);
16 ResultSet Personas = SentenciaSQL.executeQuery("SELECT
17 * FROM DatosPersonales");
18
19 while (Personas.next()) {
20 Nombre = Personas.getString("Nombre");
21 Apellido = Personas.getString("Apellido");
22 if (Apellido.equalsIgnoreCase("Moreno")&&
23 Nombre.equalsIgnoreCase("Ana")){
24 // IMPORTANTE: por cada getXxxxx("Campo") hay que
25 // realizar un updateXxxxx("Campo")
26 Personas.updateString("Nombre","Luis");
 JESÚS BOBADILLA SANCHO 143

27 Personas.updateString("Apellido","Reverte");
28 Personas.updateInt("Edad",45);
29 Personas.updateString("DNI","47645876F");
30 Personas.updateRow();
31 System.out.println("Registro modificado");
32 break;
33 }
34 }
35
36 Personas.close();
37 Conexion.close();
38 SentenciaSQL.close();
39 }
40 catch (ClassNotFoundException e) {
41 System.out.println("Clase no encontrada");
42 }
43 catch (SQLException e) {
44 System.out.println(e);
45 }
46 }
47
48 }

La siguiente secuencia de comandos muestra el correcto funcionamiento de


nuestro programa:
144 © JESÚS BOBADILLA SANCHO

7.6.2 Inserción

En la clase Insercion (línea 3) se muestra de una manera muy sencilla la


forma de incluir un nuevo registro (tupla) en una base de datos. Las líneas 7 a 15 se
encargan de crear una conexión con capacidad para modificar la tabla
(“CONCUR_UPDATABLE”).

En la línea 17 nos movemos a la posición de inserción, utilizando el método


moveToInsertRow, que pertenece al interfaz ResultSet. La posición actual del cursor
se guarda automáticamente y puede ser restaurada utilizando el método
moveToCurrentRow (línea 23); la línea de inserción es un buffer asociado a un
ResultSet modificable.

Una vez situados en la posición de inserción, debemos definir el contenido


de todos los campos, utilizando los métodos “updateXxxx” requeridos (líneas 18 a
21). Finalmente, utilizamos el método insertRow (línea 22) para añadir físicamente
el registro en el RecordSet y en la base de datos.

Obsérvese como en la línea 19 se utiliza como argumento el número del


campo en el RecordSet, en lugar del nombre del mismo. Podemos emplear ambas
posibilidades según nos convenga, atendiendo a las características de la aplicación.

En todo el proceso de inserción es necesario cumplir una serie de


restricciones:
• Cuando nos encontramos en la posición de inserción (tras ejecutar el método
moveToInsertRow), solo podemos utilizar los métodos “getXxxx”,
“updateXxxx” e insertRow.
• Todos los campos deben tener un valor antes de utilizar el método
insertRow.
• En cada campo, debe utilizarse un método “updateXxxx” antes de poder
hacer uso del correspondiente “getXxxxx”.

1 import java.sql.*;
2
3 public class Insercion {
4
5 public static void main(String[] args){
6 try{
7 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
8 String BaseDeDatos = "jdbc:odbc:NombreLogico";
9 Connection Conexion =
10 DriverManager.getConnection(BaseDeDatos);
11 Statement SentenciaSQL = Conexion.createStatement(
 JESÚS BOBADILLA SANCHO 145

12 ResultSet.TYPE_SCROLL_INSENSITIVE,
13 ResultSet.CONCUR_UPDATABLE);
14 ResultSet Personas = SentenciaSQL.executeQuery("SELECT
15 * FROM DatosPersonales");
16
17 Personas.moveToInsertRow();
18 Personas.updateString("DNI","50839979M");
19 Personas.updateString(2,"Pedro");
20 Personas.updateString("Apellido","Cela");
21 Personas.updateInt("Edad",78);
22 Personas.insertRow();
23 // Personas.moveToCurrentRow();
24
25 Personas.close();
26 Conexion.close();
27 SentenciaSQL.close();
28 }
29 catch (ClassNotFoundException e) {
30 System.out.println("Clase no encontrada");
31 }
32 catch (SQLException e) {
33 System.out.println(e);
34 }
35 }
36
37 }

En la siguiente secuencia de ejecuciones se puede observar la inserción del


registro “Pedro Cela 78 50839979M”, tal y como se ha definido en las líneas 18 a
21).
146 © JESÚS BOBADILLA SANCHO

7.6.3 Borrado

En el siguiente ejemplo buscamos a través de la tabla DatosPersonales el


registro que hemos añadido en el apartado anterior (“Pedro Cela”), para borrarlo.

La creación de los objetos Connection, Statement y ResultSet se realiza de la


manera habitual (líneas 8 a 16). La búsqueda del registro emplea estructuras ya
utilizadas: bucle en la línea 18, obtención de atributos en las líneas 19 y 20, y
comparación con los datos buscados en las líneas 21 y 22.

La manera de borrar un registro en una tabla es muy simple: en primer lugar


situamos el cursor en el registro y luego utilizamos el método deleteRow (línea 24).
El método deleteRow borra el registro en el recordSet y en la base de datos; no
podemos aplicar este método cuando el cursor está situado en la posición de
inserción.

1 import java.sql.*;
2
3 public class Borrado {
4
5 public static void main(String[] args){
6 String Nombre,Apellido;
7 try{
8 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
9 String BaseDeDatos = "jdbc:odbc:NombreLogico";
10 Connection Conexion =
11 DriverManager.getConnection(BaseDeDatos);
12 Statement SentenciaSQL = Conexion.createStatement(
13 ResultSet.TYPE_SCROLL_INSENSITIVE,
14 ResultSet.CONCUR_UPDATABLE);
15 ResultSet Personas = SentenciaSQL.executeQuery("SELECT
16 * FROM DatosPersonales");
17
18 while (Personas.next()) {
19 Nombre = Personas.getString("Nombre");
20 Apellido = Personas.getString("Apellido");
21 if (Apellido.equalsIgnoreCase("Cela")&&
22 Nombre.equalsIgnoreCase("Pedro")){
23 System.out.println("Registro eliminado");
24 Personas.deleteRow();
25 break;
26 }
27 }
28 Personas.close();
29 Conexion.close();
 JESÚS BOBADILLA SANCHO 147

30 SentenciaSQL.close();
31 }
32 catch (ClassNotFoundException e) {
33 System.out.println("Clase no encontrada");
34 }
35 catch (SQLException e) {
36 System.out.println(e);
37 }
38 }
39
40 }

7.7 APLICACIÓN DE MANTENIMIENTO DE LOS DATOS


DE UNA TABLA
7.7.1 Arquitectura de la aplicación

Basándonos en los ejemplos explicados anteriormente, vamos a desarrollar


una aplicación que nos permita realizar un mantenimiento (altas, bajas,
modificaciones, consulta y listado) de los datos contenidos en la tabla
DatosPersonales.
148 © JESÚS BOBADILLA SANCHO

Las clases que hemos desarrollado en los apartados precedentes van a


convertirse en objetos reutilizables (que denominaremos “ObXxxxx””). El código de
estos objetos se mantendrá prácticamente igual que el de las clases originales, lo que
permitirá que este ejemplo se entienda con mucha facilidad.

El mantenimiento de la tabla se realizará a través de un interfaz gráfico de


usuario (GUI) que implementamos en la clase GUIAccesoBD y que ponemos en
ejecución gracias al método main de la clase PruebaAccesoBD.

Resulta muy adecuado crear una clase que encapsule los campos de la tabla,
con el objetivo de que en cada instancia de esa clase se pueda definir una fila
(registro) del RecordSet. La clase del ejemplo que realiza esta función es
ObRegistro.

PruebaAccesoBD

GUIAccesoBD

ObListado ObConsulta

ObBorrado ObInserccion

ObModificacion

ObPosicionamiento

ObRegistro

7.7.2 Abstracción de datos

El encapsulamiento de los datos lo realiza la clase ObRegistro. Los objetos


de tipo ObRegistro contienen las propiedades DNI, Nombre, Apellido y Edad (líneas
2 y 3), que coinciden con los campos de la tabla DatosPersonales.
 JESÚS BOBADILLA SANCHO 149

Para asignar valores a las propiedades disponemos del constructor situado en


la línea 5. Los datos se pueden obtener individualmente gracias a los métodos de
acceso (“DameXxxx”) definidos en las líneas 13, 17, 21 y 25.

1 public class ObRegistro {


2 private String DNI, Nombre, Apellido;
3 private int Edad;
4
5 ObRegistro (String DNI, String Nombre, String Apellido,
6 int Edad) {
7 this.DNI = DNI;
8 this.Nombre = Nombre;
9 this.Apellido = Apellido;
10 this.Edad = Edad;
11 }
12
13 String DameDNI() {
14 return DNI;
15 }
16
17 String DameNombre() {
18 return Nombre;
19 }
20
21 String DameApellido() {
22 return Apellido;
23 }
24
25 int DameEdad() {
26 return Edad;
27 }
28
29 }

7.7.3 Inserción

La clase ObInserccion nos proporciona un buen ejemplo de la conveniencia


de abstraer los datos en un objeto, así como de la facilidad con la que se pueden
entender los objetos de este apartado una vez que hemos asimilado los de los
apartados anteriores.
150 © JESÚS BOBADILLA SANCHO

La inserción de datos utilizando esta clase se consigue al crear instancias de


la misma. El constructor situado en la línea 5 recoge un objeto de tipo ObRegistro e
introduce sus datos individua les en los campos de la tabla (líneas 18 a 22).

Entre la llamada al método moveToInsertRow (línea 17) y la llamada al


método insertRow (línea 23), utilizamos los métodos “updateXxxx” adecuados,
proporcionando los datos individuales contenidos en el objeto Datos de tipo
ObRegistro (línea 5).

El resto del código ha sido tomado, sin ninguna modificación, de la clase


Insercion, explicada en apartados anteriores.

1 import java.sql.*;
2
3 public class ObInserccion {
4
5 ObInserccion(ObRegistro Datos){
6 try{
7 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
8 String BaseDeDatos = "jdbc:odbc:NombreLogico";
9 Connection Conexion =
10 DriverManager.getConnection(BaseDeDatos);
11 Statement SentenciaSQL = Conexion.createStatement(
12 ResultSet.TYPE_SCROLL_INSENSITIVE,
13 ResultSet.CONCUR_UPDATABLE);
14 ResultSet Personas = SentenciaSQL.executeQuery("SELECT
15 * FROM DatosPersonales");
16
17 Personas.moveToInsertRow();
18 Personas.updateString("DNI", Datos.DameDNI());
19 Personas.updateString("Nombre",Datos.DameNombre());
20 Personas.updateString("Apellido",
21 Datos.DameApellido());
22 Personas.updateInt("Edad",Datos.DameEdad());
23 Personas.insertRow();
24
25 Personas.close();
26 Conexion.close();
27 SentenciaSQL.close();
28 }
29 catch (ClassNotFoundException e) {
30 System.out.println("Clase no encontrada");
31 }
32 catch (SQLException e) {
33 System.out.println(e);
34 }
35 } }
 JESÚS BOBADILLA SANCHO 151

7.7.4 Consulta

En esta aplicación, los datos se podrán consultar proporcionando el DNI de


la persona; de esta manera, el constructor de la clase ObConsulta (línea 8) admite un
parámetro de tipo String a través del que se proporcionan argumentos con
identificadores de DNI.

Cuando se crea una instancia de la clase ObConsulta, el valor del DNI


suministrado (DNIPedido) se compara con el DNI obtenido en cada registro de la
tabla (líneas 18 a 20); en caso de encontrarse una coincidencia se obtiene la posición
del cursor, empleando el método getRow (línea 21), y se guarda en la propiedad
privada Posicion (línea 5).

En las líneas 22 a 24 se obtienen los atributos del registro hallado, y en las


líneas 25 y 26 se insertan estos valores en el objeto DatosPersona, de tipo
ObRegistro (línea 6).

Los métodos públicos PosicionEncontrada (línea 43) y DameDatos (línea


47) nos permiten acceder, desde el exterior, a las propiedades privadas Posicion y
DatosPersona. De esta manera, tras una consulta, podemos obtener los datos
deseados, además de la posición en la que se encuentran, con el fin de visualizarlos,
borrarlos o modificarlos.

1 import java.sql.*;
2
3 public class ObConsulta {
4
5 private int Posicion = 0;
6 private ObRegistro DatosPersona;
7
8 ObConsulta(String DNIPedido){
9 try{
10 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
11 String BaseDeDatos = "jdbc:odbc:NombreLogico";
12 Connection Conexion =
13 DriverManager.getConnection(BaseDeDatos);
14 Statement SentenciaSQL = Conexion.createStatement();
15 ResultSet Personas = SentenciaSQL.executeQuery("SELECT
16 * FROM DatosPersonales");
17
18 while (Personas.next()) {
19 String DNI = Personas.getString("DNI");
20 if (DNI.equalsIgnoreCase(DNIPedido)){
21 Posicion = Personas.getRow();
22 String Nombre = Personas.getString("Nombre");
152 © JESÚS BOBADILLA SANCHO

23 String Apellido = Personas.getString("Apellido");


24 int Edad = Personas.getInt("Edad");
25 DatosPersona = new
26 ObRegistro(DNI,Nombre,Apellido,Edad);
27 break;
28 }
29
30 }
31 Personas.close();
32 Conexion.close();
33 SentenciaSQL.close();
34 }
35 catch (ClassNotFoundException e) {
36 System.out.println("Clase no encontrada");
37 }
38 catch (SQLException e) {
39 System.out.println(e);
40 }
41 }
42
43 public int PosicionEncontrada() {
44 return Posicion;
45 }
46
47 public ObRegistro DameDatos() {
48 return DatosPersona;
49 }
50
51 }

7.7.5 Modificación

La modificación de datos se realiza a través de la clase ObModificacion


(línea 3). Al crear una instancia de la clase (línea 5) se debe proporcionar la posición
del registro que deseamos variar y los nuevos valores que pretendemos incluir.

Conociendo la posición del registro a modificar (PosicionBuscada) resulta


inmediato situar el cursor en el mismo; basta con hacer uso del método absolute
(línea 17). Posteriormente actualizamos cada campo, obteniendo los nuevos valores
del parámetro Datos (líneas 18 a 21). Finalmente forzamos la escritura en la base de
datos, empleando el método updateRow (línea 22).
 JESÚS BOBADILLA SANCHO 153

1 import java.sql.*;
2
3 public class ObModificacion {
4
5 ObModificacion(int PosicionBuscada, ObRegistro Datos) {
6 try{
7 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
8 String BaseDeDatos = "jdbc:odbc:NombreLogico";
9 Connection Conexion =
10 DriverManager.getConnection(BaseDeDatos);
11 Statement SentenciaSQL = Conexion.createStatement(
12 ResultSet.TYPE_SCROLL_INSENSITIVE,
13 ResultSet.CONCUR_UPDATABLE);
14 ResultSet Personas = SentenciaSQL.executeQuery("SELECT
15 * FROM DatosPersonales");
16
17 Personas.absolute(PosicionBuscada);
18 Personas.updateString("DNI",Datos.DameDNI());
19 Personas.updateString("Nombre",Datos.DameNombre());
20 Personas.updateString("Apellido",Datos.DameApellido());
21 Personas.updateInt("Edad",Datos.DameEdad());
22 Personas.updateRow();
23
24 Personas.close();
25 Conexion.close();
26 SentenciaSQL.close();
27 }
28 catch (ClassNotFoundException e) {
29 System.out.println("Clase no encontrada");
30 }
31 catch (SQLException e) {
32 System.out.println(e);
33 }
34 }
35
36 }
154 © JESÚS BOBADILLA SANCHO

7.7.6 Borrado

La clase encargada de borrar los datos (obBorrado) resulta realmente


sencilla: su único constructor (línea 5) admite un parámetro que indica la posición de
la tabla que se desea borrar (PosicionBuscada). En la línea 17 nos situamos en la
posición deseada, empleando el método absolute; en la línea 18 borramos el registro
en el RecordSet y en la base de datos, usando el método deleteRow.

1 import java.sql.*;
2
3 public class ObBorrado {
4
5 ObBorrado(int PosicionBuscada){
6 try{
7 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
8 String BaseDeDatos = "jdbc:odbc:NombreLogico";
9 Connection Conexion =
10 DriverManager.getConnection(BaseDeDatos);
11 Statement SentenciaSQL = Conexion.createStatement(
12 ResultSet.TYPE_SCROLL_INSENSITIVE,
13 ResultSet.CONCUR_UPDATABLE);
14 ResultSet Personas = SentenciaSQL.executeQuery("SELECT
15 * FROM DatosPersonales");
16
17 Personas.absolute(PosicionBuscada);
18 Personas.deleteRow();
19
20 Personas.close();
21 Conexion.close();
22 SentenciaSQL.close();
23 }
24 catch (ClassNotFoundException e) {
25 System.out.println("Clase no encontrada");
26 }
27 catch (SQLException e) {
28 System.out.println(e);
29 }
30 }
31
32 }
 JESÚS BOBADILLA SANCHO 155

7.7.7 Listado

La clase ObListado proviene de la clase Listado; se proporciona un


constructor (línea 6) que admite un componente de tipo TextArea, en el que se
escribirá el contenido de la tabla.

Antes de recorrer la tabla borramos la información existente en el objeto


AreaDeListado (línea 17). En cada iteración del bucle (línea 19) obtenemos los
valores de los campos (líneas 20 a 23), la posición del registro (línea 24) utilizando
el método getRow, y por fin añadimos estos datos al área de texto (líneas 27 y 28),
haciendo uso del método append sobre el componente AreaDeListado.

1 import java.sql.*;
2 import java.awt.TextArea;
3
4 public class ObListado {
5
6 ObListado(TextArea AreaDeListado){
7 String DNI, Nombre, Apellido, Linea;
8 int Edad, Posicion;
9 try{
10 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
11 String BaseDeDatos = "jdbc:odbc:NombreLogico";
12 Connection Conexion =
13 DriverManager.getConnection(BaseDeDatos);
14 Statement SentenciaSQL = Conexion.createStatement();
15 ResultSet Personas = SentenciaSQL.executeQuery("SELECT
16 * FROM DatosPersonales");
17 AreaDeListado.setText("");
18
19 while (Personas.next()) {
20 DNI = Personas.getString("DNI");
21 Nombre = Personas.getString("Nombre");
22 Apellido = Personas.getString("Apellido");
23 Edad = Personas.getInt("Edad");
24 Posicion = Personas.getRow();
25 Linea = DNI+", "+Nombre+" "+Apellido+",
26 "+String.valueOf(Edad);
27 AreaDeListado.append(String.valueOf(Posicion)+":
28 "+Linea+"\n");
29 }
30
31 Personas.close();
32 Conexion.close();
33 SentenciaSQL.close();
34 }
35 catch (ClassNotFoundException e) {
156 © JESÚS BOBADILLA SANCHO

36 System.out.println("Clase no encontrada");
37 }
38 catch (SQLException e) {
39 System.out.println(e);
40 }
41 }
42
43 }

7.7.8 Posicionamiento

En buena parte de las clases anteriores hemos trabajado con la posición del
cursor en la tabla. La clase ObPosicionamiento nos permite modificar dicho cursor a
partir de la posición actual (que suministramos como parámetro en el constructor).
Hay que tener en cuenta que en todas las clases suministradas, la base de datos se
cierra después de realizar las acciones pertinentes (inserción, modificación, ...), por
lo que la posición del cursor de la tabla se pierde; esta es la razón por la que
debemos guardar esta posición en un ámbito superior y pasarla a la clase a través del
parámetro Posicion del constructor (línea 8).

Las posibilidades de movimiento del cursor programadas son:

Parámetro Movimiento en la Método Línea de


Accion tabla código
0 Última posición last() 29
1 Primera posición first() 32
2 5 posiciones menos relative(-5) 35
3 Anterior previous() 38
4 Siguiente next() 41
5 5 posiciones más relative(+5) 44

Los métodos de posicionamiento del cursor devuelven un valor lógico,


indicando si la acción ha podido realizarse (por ejemplo, el método next devolverá
false si ya nos encontramos en la última posición de la tabla). Este valor lo
almacenamos en la variable PosicionCorrecta (línea 11) y lo utilizamos (línea 47)
para actualizar la propiedad global Posicion con la nueva situación del cursor (línea
48) o con la posición original del cursor (línea 54), además de proporcionar los datos
hallados (líneas 49 a 52) o una indicación de que la operación fue fallida (líneas 55 a
58).

Los métodos Posicion (línea 74) y DameDatos (línea 78) proporcionan la


información que se consigue tras un posicionamiento (fallido o no).
 JESÚS BOBADILLA SANCHO 157

1 import java.sql.*;
2
3 public class ObPosicionamiento {
4
5 private ObRegistro DatosPersona;
6 private int Posicion;
7
8 ObPosicionamiento(String Accion, int Posicion) {
9 int IDAccion,Edad;
10 String DNI,Nombre,Apellido;
11 boolean PosicionCorrecta=false;
12
13 try{
14 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
15 String BaseDeDatos = "jdbc:odbc:NombreLogico";
16 Connection Conexion =
17 DriverManager.getConnection(BaseDeDatos);
18 Statement SentenciaSQL = Conexion.createStatement(
19 ResultSet.TYPE_SCROLL_INSENSITIVE,
20 ResultSet.CONCUR_UPDATABLE);
21 ResultSet Personas = SentenciaSQL.executeQuery("SELECT
22 * FROM DatosPersonales");
23
24 Personas.absolute(Posicion);
25
26 IDAccion = Integer.parseInt(Accion);
27 switch (IDAccion) {
28 case 0: //
29 PosicionCorrecta = Personas.last();
30 break;
31 case 1:
32 PosicionCorrecta = Personas.first();
33 break;
34 case 2:
35 PosicionCorrecta = Personas.relative(-5);
36 break;
37 case 3:
38 PosicionCorrecta = Personas.previous();
39 break;
40 case 4:
41 PosicionCorrecta = Personas.next();
42 break;
43 case 5:
44 PosicionCorrecta = Personas.relative(+5);
45 break;
46 }
47 if (PosicionCorrecta) {
48 this.Posicion = Personas.getRow();
158 © JESÚS BOBADILLA SANCHO

49 DNI = Personas.getString("DNI");
50 Nombre = Personas.getString("Nombre");
51 Apellido = Personas.getString("Apellido");
52 Edad = Personas.getInt("Edad");
53 } else {
54 this.Posicion = Posicion;
55 DNI = "----";
56 Nombre = "----";
57 Apellido = "----";
58 Edad = 0;
59 }
60 DatosPersona = new ObRegistro(DNI,Nombre,Apellido,Edad);
61
62 Personas.close();
63 Conexion.close();
64 SentenciaSQL.close();
65 }
66 catch (ClassNotFoundException e) {
67 System.out.println("Clase no encontrada");
68 }
69 catch (SQLException e) {
70 System.out.println(e);
71 }
72 }
73
74 public int Posicion() {
75 return Posicion;
76 }
77
78 public ObRegistro DameDatos() {
79 return DatosPersona;
80 }
81
82 }

7.7.9 Interfaz gráfico de usuario (GUI)

Las clases que hemos desarrollado nos pueden servir para diseñar diferentes
aplicaciones de acceso a la tabla DatosPersonales. Como ejemplo de aplicación que
hace uso de las mismas, se proporciona la clase GUIAccesoBD, que permite a un
usuario realizar consultas, inserciones, borrados, modificaciones y recorridos en
nuestra base de datos.
 JESÚS BOBADILLA SANCHO 159

La estructura interna de la clase es la siguiente:

import java.awt.*;
import java.awt.event.*;

public class GUIAccesoBD {


// Propiedades privadas

GUIAccesoBD() {
// Definicion del interfaz grafico de usuario
// Establecimiento de los “Listeners”
}

private class ConsultarDatos implements ActionListener {


// Utiliza el objeto ObConsulta
}

private class InsertarDatos implements ActionListener {


// Utiliza el objeto ObInserccion
}

private class ModificarDatos implements ActionListener {


// Utiliza el objeto ObModificacion
}

private class BorrarDatos implements ActionListener {


// Utiliza el objeto ObBorrado
}

private class ListarDatos implements ActionListener {


// Utiliza el objeto ObListado
}

private class Controles implements ActionListener {


// Utiliza el objeto ObPosicionamiento
}
}

Vamos a analizar por separado cada uno de los elementos de la clase


GUIAccesoBD:
160 © JESÚS BOBADILLA SANCHO

7.7.9.1 Constructor GUIAccesoBD

El constructor de esta clase se encarga de definir todo el interfaz gráfico de


usuario que utiliza la aplicación. El resultado es el siguiente:

En la siguiente porción de código se muestran las propiedades de la clase


(líneas 1 a 8) y el contenido del constructor (línea 10). A partir de la línea 84 se
añaden los ‘ActionListeners’, que responden a las pulsaciones del usuario en los
botones del GUI.

El método DamePanel (línea 99) proporciona el panel que contiene el GUI,


para que pueda ser insertado modularmente en aplicaciones con un interfaz gráfico
de usuario más completo.

1 private TextField CampoDNI;


2 private TextField CampoNombre;
3 private TextField CampoApellido;
4 private TextField CampoEdad;
5 private TextArea AreaDeListado;
6 private Panel MiPanel;
7
8 private int PosicionBuscada = 1;
9
10 GUIAccesoBD() {
11 MiPanel = new Panel(new GridLayout(1,2));
12 Panel PanelIzq = new Panel(new GridLayout(6,1));
13 Panel PanelDNI = new
14 Panel(new FlowLayout(FlowLayout.LEFT));
15 Panel PanelNombre = new
16 Panel(new FlowLayout(FlowLayout.LEFT));
17 Panel PanelApellido = new
18 Panel(new FlowLayout(FlowLayout.LEFT));
19 Panel PanelEdad = new
20 Panel(new FlowLayout(FlowLayout.LEFT));
21 Panel PanelAccion = new
22 Panel(new FlowLayout(FlowLayout.CENTER));
23 Panel PanelControles = new
 JESÚS BOBADILLA SANCHO 161

24 Panel(new FlowLayout(FlowLayout.CENTER));
25
26 Label EtiquetaDNI = new Label("DNI");
27 Label EtiquetaNombre = new Label("Nombre");
28 Label EtiquetaApellido = new Label("Apellido");
29 Label EtiquetaEdad = new Label("Edad");
30
31 CampoDNI = new TextField(9);
32 CampoNombre = new TextField(15);
33 CampoApellido = new TextField(30);
34 CampoEdad = new TextField(3);
35
36 AreaDeListado = new TextArea();
37
38 Button BotonConsultar = new Button("Consultar");
39 Button BotonInsertar = new Button("Insertar");
40 Button BotonModificar = new Button("Modificar");
41 Button BotonBorrar = new Button("Borrar");
42 Button BotonListar = new Button("Listar");
43
44 Button BotonPrimero = new Button("|<");
45 BotonPrimero.setName("1");
46 Button BotonMenosCinco = new Button("--");
47 BotonMenosCinco.setName("2");
48 Button BotonAnterior = new Button("-");
49 BotonAnterior.setName("3");
50 Button BotonSiguiente = new Button("+");
51 BotonSiguiente.setName("4");
52 Button BotonMasCinco = new Button("++");
53 BotonMasCinco.setName("5");
54 Button BotonUltimo = new Button(">|");
55 BotonUltimo.setName("0");
56
57 PanelIzq .add(PanelDNI);
58 PanelIzq .add(PanelNombre);
59 PanelIzq .add(PanelApellido);
60 PanelIzq .add(PanelEdad);
61 PanelIzq .add(PanelAccion);
62 PanelIzq .add(PanelControles);
63 MiPanel.add(PanelIzq);
64 MiPanel.add(AreaDeListado);
65
66 PanelDNI.add (EtiquetaDNI); PanelDNI.add(CampoDNI);
67 PanelNombre.add (EtiquetaNombre);
68 PanelNombre.add(CampoNombre);
69 PanelApellido.add (EtiquetaApellido);
70 PanelApellido.add(CampoApellido);
71 PanelEdad.add (EtiquetaEdad); PanelEdad.add(CampoEdad);
162 © JESÚS BOBADILLA SANCHO

72 PanelAccion.add(BotonConsultar);
73 PanelAccion.add(BotonInsertar);
74 PanelAccion.add(BotonModificar);
75 PanelAccion.add(BotonBorrar);
76 PanelAccion.add(BotonListar);
77 PanelControles.add(BotonPrimero);
78 PanelControles.add(BotonMenosCinco);
79 PanelControles.add(BotonAnterior);
80 PanelControles.add(BotonSiguiente);
81 PanelControles.add(BotonMasCinco);
82 PanelControles.add(BotonUltimo);
83
84 BotonConsultar.addActionListener(new ConsultarDatos());
85 BotonInsertar.addActionListener(new InsertarDatos());
86 BotonModificar.addActionListener(new ModificarDatos());
87 BotonBorrar.addActionListener(new BorrarDatos());
88 BotonListar.addActionListener(new ListarDatos());
89
90 BotonPrimero.addActionListener(new Controles());
91 BotonMenosCinco.addActionListener(new Controles());
92 BotonAnterior.addActionListener(new Controles());
93 BotonSiguiente.addActionListener(new Controles());
94 BotonMasCinco.addActionListener(new Controles());
95 BotonUltimo.addActionListener(new Controles());
96 }
97
98
99 public Panel DamePanel() {
100 return MiPanel;
101 }

7.7.9.2 Listener ConsultarDatos

El método actionPerformed de la clase ConsultarDatos se encarga de


obtener los datos de una persona que se identifica a través de su DNI. En las líneas
104 y 105 se crea un objeto de tipo ObConsulta, pasándole el DNI proporcionado
por el usuario.

Utilizamos el método PosicionEncontrada, perteneciente a la clase


ObConsulta (líneas 106 y 107). Si se ha encontrado el registro (línea 109),
obtenemos sus datos (líneas 110 y 111) y los mostramos en los campos del GUI
(líneas 112 a 114); si no se ha encontrado el registro, preparamos unos contenidos
que muestran esta situación (líneas 116 a 118).
 JESÚS BOBADILLA SANCHO 163

102 private class ConsultarDatos implements ActionListener {


103 public void actionPerformed(ActionEvent Evento) {
104 ObConsulta InstanciaConsulta = new
105 ObConsulta(CampoDNI.getText());
106 PosicionBuscada =
107 InstanciaConsulta.PosicionEncontrada();
108
109 if (PosicionBuscada!=0) {
110 ObRegistro InstanciaFila =
111 InstanciaConsulta.DameDatos();
112 CampoDNI.setText(InstanciaFila.DameDNI());
113 CampoNombre.setText(InstanciaFila.DameNombre());
114 CampoApellido.setText(InstanciaFila.DameApellido());
CampoEdad.setText(String.
valueOf(InstanciaFila.DameEdad()));
115 } else {
116 CampoNombre.setText("------");
117 CampoApellido.setText("------");
118 CampoEdad.setText("---");
119 }
120 }
121 }

7.7.9.3 Listener InsertarDatos

Para insertar un registro, en primer lugar creamos un objeto de tipo


ObRegistro con los datos proporcionados por el usuario (líneas 124 y 125);
posteriormente se crea una instancia del objeto ObInserccion, pasándole como
argumento el registro de tipo ObRegisto (líneas 127 y 128).

Después de la inserción vaciamos los campos del GUI, con la intención de


facilitar al usuario la introducción de nuevos datos (líneas 129 y 130).
164 © JESÚS BOBADILLA SANCHO

122 private class InsertarDatos implements ActionListener {


123 public void actionPerformed(ActionEvent Evento) {
124 ObRegistro InstanciaFila = new
ObRegistro(CampoDNI.getText(),CampoNombre.getText(),
125 CampoApellido.getText(),
Integer.parseInt(CampoEdad.getText()));
126
127 ObInserccion InstanciaInserccion = new
128 ObInserccion(InstanciaFila);
129 CampoDNI.setText(""); CampoNombre.setText("");
130 CampoApellido.setText(""); CampoEdad.setText("");
131 PosicionBuscada=1;
132 }
133 }

7.7.9.4 Listener ModificarDatos

La modificación de datos comienza con una consulta (líneas 136 a 139),


proporcionando el DNI; después preguntamos si se ha encontrado el registro (línea
141). En caso afirmativo, se crea un registro con los nuevos datos (líneas 142 a 144)
y, posteriormente, se realiza la modificación empleando este registro como
argumento (líneas 146 y 147).

134 private class ModificarDatos implements ActionListener {


135 public void actionPerformed(ActionEvent Evento) {
136 ObConsulta InstanciaConsulta = new
137 ObConsulta(CampoDNI.getText());
138 PosicionBuscada =
139 InstanciaConsulta.PosicionEncontrada();
140
141 if (PosicionBuscada!=0) {
142 ObRegistro InstanciaFila = new
143 ObRegistro(CampoDNI.getText(),
144 CampoNombre.getText(), CampoApellido.getText(),
 JESÚS BOBADILLA SANCHO 165

Integer.parseInt(CampoEdad.getText()));
145
146 ObModificacion InstanciaModificacion = new
147 ObModificacion(PosicionBuscada, InstanciaFila);
148 CampoDNI.setText(""); CampoNombre.setText("");
149 CampoApellido.setText(""); CampoEdad.setText("");
150 } else {
151 CampoNombre.setText("------");
152 CampoApellido.setText("------");
153 CampoEdad.setText("---");
154 }
155 }
156 }

7.7.9.5 Listener BorrarDatos

La clase BorrarDatos es muy similar, en programación, a la clase


ModificarDatos. En ambas se realiza primero una consulta (líneas 159 y 160), y tras
asegurarse de que hemos encontrado el registro a borrar (líneas 161 a 164), se crea
una instancia de la clase adecuada; en este caso ObBorrado (líneas 165 y 166).

157 private class BorrarDatos implements ActionListener {


158 public void actionPerformed(ActionEvent Evento) {
159 ObConsulta InstanciaConsulta = new
160 ObConsulta(CampoDNI.getText());
161 PosicionBuscada =
162 InstanciaConsulta.PosicionEncontrada();
163
164 if (PosicionBuscada!=0) {
165 ObBorrado InstanciaBorrado = new
166 ObBorrado(PosicionBuscada);
167 CampoDNI.setText(""); CampoNombre.setText("");
168 CampoApellido.setText(""); CampoEdad.setText("");
169 } else {
170 CampoNombre.setText("------");
166 © JESÚS BOBADILLA SANCHO

171 CampoApellido.setText("------");
172 CampoEdad.setText("---");
173 }
174 }
175 }

7.7.9.6 Listener ListarDatos

La clase ListarDatos resulta especialmente simple: es suficiente crear una


instancia del objeto ObListado, pasándole como argumento el área de texto donde se
desea situar el listado de los registros.

176 private class ListarDatos implements ActionListener {


177 public void actionPerformed(ActionEvent Evento) {
178 ObListado InstanciaListado = new
179 ObListado(AreaDeListado);
180 PosicionBuscada = 1;
181 }
182 }
183

7.7.9.7 Listener Controles

Cuando se ejecuta el único método actionPerformed del interfaz


ActionListener en la clase controles, lo primero que hay que hacer es determinar cuál
ha sido el botón que ha generado el evento (línea 186), obteniendo posteriormente su
nombre (línea 187). En las líneas 45 a 55 se han establecido los nombres: “0”, “1”,
“2”, ... , “5”. Después se crea la instancia del objeto ObPosicionamiento , pasando
como argumentos: la acción deseada y la posición actual en la tabla (líneas 188 y
189).
 JESÚS BOBADILLA SANCHO 167

Por último se obtienen los datos de la nueva posición y, con ellos, se crea
un objeto de tipo ObRegistro (líneas 191 y 192), actualizando los campos del GUI
(líneas 193 a 197) y la posición del cursor que mantenemos en la aplicación (línea
198).

184 private class Controles implements ActionListener {


185 public void actionPerformed(ActionEvent Evento) {
186 Button Pulsado = (Button) Evento.getSource();
187 String Identificador = Pulsado.getName();
188 ObPosicionamiento InstanciaPosicionamiento = new
189 ObPosicionamiento(Identificador,PosicionBuscada);
190
191 ObRegistro InstanciaFila =
192 InstanciaPosicionamiento.DameDatos();
193 CampoDNI.setText(InstanciaFila.DameDNI());
194 CampoNombre.setText(InstanciaFila.DameNombre());
195 CampoApellido.setText(InstanciaFila.DameApellido());
196 CampoEdad.setText(String.valueOf(
197 InstanciaFila.DameEdad()));
198 PosicionBuscada=InstanciaPosicionamiento.Posicion();
199 }
200 }

7.8 OBTENCIÓN DE METADATOS


Además de poder acceder a los datos, tenemos la posibilidad de conocer
información general de las bases de datos y de la estructura de las tablas. La
información general la proporciona el interfaz DataBaseMetaData, mientras que la
información relativa a los tipos y propiedades de las columnas en un ResultSet la
conseguimos haciendo uso del interfaz ResultSetMetaData .

Los “metadatos” que se utilizan más habitualmente son los que podemos
conseguir utilizando el interfaz ResultSetMetaData. Una vez que disponemos de un
objeto de tipo ResultSet, podemos definir una instancia de la clase
ResultSetMetaData, utilizando el método getMetaData perteneciente al interfaz
ResultSet; en las líneas 14 y 15 del siguiente ejemplo se utiliza este mecanismo.

Entre la información de mayor interés que podemos obtener a través de un


objeto de tipo ResultSetMetaData , se encuentra:
168 © JESÚS BOBADILLA SANCHO

Información Método
Número de campos int getColumnCount()
Tamaño máximo del campo int getColumnDisplaySize(int Columna)
Nombre de un campo String getColumnName(int Columna)
Tipo SQL de un campo int getColumnType(int Columna)
Denominación del tipo de un campo String getColumnTypeName(int Columna)
Número de dígitos decimales int getPrecision(int Columna)
Indica si el campo puede tomar el valor null int isNullable(int Columna)
Indica si el campo no puede ser escrito boolean isReadOnly(int Columna)
Indica si el campo puede ser usado en una boolean isSearchable(int Columna)
cláusula where
Indica si se puede realizar una escritura en el boolean isWritable(int Columna)
campo

La clase MetaDatos (línea 3) hace uso del interfaz ResultSetMetaData. El


objeto DatosInternos se obtiene aplicando el método getMetaData al objeto
Personas, de tipo ResultSet (líneas 12 a 15).

A través de DatosInternos podemos conocer el número de campos del


ResultSet, empleando el método getColumnCount (línea 16).

El bucle de la línea 18 nos sirve para recorrer todos los campos de la tabla
DatosPersonales, obteniendo el nombre (getColumnName), tipo
(getColumnTypeName) y tamaño máximo (getColumnDisplaySize) de cada uno de
ellos (líneas 20 a 24).

1 import java.sql.*;
2
3 public class MetaDatos {
4
5 public static void main(String[] args){
6 try{
7 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
8 String BaseDeDatos = "jdbc:odbc:NombreLogico";
9 Connection Conexion =
10 DriverManager.getConnection(BaseDeDatos);
11 Statement SentenciaSQL = Conexion.createStatement();
12 ResultSet Personas = SentenciaSQL.
13 executeQuery("SELECT * FROM DatosPersonales");
14 ResultSetMetaData DatosInternos =
15 Personas.getMetaData();
16 int NumeroDeColumnas= DatosInternos.getColumnCount();
17
18 for (int Columna=1;Columna<=NumeroDeColumnas;
19 Columna++) {
 JESÚS BOBADILLA SANCHO 169

20 String Nombre=DatosInternos.getColumnName(Columna);
21 String Tipo =
22 DatosInternos.getColumnTypeName(Columna);
23 int Tamanio =
24 DatosInternos.getColumnDisplaySize(Columna);
25 System.out.println(Nombre+", "+Tipo+", "+Tamanio);
26 }
27
28 Personas.close();
29 Conexion.close();
30 SentenciaSQL.close();
31 }
32 catch (ClassNotFoundException e) {
33 System.out.println("Clase no encontrada");
34 }
35 catch (SQLException e) {
36 System.out.println(e);
37 }
38 }
39
40 }

7.9 EJECUCIÓN DE SENTENCIAS SQL QUE NO


DEVUELVEN RESULTADOS
Todos los ejemplos de bases de datos que hemos empleado utilizan el
método executeQuery para ejecutar la sentencia SQL: “SELECT * FROM
DatosPersonales”. El método executeQuery devuelve un objeto de tipo ResultSet
que contiene el resultado de la sentencia SQL, pero ... ¿Qué ocurre cuando la
sentencia SQL no devuelve ningún resultado? En este caso se levanta una excepción
del tipo SQLException. En el siguiente gráfico podemos observar el mensaje
obtenido al ejecutar, con el método executeQuery, la sentencia Sentencia SQL:
“DELETE FROM DatosPersonales WHERE Nombre=’Luis’”.
170 © JESÚS BOBADILLA SANCHO

Las sentencias INSERT, DELETE y UPDATE modifican el contenido de la


base de datos, pero no devuelven ningún resultado ResultSet. Para ejecutar estas
sentencias debemos emplear el método executeUpdate , perteneciente al interfaz
Statement. La clase Borrado2, en las líneas 15 y 16, nos muestra la manera de
hacerlo.

1 import java.sql.*;
2
3 public class Borrado2 {
4
5 public static void main(String[] args){
6 try{
7 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
8 String BaseDeDatos = "jdbc:odbc:NombreLogico";
9 Connection Conexion =
10 DriverManager.getConnection(BaseDeDatos);
11 Statement SentenciaSQL = Conexion.createStatement(
12 ResultSet.TYPE_SCROLL_INSENSITIVE,
13 ResultSet.CONCUR_UPDATABLE);
14
15 int Borrados = SentenciaSQL.executeUpdate("DELETE FROM
16 DatosPersonales WHERE Nombre='Luis'");
17
18 System.out.println(Borrados+" registros borrados");
19 Conexion.close();
20 SentenciaSQL.close();
21 }
22 catch (ClassNotFoundException e) {
23 System.out.println("Clase no encontrada");
24 }
25 catch (SQLException e) {
26 System.out.println(e);
27 } } }
 JESÚS BOBADILLA SANCHO 171

En definitiva, cuando las sentencias SQL devuelven resultados, utilizamos el


método executeQuery; cuando no devuelven resultados empleamos el método
executeUpdate . En el siguiente ejemplo, SQLInteractivo, hacemos uso de ambos
métodos del interfaz Statement; si la sentencia SQL es de alguno de los tipos:
UPDATE, DELETE o INSERT (líneas 15 a 17) utilizamos el método executeUpdate
(líneas 18 y 19). En los demás casos usamos el método executeQuery (líneas 23 y
24).

En el ejemplo, la sentencia SQL se proporciona como primer argumento


(args[0]) en la ejecución del programa. Cuando se obtiene un resultado, se imprime
un listado de los registros (líneas 25 a 32). Tras el código, se muestra el
funcionamiento del programa al introducirle diversas sentencias SQL de tipo
SELECT, UPDATE y DELETE.

1 import java.sql.*;
2
3 public class SQLInteractivo {
4
5 public static void main(String[] args){
6 String DNI,Nombre,Apellido; int Edad;
7 try{
8 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
9 String BaseDeDatos = "jdbc:odbc:NombreLogico";
10 Connection Conexion =
11 DriverManager.getConnection(BaseDeDatos);
12 Statement SentenciaSQL = Conexion.createStatement(
13 ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE);
14
15 if (args[0].startsWith("UPDATE")||
16 args[0].startsWith("DELETE")||
17 args[0].startsWith("INSERT")) {
18 int ElementosVariados =
19 SentenciaSQL.executeUpdate(args[0]);
20 System.out.println(ElementosVariados+" elementos han
172 © JESÚS BOBADILLA SANCHO

21 sido actualizados, insertados o borrados");


22 } else {
23 ResultSet Personas =
24 SentenciaSQL.executeQuery(args[0]);
25 while (Personas.next()) {
26 DNI = Personas.getString("DNI");
27 Nombre = Personas.getString("Nombre");
28 Apellido = Personas.getString("Apellido");
29 Edad = Personas.getInt("Edad");
30 System.out.println(Nombre+" "+Apellido+",
31 "+Edad+", "+DNI);
32 }
33 Personas.close();
34 }
35 Conexion.close();
36 SentenciaSQL.close();
37 }
38 catch (ClassNotFoundException e) {
39 System.out.println("Clase no encontrada");
40 }
41 catch (SQLException e) {
42 System.out.println(e);
43 }
44 }
45
46 }
LECCIÓN 8

APLICACIONES WEB CON ACCESO A


BASES DE DATOS

8.1 OPERACIONES SOBRE UNA TABLA


8.1.1 Introducción

En el capítulo dedicado a las bases de datos se presentó un ejemplo que


permitía realizar consultas, altas, bajas, modificaciones, etc. sobre una tabla
perteneciente a una base de datos. En este apartado se modifica el código para que
los usuarios puedan realizar dichas operaciones de forma remota, utilizando un
navegador.

Los cambios que ha sido necesario realizar, respecto a la aplicación original,


han sido muy leves, salvo en el interfaz gráfico de usuario, que obviamente hemos
reescrito para que pueda ser visualizado por los navegadores web.

El objeto ObRegistro no presenta ningún cambio. Su función es albergar los


datos de un registro de la tabla, y esa función no es dependiente del interfaz gráfico
utilizado. Tampoco ha sido necesario efectuar ninguna modificación en los objetos
ObConsulta, ObBorrado, ObInserccion, ObModificacion y ObPosicionamiento.

Como se puede observar en la estructura de la aplicación, la única clase que


presenta modificaciones de importancia es SvAccesoBD, que sustituye a
GUIAccesoBD (junto al fichero SvAccesoBD.htm) y que resulta muy fácil de
entender por su gran similitud con esta última.
174 © JESÚS BOBADILLA SANCHO

SvAccesoBD.htm

SvAccesoBD

ObListado ObConsulta

ObBorrado ObInserccion

ObModificacion

ObPosicionamiento

ObRegistro

El objeto ObListado, en la versión original, depositaba los valores de las


tuplas directamente en un área de texto. En este ejemplo almacena el listado en la
variable de tipo String Resultado (línea 6 y 26 a 29). La clase incorpora un nuevo
método: DameResultado (línea 44), que devuelve el String con el listado de la tabla.

1 import java.sql.*;
2 import java.awt.TextArea;
3
4 public class ObListado {
5
6 private String Resultado="";
7
8 ObListado(){
9 String DNI, Nombre, Apellido, Linea;
10 int Edad, Posicion;
11 try{
12 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
13 String BaseDeDatos = "jdbc:odbc:NombreLogico";
14 Connection Conexion =
15 DriverManager.getConnection(BaseDeDatos);
16 Statement SentenciaSQL = Conexion.createStatement();
17 ResultSet Personas = SentenciaSQL.executeQuery("SELECT
 JESÚS BOBADILLA SANCHO 175

18 * FROM DatosPersonales");
19
20 while (Personas.next()) {
21 DNI = Personas.getString("DNI");
22 Nombre = Personas.getString("Nombre");
23 Apellido = Personas.getString("Apellido");
24 Edad = Personas.getInt("Edad");
25 Posicion = Personas.getRow();
26 Linea = DNI+", "+Nombre+" "+Apellido+",
27 "+String.valueOf(Edad);
28 Resultado=Resultado+String.valueOf(Posicion)+
29 ": "+Linea+"\n";
30 }
31
32 Personas.close();
33 Conexion.close();
34 SentenciaSQL.close();
35 }
36 catch (ClassNotFoundException e) {
37 System.out.println("Clase no encontrada");
38 }
39 catch (SQLException e) {
40 System.out.println(e);
41 }
42 }
43
44 public String DameResultado(){
45 return Resultado;
46 }
47
48 }

8.1.2 Interfaz gráfico de usuario

El nuevo interfaz gráfico de usuario debe realizarse en HTML, para permitir


a los clientes interactuar con la base de datos utilizando navegadores. El aspecto del
GUI conseguido con el fichero SvAccesoBD.htm es prácticamente igual al que
habíamos diseñado en la clase GUIAccesoBD. A continuación se muestra el código
de la página .htm y su resultado.

<html>
<body>
<table border=1>
<tr>
<td>
176 © JESÚS BOBADILLA SANCHO

<form method="get"
action="http://localhost:8080/examples/
servlet/SvAccesoBD" >
DNI: <input type="text" name="DNI"> <br>
Nombre: <input type="text" name="Nombre"> <br>
Apellido: <input type="text" name="Apellido"> <br>
Edad: <input type="text" name="Edad"> <br><br>
<input type="submit" value="Consultar"
name="BotonConsultar">
<input type="submit" value="Insertar"
name="BotonInsertar">
<input type="submit" value="Modificar"
name="BotonModificar">
<input type="submit" value="Borrar" name="BotonBorrar">
<input type="submit" value="Listar" name="BotonListar">
<br>
<center>
<input type="submit" value=" |< "
name="BotonPrimero">
<input type="submit" value=" -- "
name="BotonMenosCinco">
<input type="submit" value=" - "
name="BotonAnterior">
<input type="submit" value=" + "
name="BotonSiguiente">
<input type="submit" value=" ++ "
name="BotonMasCinco">
<input type="submit" value=" >| "
name="BotonUltimo">
</center>
</td>
<td>
<textarea name="AreaDeListado" rows="10"
cols="35"></textarea>
</td>
</tr>
</form>
</table>
</body>
</html>
 JESÚS BOBADILLA SANCHO 177

8.1.3 El control de la aplicación

Cuando hicimos los desarrollos de este ejemplo como una aplicación, la


clase GUIAccesoBD realizaba la función de interfaz gráfico de usuario, y además
incluía las funciones de control (empleando el mecanismo de eventos de Java y su
captura con “listeners”).

Ahora estamos empleando servlets y páginas web. Los clientes


habitualmente trabajan en una computadora remota y los programas se ejecutan en
un servidor. El mecanismo de comunicaciones que soporta el protocolo http nos
permite consultar, en el servidor, los datos que han seleccionado o escrito los
usuarios; de esta manera, podemos observar una clara disociación entre interfaz
gráfico de usuario (que se ejecuta en los equipos de los clientes) y el programa que
recoge los datos y ejecuta las acciones pertinentes (que se ejecuta en el servidor).

Mientras que el interfaz gráfico de usuario ya ha sido presentado


(SvAccesoBD.htm), nos falta por desarrollar el programa de control (SvAccesoBD).
A continuación se muestra su listado completo y los comentarios de las porciones
más significativas del mismo.

El servlet SvAccesoBD (línea 5), a grandes rasgos, realiza las siguientes


acciones:
1 Recoge los valores provenientes del usuario (líneas 15 a 44)
2 Determina la naturaleza de la acción a realizar (líneas 51 a 73)
3 Realiza su cometido con la base de datos (líneas 80 a 162)
4 Genera la respuesta que recibirá el usuario (líneas 165 a 214)

La acción número 1 se implementa mediante sucesivas llamadas al método


getParameter, aplicado al objeto request (línea 11). La acción número 2 se basa en
conocer el botón que ha pulsado el usuario, que nos llega con un valor distinto de
null, e invocar al método correspondiente (ConsultarDatos, ModificarDatos,
InsertarDatos, etc.).

Los métodos de la acción 3 forman el núcleo de la clase; implementan los


pasos necesarios para realizar consultas, listados, borrados, modificaciones e
inserciones de registros en la base de datos, así como variaciones de posición. Estos
métodos se apoyan en las clases ObXxxxxxx, creando las instancias necesarias para
realizar su función (consultas, listados, ...). La programación de cada uno de los
métodos es muy similar a la realizada en los “listeners” contenidos en la clase
GUIAccesoBD, por lo que su comprensión no presenta dificultades añadidas.
178 © JESÚS BOBADILLA SANCHO

La acción 4 (generar la respuesta que recibirá el usuario) se implementa en


el método GeneraRespuesta (línea 165), que toma como parámetros los datos a
visualizar y genera la página una página html igual a SvAccesoBD.htm.

1 import java.io.*;
2 import javax.servlet.*;
3 import javax.servlet.http.*;
4
5 public class SvAccesoBD extends HttpServlet {
6
7 private int PosicionBuscada = 1;
8 private String AreaDeListado="";
9 PrintWriter out;
10
11 synchronized public void doGet(HttpServletRequest request,
12 HttpServletResponse response)
13 throws IOException, ServletException {
14
15 String DNI = request.getParameter("DNI");
16 String Nombre = request.getParameter("Nombre");
17 String Apellido = request.getParameter("Apellido");
18 String Edad = request.getParameter("Edad");
19
20 String BotonConsultar =
21 request.getParameter("BotonConsultar");
22 String BotonInsertar =
23 request.getParameter("BotonInsertar");
24 String BotonModificar =
25 request.getParameter("BotonModificar");
26 String BotonBorrar =
27 request.getParameter("BotonBorrar");
28 String BotonListar =
29 request.getParameter("BotonListar");
30
31 String BotonPrimero =
32 request.getParameter("BotonPrimero");
33 String BotonMenosCinco =
34 request.getParameter("BotonMenosCinco");
35 String BotonAnterior =
36 request.getParameter("BotonAnterior");
37 String BotonSiguiente =
38 request.getParameter("BotonSiguiente");
39 String BotonMasCinco =
40 request.getParameter("BotonMasCinco");
41 String BotonUltimo =
42 request.getParameter("BotonUltimo");
43
44 AreaDeListado = request.getParameter("AreaDeListado");
45
 JESÚS BOBADILLA SANCHO 179

46 response.setContentType("text/html");
47 out = response.getWriter();
48 out.println("<html>");
49 out.println("<body>");
50
51 if (BotonConsultar!=null)
52 ConsultarDatos(DNI);
53 if (BotonInsertar!=null)
54 InsertarDatos(DNI,Nombre,Apellido,Edad);
55 if (BotonModificar!=null)
56 ModificarDatos(DNI,Nombre,Apellido,Edad);
57 if (BotonBorrar!=null)
58 BorrarDatos(DNI);
59 if (BotonListar!=null)
60 ListarDatos(DNI);
61
62 if (BotonPrimero!=null)
63 Controles("1");
64 if (BotonMenosCinco!=null)
65 Controles("2");
66 if (BotonAnterior!=null)
67 Controles("3");
68 if (BotonSiguiente!=null)
69 Controles("4");
70 if (BotonMasCinco!=null)
71 Controles("5");
72 if (BotonUltimo!=null)
73 Controles("0");
74
75 out.println("</body>");
76 out.println("</html>");
77 }
78
79
80 private void ConsultarDatos(String DNI) {
81 ObConsulta InstanciaConsulta = new ObConsulta(DNI);
82 PosicionBuscada=InstanciaConsulta.PosicionEncontrada();
83 if (PosicionBuscada!=0) {
84 ObRegistro InstanciaFila =
85 InstanciaConsulta.DameDatos();
86 GenerarRespuesta(InstanciaFila.DameDNI(),
87 InstanciaFila.DameNombre(),
88 InstanciaFila.DameApellido(),
89 String.valueOf(InstanciaFila.DameEdad()));
90 } else {
91 GenerarRespuesta(DNI,"----","-----","---");
92 PosicionBuscada = 1;
93 }
94 }
180 © JESÚS BOBADILLA SANCHO

95
96
97 public void ListarDatos(String DNI) {
98 ObListado InstanciaListado = new ObListado();
99 AreaDeListado = InstanciaListado.DameResultado();
100 ConsultarDatos(DNI);
101 PosicionBuscada = 1;
102 }
103
104
105 public void BorrarDatos(String DNI){
106 ObConsulta InstanciaConsulta = new ObConsulta(DNI);
107 PosicionBuscada =
108 InstanciaConsulta.PosicionEncontrada();
109
110 if (PosicionBuscada!=0) {
111 ObBorrado InstanciaBorrado =
112 new ObBorrado(PosicionBuscada);
113 GenerarRespuesta("","","","");
114 } else {
115 GenerarRespuesta("----","----","----","----");
116 }
117 PosicionBuscada = 1;
118 }
119
120
121 public void ModificarDatos(String DNI, String Nombre,
122 String Apellido, String Edad) {
123 ObConsulta InstanciaConsulta = new ObConsulta(DNI);
124 PosicionBuscada =
125 InstanciaConsulta.PosicionEncontrada();
126
127 if (PosicionBuscada!=0) {
128 ObRegistro InstanciaFila = new ObRegistro(DNI,
129 Nombre, Apellido, Integer.parseInt(Edad));
130 ObModificacion InstanciaModificacion =
131 new ObModificacion(PosicionBuscada, InstanciaFila);
132 GenerarRespuesta(DNI,Nombre,Apellido,Edad);
133 } else {
134 GenerarRespuesta("----","----","----","----");
135 PosicionBuscada = 1;
136 }
137 }
138
139
140 public void InsertarDatos(String DNI, String Nombre,
141 String Apellido, String Edad) {
142 ObRegistro InstanciaFila = new ObRegistro(DNI,
143 Nombre, Apellido, Integer.parseInt(Edad));
 JESÚS BOBADILLA SANCHO 181

144 ObInserccion InstanciaInserccion =


145 new ObInserccion(InstanciaFila);
146 GenerarRespuesta("","","","");
147 PosicionBuscada=1;
148 }
149
150
151 public void Controles(String Identificador) {
152 ObPosicionamiento InstanciaPosicionamiento = new
153 ObPosicionamiento(Identificador,PosicionBuscada);
154 ObRegistro InstanciaFila =
155 InstanciaPosicionamiento.DameDatos();
156 GenerarRespuesta(InstanciaFila.DameDNI(),
157 InstanciaFila.DameNombre(),
158 InstanciaFila.DameApellido(),
159 String.valueOf(InstanciaFila.DameEdad()));
160
161 PosicionBuscada =InstanciaPosicionamiento.Posicion();
162 }
163
164
165 private void GenerarRespuesta(String DNI, String
166 Nombre, String Apellido, String Edad) {
167 out.println("<table border=1>");
168 out.println("<tr>");
169 out.println("<td>");
170 out.println("<form method='get'
171 action='http://localhost:8080/examples/
172 servlet/SvAccesoBD'>");
173 out.println("DNI: <input type='text' name='DNI'
174 value="+DNI+"><br>");
175 out.println("Nombre: <input type='text'
176 name='Nombre' value="+Nombre+"><br>");
177 out.println("Apellido: <input type='text'
178 name='Apellido' value="+Apellido+"><br>");
179 out.println("Edad: <input type='text' name='Edad'
180 value="+Edad+"><br><br>");
181
182 out.println("<input type='submit' value='Consultar'
183 name='BotonConsultar'>");
184 out.println("<input type='submit' value='Insertar'
185 name='BotonInsertar'>");
186 out.println("<input type='submit' value='Modificar'
187 name='BotonModificar'>");
188 out.println("<input type='submit' value='Borrar'
189 name='BotonBorrar'>");
190 out.println("<input type='submit' value='Listar'
191 name='BotonListar'> <br>");
192 out.println("<center>");
182 © JESÚS BOBADILLA SANCHO

193 out.println("<input type='submit' value=' |< '


194 name='BotonPrimero'>");
195 out.println("<input type='submit' value=' -- '
196 name='BotonMenosCinco'>");
197 out.println("<input type='submit' value=' - '
198 name='BotonAnterior'>");
199 out.println("<input type='submit' value=' + '
200 name='BotonSiguiente'>");
201 out.println("<input type='submit' value=' ++ '
202 name='BotonMasCinco'>");
203 out.println("<input type='submit' value=' >| '
204 name='BotonUltimo'>");
205 out.println("</center>");
206 out.println("</td>");
207 out.println("<td>");
208 out.println(“<textarea name='AreaDeListado' rows='10'
209 cols='35'>"+AreaDeListado+"</textarea>");
210 out.println("</td>");
211 out.println("</tr>");
212 out.println("</form>");
213 out.println("</table>");
214 }
215 }

8.2 GENERACIÓN Y CORRECCIÓN AUTOMÁTICA DE


TESTS
8.2.1 Definición del ejemplo

En este apartado se realiza una aplicación que genera preguntas de test a


partir de los datos contenidos en una tabla, y que es capaz de realizar una corrección
automática de los mismos una vez que el usuario ha contestado a las preguntas. La
tabla, de nombre Test, contiene los siguientes campos: Leccion, NumPregunta,
Pregunta, Respuesta1, Respuesta2, Respuesta3, Respuesta4, Solucion1, Solucion2,
Solucion3 y Solucion4. Los 2 primeros campos son de tipo entero, los 4 últimos de
tipo lógico y el resto son alfanuméricos.

Las preguntas de test están agrupadas por lecciones (definidas en el campo


Leccion). Cada lección contiene un número variable de preguntas (enumeradas en el
campo NumPregunta ). De esta manera, la lección 1, que contiene 3 preguntas, se
 JESÚS BOBADILLA SANCHO 183

define en 3 registros, la lección 2 con 6 preguntas, se define en 6 registros, etc. A


continuación se muestra parte del contenido de los 3 primeros campos de la tabla:

Leccion NumPregunta Pregunta


1 1 La variable de entorno CLASSPATH
1 2 La ubicación de los ficheros ejecutables del SDK
1 3 La ayuda del SDK
2 6 Si obtenemos un error en la ejecución puede ser
2 1 porque
Los programas en Java
2 2 Respecto a la estructura mínima de un programa
2 3 en
En Java
los programas hay que tener en cuenta que
2 4 Los programas (el código) debemos grabarlos
2 5 Si obtenemos un error en compilación puede ser
porque

Los 4 campos RespuestaX contienen las 4 respuestas asignadas a cada


pregunta de las lecciones. El test se ha diseñado de manera que puede haber
cualquier número de respuestas correctas en una pregunta. Los campos SolucionX
nos sirven para saber cuales son las respuestas correctas a cada pregunta.

La primera pregunta de la lección 1 se puede mostrar de la siguiente manera:

Las soluciones a las 2 primeras lecciones (de las 5 proporcionadas en el CD)


son:

Solucion1 Solucion2 Solucion3 Solucion4


No Sí No Sí
No Sí Sí No
Sí No Sí Sí
No Sí Sí Sí
184 © JESÚS BOBADILLA SANCHO

No Sí No No
Sí Sí No Sí
Sí No Sí Sí
No Sí Sí No
Sí Sí Sí Sí

Para resolver este ejercicio haciendo uso de servlets, utilizamos la siguiente


arquitectura:

GeneraTests.htm

Número de lección GeneraTests.java

GeneraRespuestas.java Test en formato html

Número de respuestas acertadas

La página web GeneraTests.htm sirve de arranque a la aplicación; se


encarga de proporcionar una caja de texto para que el usuario escriba el número de
lección sobre el que desea realizar el test. Esta página provoca la ejecución del
servlet GeneraTests.java, que construye en html el test de la lección seleccionada,
tomando las preguntas y respuestas de la base de datos.

Cuando el usuario ha rellenado el test y pulsa el botón de enviar, se ejecuta


el servlet GeneraRespuestas.java, encargado de verificar la corrección de las
respuestas introducidas por el cliente.

8.2.2 Selección de la lección de test

El fichero GeneraTests.htm es muy sencillo, proporciona una caja de texto y


un botón de envío. La URL asociada a su parámetro action es la del servlet
GeneraTests. El nombre asociado a la caja de texto es Leccion, que será utilizado
por el servlet llamado para recoger este parámetro.

<html>
<body>
 JESÚS BOBADILLA SANCHO 185

<h1> Tests </h1>


<form action="http://localhost:8080/examples/
servlet/GeneraTests" method="get">
<h2>Lección número: <input type="text" name=Leccion
SIZE=2></h2>
<input type="submit" value="Enviar datos">
</form>
</body>
</html>

8.2.3 Servlet de generación de tests

La clase GeneraTests (línea 6) es un servlet que se encarga de generar las


preguntas de test de la lección seleccionada por el usuario. Esta lección se obtiene
utilizando el método getParameter, aplicado al objeto request (línea 11).

La página de respuesta proporciona un formulario que nos envía al servlet


GeneraRespuestas (línea 18), que a su vez necesita conocer la lección seleccionada
por el usuario, por ello se incluye un campo de tipo oculto que contiene esta
información (líneas 20 y 21).

El método GeneraListado (líneas 22 y 30) se encarga de escribir las


preguntas, sus respuestas y las cajas de verificación sobre las que actuarán los
usuarios. En las líneas 36 a 42 se abre la base de datos y se seleccionan (select) los
registros de la tabla Test que contengan en el campo Leccion la lección seleccionada
por el usuario.

El bucle situado en la línea 44 itera sobre cada una de las preguntas


contenidas en el ResultSet Preguntas. En el interior del bucle se obtiene (por cada
registro, que contiene una pregunta) el valor del campo NumPregunta , de la
pregunta (campo Pregunta ) y de las 4 respuestas (líneas 45 a 50).
186 © JESÚS BOBADILLA SANCHO

En las líneas 52 y 53 se muestra el número de pregunta y su texto; en las


líneas 54 a 61 las respuestas, junto a sus cajas de verificación. Resulta muy
importante entender que a cada respuesta se le ha asignado una caja de verificación
con el nombre: “Rna, Rnb, Rnc o Rnd”, siendo ‘n’ el número de pregunta y ‘a’, ‘b’,
‘c’ y ‘d’ el orden de las respuestas. Estos nombres (R2a, R1c, R4d, etc.) se utilizan
en el servlet GeneraRespuestas para conocer las respuestas de los usuarios.

1 import java.io.*;
2 import javax.servlet.*;
3 import javax.servlet.http.*;
4 import java.sql.*;
5
6 public class GeneraTests extends HttpServlet {
7
8 public void doGet(HttpServletRequest request,
9 HttpServletResponse response)
10 throws IOException, ServletException {
11 String Leccion = request.getParameter("Leccion");
12
13 response.setContentType("text/html");
14 PrintWriter out = response.getWriter();
15 out.println("<html>");
16 out.println("<body>");
17 out.println("<h1>Lección: "+Leccion+"</h1>");
18 out.println("<form action=GeneraRespuestas
19 method=get>");
20 out.println("<br><input type=hidden name=Leccion
21 value="+Leccion+">");
22 GeneraListado(out,Leccion);
23 out.println("<br><input type=submit>");
24 out.println("</form>");
25 out.println("</body>");
26 out.println("</html>");
27 }
28
29
30 private void GeneraListado(PrintWriter out,
31 String Leccion){
32 String Pregunta,Respuesta1,Respuesta2,
33 Respuesta3,Respuesta4;
34 int NumPregunta;
35 try{
36 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
37 String BaseDeDatos = "jdbc:odbc:Tests";
38 Connection Conexion =
39 DriverManager.getConnection(BaseDeDatos);
40 Statement SentenciaSQL = Conexion.createStatement();
41 ResultSet Preguntas = SentenciaSQL.executeQuery(
42 "SELECT * FROM Test WHERE Leccion="+Leccion);
 JESÚS BOBADILLA SANCHO 187

43
44 while (Preguntas.next()) {
45 NumPregunta = Preguntas.getInt("NumPregunta");
46 Pregunta = Preguntas.getString("Pregunta");
47 Respuesta1 = Preguntas.getString("Respuesta1");
48 Respuesta2 = Preguntas.getString("Respuesta2");
49 Respuesta3 = Preguntas.getString("Respuesta3");
50 Respuesta4 = Preguntas.getString("Respuesta4");
51
52 out.println("<h2>"+NumPregunta+". "+
53 Pregunta+"</h2>");
54 out.println("<input type=checkbox value=true
55 name=R"+NumPregunta+"a>"+Respuesta1+"<br>");
56 out.println("<input type=checkbox value=true
57 name=R"+NumPregunta+"b>"+Respuesta2+"<br>");
58 out.println("<input type=checkbox value=true
59 name=R"+NumPregunta+"c>"+Respuesta3+"<br>");
60 out.println("<input type=checkbox value=true
61 name=R"+NumPregunta+"d>"+Respuesta4+"<br>");
62 }
63
64 Preguntas.close();
65 Conexion.close();
66 SentenciaSQL.close();
67 }
68 catch (ClassNotFoundException e) {
69 System.out.println("Clase no encontrada");
70 }
71 catch (SQLException e) {
72 System.out.println(e);
73 }
74 }
75 }
188 © JESÚS BOBADILLA SANCHO

8.2.4 Servlet de generación de respuestas

La clase GeneraRespuestas (línea 6) se encarga de consultar las respuestas


de test seleccionadas por el usuario, comparándolas con las respuestas correctas
establecidas en la base de datos; como resultado de esta comparación, el servlet
proporciona un listado de preguntas acertadas y falladas.

Antes de seleccionar los registros de la base de datos es necesario conocer la


lección sobre la que se realiza el test. En la línea 11 se obtiene este dato, que
proviene de la clase GeneraTests a través de un campo oculto con nombre Leccion.

El núcleo de la clase se implementa en el método privado GeneraRespuestas


(líneas 19 y 26). En las líneas 32 a 38 se obtienen los registros de la tabla Test
correspondientes a la lección seleccionada por el usuario.

En la línea 40 se implementa un bucle que itera por cada pregunta de la


lección. En la línea 41 se obtiene el número de pregunta y en las líneas 42 a 45 las
respuestas correctas a dicha pregunta.

Las líneas 47 a 54 recogen las respuestas del usuario a la pregunta obtenida


en cada iteración del bucle. Obsérvese como se generan los nombres introducidos en
los métodos getParameter: “Rna, Rnb, Rnc y Rnd”, siendo ‘n’ el número de
pregunta; de esta forma hacemos coincidir los nombres ‘construidos’ en la clase
GeneraTests con los de la clase GeneraRespuestas.

En las líneas 56 a 59 se implementa la lógica que determina si se ha acertado


una pregunta: obligamos a acertar las 4 respuestas, es decir, las 4 respuestas del
usuario (RxUsuario ) deben coincidir con los valores que proporciona la base de
datos (SolucionX).

1 import java.io.*;
2 import javax.servlet.*;
3 import javax.servlet.http.*;
4 import java.sql.*;
5
6 public class GeneraRespuestas extends HttpServlet {
7
8 public void doGet(HttpServletRequest request,
9 HttpServletResponse response)
10 throws IOException, ServletException {
11 String Leccion = request.getParameter("Leccion");
12
13 response.setContentType("text/html");
14 PrintWriter out = response.getWriter();
15 out.println("<html>");
16 out.println("<body>");
 JESÚS BOBADILLA SANCHO 189

17 out.println("<h1>Lección: "+Leccion+"</h1>");
18
19 GeneraRespuestas(request,out,Leccion);
20
21 out.println("</body>");
22 out.println("</html>");
23 }
24
25
26 private void GeneraRespuestas(HttpServletRequest
27 request,PrintWriter out,String Leccion){
28 boolean SolucionA,SolucionB,SolucionC,SolucionD,
29 RaUsuario,RbUsuario,RcUsuario,RdUsuario,Acertado;
30 int NumPregunta;
31 try{
32 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
33 String BaseDeDatos = "jdbc:odbc:Tests";
34 Connection Conexion =
35 DriverManager.getConnection(BaseDeDatos);
36 Statement SentenciaSQL = Conexion.createStatement();
37 ResultSet Preguntas = SentenciaSQL.executeQuery(
38 "SELECT * FROM Test WHERE Leccion="+Leccion);
39
40 while (Preguntas.next()) {
41 NumPregunta = Preguntas.getInt("NumPregunta");
42 SolucionA = Preguntas.getBoolean("Solucion1");
43 SolucionB = Preguntas.getBoolean("Solucion2");
44 SolucionC = Preguntas.getBoolean("Solucion3");
45 SolucionD = Preguntas.getBoolean("Solucion4");
46
47 RaUsuario = new Boolean(request.getParameter(
48 "R"+NumPregunta+"a")).booleanValue();
49 RbUsuario = new Boolean(request.getParameter(
50 "R"+NumPregunta+"b")).booleanValue();
51 RcUsuario = new Boolean(request.getParameter(
52 "R"+NumPregunta+"c")).booleanValue();
53 RdUsuario = new Boolean(request.getParameter(
54 "R"+NumPregunta+"d")).booleanValue();
55
56 Acertado = (SolucionA==RaUsuario &&
57 SolucionB==RbUsuario &&
58 SolucionC==RcUsuario &&
59 SolucionD==RdUsuario);
60
61 out.println("<h2>"+NumPregunta+". "+
62 Acertado+"</h2>");
63 }
64
65 Preguntas.close();
190 © JESÚS BOBADILLA SANCHO

66 Conexion.close();
67 SentenciaSQL.close();
68 }
69 catch (ClassNotFoundException e) {
70 System.out.println("Clase no encontrada");
71 }
72 catch (SQLException e) {
73 System.out.println(e);
74 }
75 }
76
77 }
 JESÚS BOBADILLA SANCHO 191

8.3 NÚCLEO DE UNA APLICACIÓN DE FORO


8.3.1 Definición del ejemplo

En este apartado se desarrolla un foro muy sencillo, no supervisado, a partir


del cual resultaría muy fácil realizar un servicio telemático completo de foro. La
aplicación permite:
• Crear, consultar y borrar los temas de discusión
• Insertar y consultar las opiniones de los diferentes temas

La arquitectura de la aplicación se resume en el siguiente diagrama:

ForoTema.htm

ForoTema

ForoInserccionTema

ForoBorradoTema

ForoAccesoTema

ForoInsertarOpTema

ForoAccesoBD

Existe una base de datos “Foro”, que albergará la lista de los temas abiertos
a discusión en cada momento (en la tabla Temas), además de una tabla por cada
tema de discusión, conteniendo las opiniones de los usuarios; cada registro de la
tabla Temas representa un tema del foro. Sus campos son:
• Identificador: alfanumérico de 10 posiciones
• Descripcion: alfanumérico de 80 posiciones
192 © JESÚS BOBADILLA SANCHO

Cada tema de discusión tendrá asociada una tabla cuyo nombre haremos
coincidir con el identificador elegido en la tabla Temas. Las tablas de temas
contendrán un solo campo Opinion, de tipo alfanumérico de 80 posiciones. Estas
tablas se deben crear dinámicamente, según los usuarios van introduciendo temas de
discusión.

A continuación se muestra un ejemplo gráfico de la base de datos Foro.


Supondremos que se han creado dos temas de discusión, el primero identificado
como PenaMuerte y el segundo como AbrigoPiel; estos identificadores se utilizan
para crear las tablas en las que los usuarios escriben sus opiniones sobre cada tema.

Foro

Temas

Identificador Descripcion

PenaMuerte ¿Considera ético mantener la pena de muerte?

AbrigoPiel ¿No cree innecesario y cruel matar animales...

PenaMuerte

Opinion

Existen demasiados errores judiciales como...

Si la religión mayoritaria de un país...

AbrigoPiel

Opinion

Usar pieles de animales resulta innecesario...

Mientras matemos animales para comer...

Habría que distinguir entre aquellos...


 JESÚS BOBADILLA SANCHO 193

8.3.2 Página web de arranque de la aplicación

La página web ForoTema.htm nos muestra cinco botones de radio para


seleccionar entre consulta, inserción y borrado de temas, y consulta e inserción de
opiniones en un tema. Cada una de estas opciones se encuentra identificada por
diferentes parámetros value asociados al name Operacion. El servlet que se ejecuta
al enviar los datos es ForoTema.

A continuación se muestra la página web y su visualización en un


navegador:

<html>
<body>
<h2> Operaciones sobre temas </h2>
<form action="http://localhost:8080/examples/
servlet/ForoTema" method="get">
<input type="radio" name=Operacion checked
value=Consulta> Consultar temas <br>
<input type="radio" name=Operacion value=Insercion>
Insertar tema <br>
<input type="radio" name=Operacion value=Borrado>
Borrar tema<br>

<input type="radio" name=Operacion value=Acceso>


Consultar opiniones<br>
<input type="radio" name=Operacion value=InsertarOp>
Insertar opinión <br><br>
<input type="submit" value="Enviar opción"> <br>
</form>
</body>
</html>
194 © JESÚS BOBADILLA SANCHO

8.3.3 Clase de utilidades: ForoAccesoBD

Para simplificar la programación de las diversas operaciones que hay que


realizar sobre las tablas que componen el foro, se ha considerado conveniente
desarrollar una clase que nos proporciona varios métodos generales de acceso a
bases de datos. Esta clase es ForoAccesoBd .

El constructor de la clase (línea 10) se encarga de establecer la conexión con


la base de datos que se le proporciona como argumento (parámetro NombreLogico)
y crear un objeto SentenciaSQL, de tipo Statement (líneas 7 y 15).

El método Ejecuta (línea 26) es muy sencillo de implementar, sin embargo


permite realizar acciones muy importantes: ejecuta la sentencia SQL que se le
proporcione como argumento (líneas 28 y 29). Hay que tener en cuenta que no se
realiza ningún tipo de validación de la sentencia SQL; además se utiliza el método
executeUpdate , por lo que debemos emplear sentencias que no nos devuelvan
objetos de tipo RecordSet. Emplearemos este método para: crear tablas, insertar y
borrar registros, etc.

El método Lista (línea 35) nos proporciona una tabla HTML con los datos
obtenidos tras ejecutar la sentencia SQL de selección que le pasemos como
argumento. Esta ejecución se realiza utilizando el método executeQuery (línea 38),
que nos devuelve un objeto de tipo RecordSet.

Con el fin de listar todos los campos del objeto Personas, de tipo ResultSet,
hacemos uso del método getMetaData, (línea 40) para obtener un objeto
ResultSetMetaData y, a través de él, conocer el número de campos
(getColumnCount, línea 41) y sus nombres (getColumnName, línea 49). Este método
nos sirve para obtener listados de tablas cuyos campos sean de tipo alfanumérico
(siempre usamos el método getString, línea 50).

El método CreaLista (línea 63) devuelve un array de Strings con los


contenidos de los atributos de la tabla Tabla, situados en el campo Campo
(parámetros del método). Obsérvese la manera de crear y ejecutar la instrucción
SELECT (línea 67 y 68), obtener el número de tuplas (filas) combinando los
métodos last y getRow (líneas 69 y 70) e iterar en los registros de la tabla (bucle en
la línea 74). Antes de comenzar el bucle (y ejecutar el método next) situamos el
cursor en la fila anterior a la primera (método beforeFirst, línea 72).

El método Cierra (línea 85) se encarga de liberar los recursos empleados,


utilizando los métodos close de los objetos Statement y Connection (líneas 87 y 88).

1 import java.sql.*;
2 import java.io.*;
3 import java.awt.TextArea;
 JESÚS BOBADILLA SANCHO 195

4
5 public class ForoAccesoBD {
6
7 private Statement SentenciaSQL;
8 private Connection Conexion;
9
10 ForoAccesoBD(String NombreLogico){
11 try{
12 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
13 String BaseDeDatos = "jdbc:odbc:"+NombreLogico;
14 Conexion = DriverManager.getConnection(BaseDeDatos);
15 SentenciaSQL = Conexion.createStatement(
16 ResultSet.TYPE_SCROLL_INSENSITIVE,
17 ResultSet.CONCUR_UPDATABLE);
18 } catch (ClassNotFoundException e) {
19 System.out.println("Clase no encontrada");
20 } catch (SQLException e) {
21 System.out.println(e);
22 }
23 } // Constructor
24
25
26 synchronized public void Ejecuta(String Sql) {
27 try{
28 int RegistrosInvolucrados =
29 SentenciaSQL.executeUpdate(Sql);
30 } catch (SQLException e) {
31 System.out.println(e);
32 }
33 }
34
35 synchronized public void Lista(String Sql,
36 PrintWriter out) {
37 try{
38 ResultSet Personas = SentenciaSQL.executeQuery(Sql);
39 ResultSetMetaData DatosInternos =
40 Personas.getMetaData();
41 int NumeroDeColumnas=DatosInternos.getColumnCount();
42
43 out.println("<table border=1>");
44 while (Personas.next()) {
45 out.println("<tr>");
46 for (int Columna=1;
47 Columna<=NumeroDeColumnas;Columna++) {
48 String Nombre =
49 DatosInternos.getColumnName(Columna);
50 String Contenido = Personas.getString(Nombre);
51 out.println("<td>"+Contenido+"</td>");
52 }
196 © JESÚS BOBADILLA SANCHO

53 out.println("</tr>");
54 }
55 out.println("</table>");
56 Personas.close();
57 } catch (SQLException e) {
58 System.out.println(e);
59 }
60 }
61
62
63 synchronized public String[] CreaLista(String
64 Tabla,String Campo) {
65 String[] Lista=null;
66 try {
67 String Sql = "SELECT "+Campo+" FROM "+Tabla;
68 ResultSet Personas = SentenciaSQL.executeQuery(Sql);
69 Personas.last();
70 int Registros=Personas.getRow();
71 Lista = new String[Registros];
72 Personas.beforeFirst();
73
74 while (Personas.next())
75 if (!Personas.isAfterLast())
76 Lista[Personas.getRow()-1]=Personas.getString(Campo);
77
78 } catch (SQLException e) {
79 System.out.println(e);
80 }
81 return Lista;
82 }
83
84
85 synchronized public void Cierra() {
86 try{
87 SentenciaSQL.close();
88 Conexion.close();
89 } catch (SQLException e) {
90 System.out.println(e);
91 }
92 }
93
94 }
 JESÚS BOBADILLA SANCHO 197

8.3.4 Clase ForoTema

La clase ForoTema (línea 5) recibe la acción que el usuario solicita,


proveniente de la página ForoTema.htm, a través del nombre Operacion (línea 10).
Si la operación solicitada es la inserción de un tema (línea 16), se genera una página
HTML que permite la introducción de un texto que identifique el tema (líneas 20 y
21) y de otro texto para la descripción (líneas 22 y 23). Estos datos se envían al
servlet ForoInserccionTema (líneas 18 y 19).

En los casos en los que se ha solicitado una operación de borrado o


selección de un tema (líneas 29 y 30), o bien de introducción de una opinión (línea
31), en primer lugar se obtiene una array de Strings con los datos contenidos en el
campo Identificador de la tabla Temas (líneas 35 y 36), utilizando una instancia de
nuestra clase ForoAccesoBD (línea 34). En las líneas 39 a 43 se genera una lista
desplegable “Temas” con los identificadores que hemos obtenido; esta lista servirá
para que el usuario pueda seleccionar con facilidad el tema que le interesa. En el
caso de que la operación a realizar sea la introducción de una opinión (línea 45), el
usuario dispondrá, además de la lista desplegable, de un área de texto (líneas 46 y
47) para escribir su opinión sobre el tema que elija. En las líneas 32 y 33 se definen
los servlets que recogerán y tratarán estos datos: ForoBorradoTema,
ForoAccesoTema y ForoInsertarOpTema.

La operación de consulta (línea 53) se resuelve creando una instancia de


nuestra clase de utilidades ForoAccesoBD (línea 54) y utilizando el método Lista
sobre toda la tabla Temas (línea 55).
198 © JESÚS BOBADILLA SANCHO

1 import java.io.*;
2 import javax.servlet.*;
3 import javax.servlet.http.*;
4
5 public class ForoTema extends HttpServlet {
6
7 synchronized public void doGet(HttpServletRequest
8 request, HttpServletResponse response)
9 throws IOException, ServletException {
10 String Operacion = request.getParameter("Operacion");
11
12 PrintWriter out = response.getWriter();
13 out.println("<html>");
14 out.println("<body>");
15
16 if (Operacion.equals("Insercion")) {
17 out.println("<h2> Insercción de un tema </h2>");
18 out.println("<form action='http://localhost:8080/
19 examples/servlet/ForoInserccionTema' method=get>");
20 out.println("Identificador: <input type=text
21 name=Identificador> <br>");
22 out.println("Descripción: <input type=text
23 name=Descripcion> <br><br>");
24 out.println("<input type=submit value='Enviar tema'>
25 <br>");
26 out.println("</form>");
27 }
28
29 if (Operacion.equals("Borrado")||
30 Operacion.equals("Acceso")||
31 Operacion.equals("InsertarOp")){
32 out.println("<form action='http://localhost:8080/
33 examples/servlet/Foro"+Operacion+"Tema' method=get>");
34 ForoAccesoBD UtilidadesBD = new ForoAccesoBD("Foro");
35 String[] Identificadores =
36 UtilidadesBD.CreaLista("Temas","Identificador");
37 UtilidadesBD.Cierra();
38
39 out.println("<select name=Temas>");
40 for (int i=0;i<Identificadores.length;i++)
 JESÚS BOBADILLA SANCHO 199

41 out.println("<option>"+Identificadores[i]+
42 "</option>");
43 out.println("</select><br><br>");
44
45 if (Operacion.equals("InsertarOp")) {
46 out.println("<textarea rows=3 col=20
47 name=Opinion>Escribe tu opinión</textarea><br>");
48 }
49 out.println("<input type=submit value='Enviar'> <br>");
50 out.println("</form>");
51 }
52
53 if (Operacion.equals("Consulta")) {
54 ForoAccesoBD UtilidadesBD = new ForoAccesoBD("Foro");
55 UtilidadesBD.Lista("SELECT * FROM Temas",out);
56 UtilidadesBD.Cierra();
57 }
58
59 out.println("</body>");
60 out.println("</html>");
61 }
62 }

8.3.5 Inserción de temas: clase ForoInserccionTema

La clase ForoInserccionTema (línea 5), en su método doGet, recoge el


identificador y descripción (líneas 10 a 13) enviados desde la página HTML
generada por la clase ForoTema; después crea una instancia TemasForo de la clase
ForoAccesoBD (línea 15), que servirá para insertar los valores recogidos (líneas 16 y
17) y crear una nueva tabla (líneas 19 y 20) que albergará las opiniones de los
usuarios sobre ese nuevo tema. Finalmente se muestra un listado de la tabla temas
(línea 27) actualizada y se cierran los recursos abiertos (línea 31). Obsérvese el uso
de sentencias SQL para realizar todas las operaciones sobre la base de datos, como
método alternativo a la utilización de métodos del interfaz ResultSet, tales como
insertRow, deleteRow, updateXxxx, etc.
200 © JESÚS BOBADILLA SANCHO

1 import java.io.*;
2 import javax.servlet.*;
3 import javax.servlet.http.*;
4
5 public class ForoInserccionTema extends HttpServlet {
6
7 synchronized public void doGet(HttpServletRequest
8 request, HttpServletResponse response)
9 throws IOException, ServletException {
10 String Identificador =
11 request.getParameter("Identificador");
12 String Descripcion =
13 request.getParameter("Descripcion");
14
15 ForoAccesoBD TemasForo= new ForoAccesoBD("Foro");
16 TemasForo.Ejecuta("INSERT INTO Temas VALUES
17 ('"+Identificador+"','"+Descripcion+"')");
18
19 TemasForo.Ejecuta("CREATE TABLE "+Identificador+
20 " (Opinion CHAR(80))");
21
22 response.setContentType("text/html");
23 PrintWriter out = response.getWriter();
24 out.println("<html>");
25 out.println("<body>");
26 out.println("Tema: "+Identificador+" insertado <br>");
27 TemasForo.Lista("SELECT * FROM Temas",out);
28 out.println("</body>");
29 out.println("</html>");
30
31 TemasForo.Cierra();
32 }
33 }

8.3.6 Borrado de temas: clase ForoBorradoTema

Esta clase actúa de forma similar a ForoInserccionTema, pero realizando las


acciones contrarias: borra el registro correspondiente al tema seleccionado (líneas 13
y 14) y la tabla donde se guardan las opiniones de ese tema (línea 15). Mientras que
para crear una tabla utilizamos la sentencia “create table”, para borrarla hacemos uso
de “drop table”
 JESÚS BOBADILLA SANCHO 201

1 import java.io.*;
2 import javax.servlet.*;
3 import javax.servlet.http.*;
4
5 public class ForoBorradoTema extends HttpServlet {
6
7 synchronized public void doGet(HttpServletRequest
8 request, HttpServletResponse response)
9 throws IOException, ServletException {
10 String Tema = request.getParameter("Temas");
11
12 ForoAccesoBD TemasForo= new ForoAccesoBD("Foro");
13 TemasForo.Ejecuta("DELETE FROM Temas WHERE
14 Identificador='"+Tema+"'");
15 TemasForo.Ejecuta("DROP TABLE "+Tema);
16
17 response.setContentType("text/html");
18 PrintWriter out = response.getWriter();
19 out.println("<html>");
20 out.println("<body>");
21 out.println("Tema: "+Tema+" borrado <br>");
22 TemasForo.Lista("SELECT * FROM Temas",out);
23 out.println("</body>");
24 out.println("</html>");
25
26 TemasForo.Cierra();
27 }
28 }

8.3.7 Consulta de opiniones: clase ForoAccesoTema

Una vez que el usuario ha seleccionado el tema que desea consultar (usando
la lista desplegable generada por la clase ForoTema), el método doGet de
ForoAccesoTema puede recoger el tema escogido (línea 10). Con esta información
nos resulta muy sencillo crear un listado de la tabla correspondiente (líneas 12 y 19).
Como se puede observar, nuestra clase ForoAccesoBD nos simplifica mucho la
programación y evita la repetición innecesaria de código.
202 © JESÚS BOBADILLA SANCHO

1 import java.io.*;
2 import javax.servlet.*;
3 import javax.servlet.http.*;
4
5 public class ForoAccesoTema extends HttpServlet {
6
7 synchronized public void doGet(HttpServletRequest
8 request, HttpServletResponse response)
9 throws IOException, ServletException {
10 String Tema = request.getParameter("Temas");
11
12 ForoAccesoBD TemasForo= new ForoAccesoBD("Foro");
13
14 response.setContentType("text/html");
15 PrintWriter out = response.getWriter();
16 out.println("<html>");
17 out.println("<body>");
18 out.println("Tema: "+Tema+"<br>");
19 TemasForo.Lista("SELECT * FROM "+Tema,out);
20 out.println("</body>");
21 out.println("</html>");
22
23 TemasForo.Cierra();
24 }
25 }

8.3.8 Inserción de opiniones: clase ForoInsertarOpTema

Cuando se ejecuta el método doGet de la clase ForoInsertarOpTema, se


puede recoger el tema seleccionado por el usuario (línea 10) y su opinión (línea 11).
Con estos datos creamos una sentencia SQL que añade la opinión en la tabla
correspondiente al tema (línea 14). Posteriormente listamos la tabla actualizada
(línea 22).
 JESÚS BOBADILLA SANCHO 203

1 import java.io.*;
2 import javax.servlet.*;
3 import javax.servlet.http.*;
4
5 public class ForoInsertarOpTema extends HttpServlet {
6
7 synchronized public void doGet(HttpServletRequest
8 request, HttpServletResponse response)
9 throws IOException, ServletException {
10 String Tema = request.getParameter("Temas");
11 String Opinion = request.getParameter("Opinion");
12
13 ForoAccesoBD TemaForo= new ForoAccesoBD("Foro");
14 TemaForo.Ejecuta("INSERT INTO "+Tema+" VALUES
15 ('"+Opinion+"')");
16
17 response.setContentType("text/html");
18 PrintWriter out = response.getWriter();
19 out.println("<html>");
20 out.println("<body>");
21 out.println("Opinión: "+Opinion+" añadida <br>");
22 TemaForo.Lista("SELECT * FROM "+Tema,out);
23 out.println("</body>");
24 out.println("</html>");
25
26 TemaForo.Cierra();
27 }
28 }
204 © JESÚS BOBADILLA SANCHO

8.4 SERVICIO DE AGENDAS


8.4.1 Definición del ejemplo

Las agendas forman parte de los diversos servicios telemáticos que se


pueden ofrecer a empresas o usuarios particulares. Resulta muy útil disponer de una
agenda que podamos consultar y actualizar vía web, desde cualquier lugar y en todo
momento. Una ventaja añadida de este tipo de agendas es la posibilidad de actualizar
y compartir sus datos en tiempo real; por ejemplo, si un vendedor de una empresa da
de alta a un cliente en una provincia, inmediatamente sus datos se encuentran
accesibles a otros vendedores de delegaciones diferentes, que pueden ofertar
productos complementarios o más específicos a este nuevo cliente.

Existe la necesidad de ofrecer una agenda a cada cliente, de manera que


cada uno de esos clientes (posiblemente empresas) puedan ver los datos de su propia
agenda, pero no los datos de las demás agendas. Por otra parte, el número de
registros que utilizará cada cliente no es conocido a priori, y en algunos casos puede
ser alto (por ejemplo los abonados a una revista de ámbito nacional).

El diseño más adecuado para implementar el conjunto de agendas que


hemos especificado se basa en el uso de bases de datos, utilizando una base de datos
por cada cliente. Un diseño alternativo al anterior es la creación de una base de datos
que contenga una tabla por cada cliente; este es el enfoque que vamos a implementar
en nuestro ejemplo.

No debemos olvidar que los clientes que utilizan nuestro servicio de agenda
varían a lo largo del tiempo; es decir, a veces tendremos más clientes y a veces
tendremos menos. Vamos a programar la creación y borrado de agendas utilizando
la aplicación web, de manera que los propios clientes puedan dar de alta y de baja
sus agendas.

Una vez creada una agenda, permitiremos la inserción de datos (nombre,


email y telé fono de cada contacto), su consulta y borrado (a través del nombre) y el
listado de toda la agenda. Antes de ofrecer las opciones a nivel de contactos
(registros), presentaremos a los usuarios una lista desplegable de las agendas
existentes hasta el momento, de manera que se pueda seleccionar con facilidad la
agenda que se desea emplear.

Utilizando el núcleo de programación de agendas que se proporciona en este


ejemplo, resulta muy sencillo ampliar o personalizar los datos que se pueden
almacenar en las agendas (dirección, cargo, antigüedad, etc.), así como mejorar la
presentación visual de las páginas web con las que interaccionan nuestros clientes.
 JESÚS BOBADILLA SANCHO 205

8.4.2 Arquitectura de la aplicación

La clase Agenda es un servlet que genera la página web principal del


servicio. Aunque se podría haber implementado como fichero HTML, para mostrar
un enfoque alternativo a los ejemplos anteriores, se ha programado en Java. Las
opciones que muestra esta clase son: crear agenda, borrar agenda, consultar
contacto, insertar contacto, borrar contacto, listar toda la agenda.

La clase AgendaSelecciona se encarga de mostrar una lista desplegable con


todas las agendas existentes. Físicamente, estas agendas se almacenan en diferentes
tablas de una base de datos.

AgendaOpciones contiene el código necesario para crear y borrar agendas,


listar sus contenidos y preparar las páginas web que permitan a los clientes
introducir los datos de inserción, consulta y borrado de contactos (registros) en las
agendas. AgendaInCoBo se encarga de realizar la inserción, consulta o borrado de
registros en la agenda seleccionada.

Las clases que acceden a las tablas de nuestra base de datos utilizan el objeto
ForoAccesoBD, que fue implementado en el ejemplo “Foro” con la finalidad de
facilitar las operaciones que realicemos sobre bases de datos. La explicación
detallada de su funcionamiento se encuentra en el apartado anterior.

Agenda

AgendaSelecciona

AgendaOpciones

AgendaInCoBo

ForoAccesoBD
206 © JESÚS BOBADILLA SANCHO

La base de datos diseñada para esta aplicación tiene como nombre lógico:
Agenda. Inicialmente contiene una tabla vacía de nombre Agendas, constituida por
el campo Identificador, de tipo alfanumérico.

Por cada agenda que dé de alta un cliente, se añadirá en la tabla Agendas un


registro con el identificador que la define (introducido por el usuario); además se
creará una nueva tabla que tenga como nombre este identificador y con campos:
Nombre, email y Telefono, todos de tipo alfanumérico.

En cada agenda se podrán introducir los datos básicos (nombre, email y


teléfono) de las personas pertenecientes a un mismo grupo (alumnos, profesores,
clientes, proveedores, etc.).

Agenda

Agendas

Identificador

Alumnos

Profesores

Alumnos

Nombre email Telefono

Ana Paredes ana@upm.es 914589234

Pedro Flores pedro@upm.es 935708900

Profesores

Nombre email Telefono

Jorge Tejedor jorge@upm.es. 913452365


es
Santiago Alonso yago@upm.es 910023478
 JESÚS BOBADILLA SANCHO 207

8.4.3 Menú de opciones

La clase Agenda (línea 5) genera la siguiente página web:

Los botones de radio se agrupan con el nombre Operacion, que marcará la


opción seleccionada por el usuario: “Crear” (línea 19), “Borrar” (línea 21),
“Consulta” (línea 24), “Insercion” (línea 26), “Borrado” (línea 28) y “Listar” (línea
30). El servlet AgendaSelecciona (línea 17) se encarga de recoger el valor asociado
al nombre Operacion.

1 import java.io.*;
2 import javax.servlet.*;
3 import javax.servlet.http.*;
4
5 public class Agenda extends HttpServlet {
6
7 public void doGet(HttpServletRequest request,
8 HttpServletResponse response)
9 throws IOException, ServletException {
10
11 response.setContentType("text/html");
12 PrintWriter out = response.getWriter();
13 out.println("<html>");
14 out.println("<body>");
15
16 out.println("<form action='http://localhost:8080/
17 examples/servlet/AgendaSelecciona' method=get>");
18 out.println("<input type=radio name=Operacion
19 value=Crear> Crear agenda <br>");
20 out.println("<input type=radio name=Operacion
21 value=Borrar> Borrar agenda <br>");
22
23 out.println("<input type=radio name=Operacion
24 value=Consulta> Consultar contacto <br>");
25 out.println("<input type=radio name=Operacion
208 © JESÚS BOBADILLA SANCHO

26 value=Insercion> Insertar contacto <br>");


27 out.println("<input type=radio name=Operacion
28 value=Borrado> Borrar contacto<br>");
29 out.println("<input type=radio name=Operacion
30 value=Listar> Listar toda la agenda<br><br>");
31
32 out.println("<input type=submit value='Realizar la
33 operación'> <br>");
34 out.println("</form>");
35
36 out.println("</body>");
37 out.println("</html>");
38 }
39 }

8.4.4 Selección de una agenda

Antes de realizar ninguna acción concreta, debemos conocer la agenda sobre


la que desea actuar el usuario: si la opción seleccionada es “Crear agenda”, se
proporciona una caja de texto para introducir el identificador de la nueva agenda; si
se selecciona cualquier otra opción, se muestra una lista desplegable con los
identificadores de todas las agendas que se encuentran en el sistema.

El servlet AgendaSelecciona (línea 5) se encarga de crear las páginas web


que hemos diseñado: en la línea 21 se consulta si la opción seleccionada es “Crear”,
en cuyo caso se añade una caja de texto al formulario (líneas 22 y 23). En el resto de
los casos (línea 24) se define la lista desplegable (líneas 25 a 34).

Para obtener las agendas existentes en el sistema, consultamos los registros


de la tabla Agendas. En primer lugar nos creamos una instancia de la clase
ForoAccesoBD, sobre la base de datos Agenda (línea 25); utilizando la instancia,
 JESÚS BOBADILLA SANCHO 209

obtenemos un array de literales con los contenidos de los registros de la tabla


Agendas en el campo Identificador (líneas 26 y 27). Finalmente, en las líneas 30 a
34 construimos la lista desplegable tomando los valores individuales del array de
literales.

En las líneas 37 y 38 pasamos como campo oculto el valor de la operación


seleccionada por el usuario en la página web principal. El servlet de destino es
AgendaOpciones (línea 19).

1 import java.io.*;
2 import javax.servlet.*;
3 import javax.servlet.http.*;
4
5 public class AgendaSelecciona extends HttpServlet {
6
7 public void doGet(HttpServletRequest request,
8 HttpServletResponse response)
9 throws IOException, ServletException {
10
11 String Operacion = request.getParameter("Operacion");
12
13 response.setContentType("text/html");
14 PrintWriter out = response.getWriter();
15 out.println("<html>");
16 out.println("<body>");
17
18 out.println("<form action='http://localhost:8080/
19 examples/servlet/AgendaOpciones' method=get>");
20
21 if (Operacion.equals("Crear")) {
22 out.println("Nombre de la agenda: <input type=text
23 size=15 name=Agenda> <br>");
24 } else {
25 ForoAccesoBD UtilidadesBD=new ForoAccesoBD("Agenda");
26 String[] Identificadores =
27 UtilidadesBD.CreaLista("Agendas","Identificador");
28 UtilidadesBD.Cierra();
29
30 out.println("<select name=Agenda>");
31 for (int i=0;i<Identificadores.length;i++)
32 out.println("<option>"+Identificadores[i]+
33 "</option>");
34 out.println("</select><br><br>");
35 }
36
37 out.println("<input type=hidden name=Operacion
38 value="+Operacion+"><br>");
39 out.println("<input type=submit value='Enviar'> <br>");
210 © JESÚS BOBADILLA SANCHO

40 out.println("</form>");
41
42 out.println("</body>");
43 out.println("</html>");
44 }
45 }

8.4.5 Operaciones a nivel de agenda

El servlet AgendaOpciones (línea 5) recoge el identificador de la agenda


seleccionada por el usuario (línea 10) y la operación que se desea realizar (línea 11).
Si la operación pedida es la creación (línea 17), borrado (línea 25) o listado (línea
55) de una agenda, este servlet realiza la acción. Si la operación pedida es la
consulta, borrado o inserción de un registro (líneas 32 a 34) crea una página web de
introducción de los datos necesarios y delega el trabajo en el servlet AgendaInCoBo
(línea 36).

Para crear una agenda nueva (línea 17) es necesario realizar dos acciones:
insertar un nuevo registro en la tabla Agendas (líneas 19 y 20) y crear una tabla
vacía con el nombre de la agenda y los campos Nombre, email y Telefono (líneas 21
y 22).

Para borrar una agenda existente (línea 25), eliminamos el registro


correspondiente de la tabla Agendas (líneas 27 y 28) y borramos la tabla que
contiene los contactos (línea 29).

La operación de listar toda la agenda (línea 55), se resuelve utilizando el


método Lista (línea 57), perteneciente a la clase ForoAccesoBD.

Las operaciones a nivel de registros de una agenda (líneas 32 a 53) se


dividen en: “consultas y borrados”, en las que basta con conocer el nombre del
contacto (líneas 37 y 38) e “inserciones”, en las que, además, es necesario consultar
su email y teléfono (líneas 41 a 44). La clase AgendaInCoBo (Inserción, Consulta,
Borrado) realiza las operaciones a nivel de registros de las agendas.
 JESÚS BOBADILLA SANCHO 211

Obsérvese como, en las líneas 47 a 50, se traspasan los datos: identificador


de la agenda y operación a realizar, al servlet de destino (AgendaInCoBo ) de la
página web.

1 import java.io.*;
2 import javax.servlet.*;
3 import javax.servlet.http.*;
4
5 public class AgendaOpciones extends HttpServlet {
6
7 synchronized public void doGet(HttpServletRequest
8 request, HttpServletResponse response)
9 throws IOException, ServletException {
10 String Agenda = request.getParameter("Agenda");
11 String Operacion = request.getParameter("Operacion");
12
13 PrintWriter out = response.getWriter();
14 out.println("<html>");
15 out.println("<body>");
16
17 if (Operacion.equals("Crear")) {
18 ForoAccesoBD UtilidadesBD=new ForoAccesoBD("Agenda");
19 UtilidadesBD.Ejecuta("INSERT INTO Agendas VALUES
20 ('"+Agenda+"')");
21 UtilidadesBD.Ejecuta("CREATE TABLE "+Agenda+" (Nombre
22 CHAR(30),email CHAR(30),Telefono CHAR(10))");
23 }
24
25 if (Operacion.equals("Borrar")) {
26 ForoAccesoBD UtilidadesBD=new ForoAccesoBD("Agenda");
27 UtilidadesBD.Ejecuta("DELETE FROM Agendas WHERE
28 Identificador='"+Agenda+"'");
29 UtilidadesBD.Ejecuta("DROP TABLE "+Agenda);
30 }
31
32 if (Operacion.equals("Consulta")||
33 Operacion.equals("Borrado")||
34 Operacion.equals("Insercion")) {
35 out.println("<form action='http://localhost:8080/
36 examples/servlet/AgendaInCoBo' method=get>");
37 out.println("Nombre: <input type=text name=Nombre
212 © JESÚS BOBADILLA SANCHO

38 size=40><br>");
39
40 if (Operacion.equals("Insercion")) {
41 out.println("email: <input type=text name=email
42 size=20><br>");
43 out.println("Teléfono: <input type=text
44 name=Telefono size=10><br><br>");
45 }
46
47 out.println("<input type=hidden name=Agenda
48 value="+Agenda+">");
49 out.println("<input type=hidden name=Operacion
50 value="+Operacion+">");
51 out.println("<input type=submit> <br>");
52 out.println("</form>");
53 }
54
55 if (Operacion.equals("Listar")) {
56 ForoAccesoBD UtilidadesBD=new ForoAccesoBD("Agenda");
57 UtilidadesBD.Lista("SELECT * FROM "+Agenda,out);
58 UtilidadesBD.Cierra();
59 }
60
61 out.println("</body>");
62 out.println("</html>");
63 } }

8.4.6 Operaciones sobre los registros de las agendas

En primer lugar, en las líneas 10 a 12 de la clase AgendaInCoBo, se recogen


los valores Agenda (identificador de la agenda), Operacion (operación que hay que
realizar) y Nombre (valor del campo Nombre en la agenda). Las operaciones sobre
los registros de las agendas que implementa esta aplicación son: inserción, borrado
y consulta.

En la operación de inserción (línea 16) se recogen los valores referentes al


email y teléfono de la persona (líneas 17 y 18); posterior mente se introducen estos
valores (junto al nombre) en la tabla correspondiente (líneas 19 y 20). La operación
de borrado (línea 23) elimina de la agenda el registro que coincida con el nombre
proporcionado (líneas 24 y 25). La consulta (línea 28) genera una página web de
respuesta que muestra el registro cuyo nombre coincida con el nombre introducido
por el usuario (líneas 31 y 32).
 JESÚS BOBADILLA SANCHO 213

1 import java.io.*;
2 import javax.servlet.*;
3 import javax.servlet.http.*;
4
5 public class AgendaInCoBo extends HttpServlet {
6
7 synchronized public void doGet(HttpServletRequest
8 request, HttpServletResponse response)
9 throws IOException, ServletException {
10 String Agenda = request.getParameter("Agenda");
11 String Operacion = request.getParameter("Operacion");
12 String Nombre = request.getParameter("Nombre");
13
14 ForoAccesoBD UtilidadesBD= new ForoAccesoBD("Agenda");
15
16 if (Operacion.equals("Insercion")) {
17 String email = request.getParameter("email");
18 String Telefono = request.getParameter("Telefono");
19 UtilidadesBD.Ejecuta("INSERT INTO "+Agenda+" VALUES
20 ('"+Nombre+"','"+email+"','"+Telefono+"')");
21 }
22
23 if (Operacion.equals("Borrado")) {
24 UtilidadesBD.Ejecuta("DELETE FROM "+Agenda+" WHERE
25 Nombre='"+Nombre+"'");
26 }
27
28 if (Operacion.equals("Consulta")) {
29 PrintWriter out = response.getWriter();
30 out.println("<html><body>");
31 UtilidadesBD.Lista("SELECT * FROM "+Agenda+" WHERE
32 Nombre='"+Nombre+"'",out);
33 out.println("</body></html>");
34 }
35 UtilidadesBD.Cierra();
36 }
37 }

You might also like